5
5
createCallback ,
6
6
getActiveIdentityProviders ,
7
7
getAuthRequest ,
8
+ getLoginSettings ,
8
9
getOrgsByDomain ,
9
10
listSessions ,
10
11
startIdentityProviderFlow ,
@@ -37,7 +38,32 @@ const ORG_SCOPE_REGEX = /urn:zitadel:iam:org:id:([0-9]+)/;
37
38
const ORG_DOMAIN_SCOPE_REGEX = / u r n : z i t a d e l : i a m : o r g : d o m a i n : p r i m a r y : ( .+ ) / ; // TODO: check regex for all domain character options
38
39
const IDP_SCOPE_REGEX = / u r n : z i t a d e l : i a m : o r g : i d p : i d : ( .+ ) / ;
39
40
40
- function isSessionValid ( session : Session ) : boolean {
41
+ /**
42
+ * mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.)
43
+ * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId);
44
+ **/
45
+ async function isSessionValid (
46
+ session : Session ,
47
+ checkLoginSettings ?: boolean ,
48
+ ) : Promise < boolean > {
49
+ let mfaValid = true ;
50
+ if ( checkLoginSettings && session . factors ?. user ?. organizationId ) {
51
+ const loginSettings = await getLoginSettings (
52
+ session . factors ?. user ?. organizationId ,
53
+ ) ;
54
+ if ( loginSettings ?. forceMfa || loginSettings ?. forceMfaLocalOnly ) {
55
+ const otpEmail = session . factors . otpEmail ?. verifiedAt ;
56
+ const otpSms = session . factors . otpSms ?. verifiedAt ;
57
+ const totp = session . factors . totp ?. verifiedAt ;
58
+ const webAuthN = session . factors . webAuthN ?. verifiedAt ;
59
+
60
+ // must have one single check
61
+ mfaValid = ! ! ( otpEmail || otpSms || totp || webAuthN ) ;
62
+ } else {
63
+ mfaValid = true ;
64
+ }
65
+ }
66
+
41
67
const validPassword = session ?. factors ?. password ?. verifiedAt ;
42
68
const validPasskey = session ?. factors ?. webAuthN ?. verifiedAt ;
43
69
const validIDP = session ?. factors ?. intent ?. verifiedAt ;
@@ -46,43 +72,44 @@ function isSessionValid(session: Session): boolean {
46
72
? timestampDate ( session . expirationDate ) > new Date ( )
47
73
: true ;
48
74
49
- const validFactors = ! ! (
50
- ( validPassword || validPasskey || validIDP ) &&
51
- stillValid
52
- ) ;
75
+ const validFactors = ! ! ( validPassword || validPasskey || validIDP ) ;
53
76
54
- return stillValid && validFactors ;
77
+ return stillValid && validFactors && mfaValid ;
55
78
}
56
79
57
- function findValidSession (
80
+ async function findValidSession (
58
81
sessions : Session [ ] ,
59
82
authRequest : AuthRequest ,
60
- ) : Session | undefined {
61
- const validSessionsWithHint = sessions
62
- . filter ( ( s ) => {
63
- if ( authRequest . hintUserId ) {
64
- return s . factors ?. user ?. id === authRequest . hintUserId ;
65
- }
66
- if ( authRequest . loginHint ) {
67
- return s . factors ?. user ?. loginName === authRequest . loginHint ;
68
- }
69
- return true ;
70
- } )
71
- . filter ( isSessionValid ) ;
83
+ ) : Promise < Session | undefined > {
84
+ const sessionsWithHint = sessions . filter ( ( s ) => {
85
+ if ( authRequest . hintUserId ) {
86
+ return s . factors ?. user ?. id === authRequest . hintUserId ;
87
+ }
88
+ if ( authRequest . loginHint ) {
89
+ return s . factors ?. user ?. loginName === authRequest . loginHint ;
90
+ }
91
+ return true ;
92
+ } ) ;
72
93
73
- if ( validSessionsWithHint . length === 0 ) {
94
+ if ( sessionsWithHint . length === 0 ) {
74
95
return undefined ;
75
96
}
76
97
77
98
// sort by change date descending
78
- validSessionsWithHint . sort ( ( a , b ) => {
99
+ sessionsWithHint . sort ( ( a , b ) => {
79
100
const dateA = a . changeDate ? timestampDate ( a . changeDate ) . getTime ( ) : 0 ;
80
101
const dateB = b . changeDate ? timestampDate ( b . changeDate ) . getTime ( ) : 0 ;
81
102
return dateB - dateA ;
82
103
} ) ;
83
104
84
- // return most recently changed session
85
- return sessions [ 0 ] ;
105
+ // return the first valid session according to settings
106
+ for ( const session of sessionsWithHint ) {
107
+ if ( await isSessionValid ( session , true ) ) {
108
+ return session ;
109
+ }
110
+ }
111
+
112
+ return undefined ;
86
113
}
87
114
88
115
export async function GET ( request : NextRequest ) {
@@ -103,53 +130,51 @@ export async function GET(request: NextRequest) {
103
130
sessions = await loadSessions ( ids ) ;
104
131
}
105
132
106
- /**
107
- * TODO: before automatically redirecting to the callbackUrl, check if the session is still valid
108
- * possible scenaio:
109
- * mfa is required, session is not valid anymore (e.g. session expired, user logged out, etc.)
110
- * to check for mfa for automatically selected session -> const response = await listAuthenticationMethodTypes(userId);
111
- **/
112
-
113
133
if ( authRequestId && sessionId ) {
114
134
console . log (
115
135
`Login with session: ${ sessionId } and authRequest: ${ authRequestId } ` ,
116
136
) ;
117
137
118
- let selectedSession = sessions . find ( ( s ) => s . id === sessionId ) ;
138
+ const selectedSession = sessions . find ( ( s ) => s . id === sessionId ) ;
119
139
120
140
if ( selectedSession && selectedSession . id ) {
121
141
console . log ( `Found session ${ selectedSession . id } ` ) ;
122
- const cookie = sessionCookies . find (
123
- ( cookie ) => cookie . id === selectedSession ?. id ,
124
- ) ;
125
142
126
- if ( cookie && cookie . id && cookie . token ) {
127
- const session = {
128
- sessionId : cookie ?. id ,
129
- sessionToken : cookie ?. token ,
130
- } ;
143
+ const isValid = await isSessionValid ( selectedSession , true ) ;
131
144
132
- // works not with _rsc request
133
- try {
134
- const { callbackUrl } = await createCallback (
135
- create ( CreateCallbackRequestSchema , {
136
- authRequestId,
137
- callbackKind : {
138
- case : "session" ,
139
- value : create ( SessionSchema , session ) ,
140
- } ,
141
- } ) ,
142
- ) ;
143
- if ( callbackUrl ) {
144
- return NextResponse . redirect ( callbackUrl ) ;
145
- } else {
146
- return NextResponse . json (
147
- { error : "An error occurred!" } ,
148
- { status : 500 } ,
145
+ if ( isValid ) {
146
+ const cookie = sessionCookies . find (
147
+ ( cookie ) => cookie . id === selectedSession ?. id ,
148
+ ) ;
149
+
150
+ if ( cookie && cookie . id && cookie . token ) {
151
+ const session = {
152
+ sessionId : cookie ?. id ,
153
+ sessionToken : cookie ?. token ,
154
+ } ;
155
+
156
+ // works not with _rsc request
157
+ try {
158
+ const { callbackUrl } = await createCallback (
159
+ create ( CreateCallbackRequestSchema , {
160
+ authRequestId,
161
+ callbackKind : {
162
+ case : "session" ,
163
+ value : create ( SessionSchema , session ) ,
164
+ } ,
165
+ } ) ,
149
166
) ;
167
+ if ( callbackUrl ) {
168
+ return NextResponse . redirect ( callbackUrl ) ;
169
+ } else {
170
+ return NextResponse . json (
171
+ { error : "An error occurred!" } ,
172
+ { status : 500 } ,
173
+ ) ;
174
+ }
175
+ } catch ( error ) {
176
+ return NextResponse . json ( { error } , { status : 500 } ) ;
150
177
}
151
- } catch ( error ) {
152
- return NextResponse . json ( { error } , { status : 500 } ) ;
153
178
}
154
179
}
155
180
}
@@ -314,7 +339,7 @@ export async function GET(request: NextRequest) {
314
339
* This means that the user should not be prompted to enter their password again.
315
340
* Instead, the server attempts to silently authenticate the user using an existing session or other authentication mechanisms that do not require user interaction
316
341
**/
317
- const selectedSession = findValidSession ( sessions , authRequest ) ;
342
+ const selectedSession = await findValidSession ( sessions , authRequest ) ;
318
343
319
344
if ( ! selectedSession || ! selectedSession . id ) {
320
345
return NextResponse . json (
@@ -351,7 +376,7 @@ export async function GET(request: NextRequest) {
351
376
return NextResponse . redirect ( callbackUrl ) ;
352
377
} else {
353
378
// check for loginHint, userId hint and valid sessions
354
- let selectedSession = findValidSession ( sessions , authRequest ) ;
379
+ let selectedSession = await findValidSession ( sessions , authRequest ) ;
355
380
356
381
if ( ! selectedSession || ! selectedSession . id ) {
357
382
return gotoAccounts ( ) ;
0 commit comments