@@ -4,6 +4,77 @@ import { config, authType, proxyUrl } from "@/auth.config";
4
4
import { ProxyAgent , fetch as undici } from "undici" ;
5
5
import MicrosoftEntraID from "next-auth/providers/microsoft-entra-id" ;
6
6
import { AuthType } from "@/utils/authenticationType" ;
7
+ import Credentials from "next-auth/providers/credentials" ;
8
+ import { User } from "next-auth" ;
9
+
10
+ // Implement the tenant switch provider directly in auth.ts
11
+ const tenantSwitchProvider = Credentials ( {
12
+ id : "tenant-switch" ,
13
+ name : "Tenant Switch" ,
14
+ credentials : {
15
+ tenantId : { label : "Tenant ID" , type : "text" } ,
16
+ sessionAsJson : { label : "Session" , type : "text" } ,
17
+ } ,
18
+ async authorize ( credentials , req ) : Promise < User | null > {
19
+ if ( ! credentials ?. tenantId ) {
20
+ throw new Error ( "No tenant ID provided" ) ;
21
+ }
22
+
23
+ let session = JSON . parse ( credentials . sessionAsJson as string ) ;
24
+
25
+ // Fallback to getting the user from cookies if session is not available
26
+ let user : any ;
27
+ if ( session ?. user ) {
28
+ user = session . user ;
29
+ } else {
30
+ // Try to get us er info from JWT token
31
+ const token = ( req as any ) ?. token ;
32
+ if ( token ) {
33
+ user = {
34
+ id : token . sub ,
35
+ name : token . name ,
36
+ email : token . email ,
37
+ tenantId : token . tenantId ,
38
+ tenantIds : token . tenantIds ,
39
+ } ;
40
+ }
41
+ }
42
+
43
+ if ( ! user || ! user . tenantIds ) {
44
+ console . error ( "Cannot switch tenant: User information not available" ) ;
45
+ throw new Error ( "User not authenticated or missing tenant information" ) ;
46
+ }
47
+
48
+ // Verify the tenant ID is valid for this user
49
+ const validTenant = user . tenantIds . find (
50
+ ( t : { tenant_id : string } ) => t . tenant_id === credentials . tenantId
51
+ ) ;
52
+
53
+ if ( ! validTenant ) {
54
+ console . error ( `Invalid tenant ID: ${ credentials . tenantId } ` ) ;
55
+ throw new Error ( "Invalid tenant ID for this user" ) ;
56
+ }
57
+
58
+ console . log ( `Switching to tenant: ${ credentials . tenantId } ` ) ;
59
+
60
+ // if user aleady have keepActiveTenant as prefix - remove it
61
+ if ( user . accessToken . startsWith ( "keepActiveTenant=" ) ) {
62
+ user . accessToken = user . accessToken . replace ( / k e e p A c t i v e T e n a n t = \w + & / , "" ) ;
63
+ }
64
+ // add keepActiveTenant= with the current tenant to user.accessToken
65
+ user . accessToken = `keepActiveTenant=${ credentials . tenantId } &${ user . accessToken } ` ;
66
+ // Return the user with the new tenant ID
67
+ return {
68
+ ...user ,
69
+ tenantId : credentials . tenantId ,
70
+ } ;
71
+ } ,
72
+ } ) ;
73
+
74
+ // Add the tenant switch provider to the config
75
+ // Use type assertion to add the tenant switch provider to the config
76
+ // This bypasses TypeScript's type checking for this specific operation
77
+ config . providers = [ ...config . providers , tenantSwitchProvider ] as any ;
7
78
8
79
function proxyFetch (
9
80
...args : Parameters < typeof fetch >
@@ -13,9 +84,7 @@ function proxyFetch(
13
84
"Proxy called for URL:" ,
14
85
args [ 0 ] instanceof Request ? args [ 0 ] . url : args [ 0 ]
15
86
) ;
16
-
17
87
const dispatcher = new ProxyAgent ( proxyUrl ! ) ;
18
-
19
88
if ( args [ 0 ] instanceof Request ) {
20
89
const request = args [ 0 ] ;
21
90
// @ts -expect-error `undici` has a `duplex` option
@@ -29,13 +98,11 @@ function proxyFetch(
29
98
if ( isDebug ) {
30
99
// Clone the response to log it without consuming the body
31
100
const clonedResponse = response . clone ( ) ;
32
-
33
101
console . log ( "Proxy response status:" , clonedResponse . status ) ;
34
102
console . log (
35
103
"Proxy response headers:" ,
36
104
Object . fromEntries ( clonedResponse . headers )
37
105
) ;
38
-
39
106
// Log response body only in debug mode
40
107
try {
41
108
const body = await clonedResponse . text ( ) ;
@@ -47,20 +114,17 @@ function proxyFetch(
47
114
return response ;
48
115
} ) ;
49
116
}
50
-
51
117
// @ts -expect-error `undici` has a `duplex` option
52
118
return undici ( args [ 0 ] , { ...( args [ 1 ] || { } ) , dispatcher } ) . then (
53
119
async ( response ) => {
54
120
if ( isDebug ) {
55
121
// Clone the response to log it without consuming the body
56
122
const clonedResponse = response . clone ( ) ;
57
-
58
123
console . log ( "Proxy response status:" , clonedResponse . status ) ;
59
124
console . log (
60
125
"Proxy response headers:" ,
61
126
Object . fromEntries ( clonedResponse . headers )
62
127
) ;
63
-
64
128
// Log response body only in debug mode
65
129
try {
66
130
const body = await clonedResponse . text ( ) ;
@@ -77,18 +141,15 @@ function proxyFetch(
77
141
// Modify the config if using Azure AD with proxy
78
142
if ( authType === AuthType . AZUREAD && proxyUrl ) {
79
143
const provider = config . providers [ 0 ] as ReturnType < typeof MicrosoftEntraID > ;
80
-
81
144
if ( ! proxyUrl ) {
82
145
console . log ( "Proxy is not enabled for Azure AD" ) ;
83
146
} else {
84
147
console . log ( "Proxy is enabled for Azure AD:" , proxyUrl ) ;
85
148
}
86
-
87
149
// Override the `customFetch` symbol in the provider
88
150
provider [ customFetch ] = async ( ...args : Parameters < typeof fetch > ) => {
89
151
const url = new URL ( args [ 0 ] instanceof Request ? args [ 0 ] . url : args [ 0 ] ) ;
90
152
console . log ( "Custom Fetch Intercepted:" , url . toString ( ) ) ;
91
-
92
153
// Handle `.well-known/openid-configuration` logic
93
154
if ( url . pathname . endsWith ( ".well-known/openid-configuration" ) ) {
94
155
console . log ( "Intercepting .well-known/openid-configuration" ) ;
@@ -111,11 +172,9 @@ if (authType === AuthType.AZUREAD && proxyUrl) {
111
172
console . log ( "Modified issuer:" , issuer ) ;
112
173
return Response . json ( { ...json , issuer } ) ;
113
174
}
114
-
115
175
// Fallback for all other requests
116
176
return proxyFetch ( ...args ) ;
117
177
} ;
118
-
119
178
// Override profile since it uses fetch without customFetch
120
179
provider . profile = async ( profile , tokens ) => {
121
180
// @tb : this causes 431 Request Header Fields Too Large
@@ -138,7 +197,6 @@ if (authType === AuthType.AZUREAD && proxyUrl) {
138
197
// }
139
198
// }
140
199
// https://stackoverflow.com/questions/77686104/how-to-resolve-http-error-431-nextjs-next-auth
141
-
142
200
return {
143
201
id : profile . sub ,
144
202
name : profile . name ,
@@ -149,6 +207,39 @@ if (authType === AuthType.AZUREAD && proxyUrl) {
149
207
} ;
150
208
}
151
209
152
- console . log ( "Starting Keep frontend with auth type:" , authType ) ;
210
+ // Modify the session callback to ensure tenantIds are available
211
+ const originalSessionCallback = config . callbacks . session ;
212
+ config . callbacks . session = async ( params ) => {
213
+ const session = await originalSessionCallback ( params ) ;
214
+
215
+ // Make sure tenantIds from the token are added to the session
216
+ if ( params . token && "tenantIds" in params . token ) {
217
+ session . user . tenantIds = params . token . tenantIds as {
218
+ tenant_id : string ;
219
+ tenant_name : string ;
220
+ } [ ] ;
221
+ }
222
+
223
+ // Also copy tenantIds from user object if available
224
+ if ( params . user && "tenantIds" in params . user ) {
225
+ session . user . tenantIds = params . user . tenantIds ;
226
+ }
227
+
228
+ return session ;
229
+ } ;
230
+
231
+ // Modify the JWT callback to preserve tenantIds
232
+ const originalJwtCallback = config . callbacks . jwt ;
233
+ config . callbacks . jwt = async ( params ) => {
234
+ const token = await originalJwtCallback ( params ) ;
153
235
236
+ // Make sure tenantIds from the user are preserved in the token
237
+ if ( params . user && "tenantIds" in params . user ) {
238
+ token . tenantIds = params . user . tenantIds ;
239
+ }
240
+
241
+ return token ;
242
+ } ;
243
+
244
+ console . log ( "Starting Keep frontend with auth type:" , authType ) ;
154
245
export const { handlers, auth, signIn, signOut } = NextAuth ( config ) ;
0 commit comments