@@ -104,6 +104,30 @@ export function createSamlIntegration<T extends SessionType>(
104
104
return samlMessage . profile
105
105
}
106
106
107
+ const validateSamlLogoutMessage = async (
108
+ req : express . Request
109
+ ) : Promise < Profile | null > => {
110
+ let samlMessage : { profile : Profile | null ; loggedOut : boolean }
111
+ if ( req . method === 'GET' ) {
112
+ const originalQuery = url . parse ( req . url ) . query ?? ''
113
+ samlMessage = await saml . validateRedirectAsync ( req . query , originalQuery )
114
+ } else if ( req . method === 'POST' ) {
115
+ samlMessage = isSamlPostRequest ( req )
116
+ ? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
117
+ await saml . validatePostRequestAsync ( req . body )
118
+ : // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
119
+ await saml . validatePostResponseAsync ( req . body )
120
+ } else {
121
+ throw new SamlError ( `Unsupported HTTP method ${ req . method } ` )
122
+ }
123
+ if ( ! samlMessage . loggedOut ) {
124
+ throw new SamlError (
125
+ 'Invalid SAML message type: expected logout request/response'
126
+ )
127
+ }
128
+ return samlMessage . profile
129
+ }
130
+
107
131
const login : AsyncRequestHandler = async ( req , res ) => {
108
132
logAuditEvent ( eventCode ( 'sign_in_started' ) , req , 'Login endpoint called' )
109
133
try {
@@ -234,33 +258,15 @@ export function createSamlIntegration<T extends SessionType>(
234
258
const logoutCallback : AsyncRequestHandler = async ( req , res ) => {
235
259
logAuditEvent ( eventCode ( 'sign_out' ) , req , 'Logout callback called' )
236
260
237
- let samlMessage : { profile : Profile | null ; loggedOut : boolean }
238
- if ( req . method === 'GET' ) {
239
- const originalQuery = url . parse ( req . url ) . query ?? ''
240
- samlMessage = await saml . validateRedirectAsync ( req . query , originalQuery )
241
- } else if ( req . method === 'POST' ) {
242
- samlMessage = isSamlPostRequest ( req )
243
- ? // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
244
- await saml . validatePostRequestAsync ( req . body )
245
- : // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
246
- await saml . validatePostResponseAsync ( req . body )
247
- } else {
248
- throw new SamlError ( `Unsupported HTTP method ${ req . method } ` )
249
- }
250
- if ( ! samlMessage . loggedOut ) {
251
- throw new SamlError (
252
- 'Invalid SAML message type: expected logout request/response'
253
- )
254
- }
255
-
256
261
try {
262
+ const profile = await validateSamlLogoutMessage ( req )
257
263
let url : string
258
264
// There are two scenarios:
259
265
// 1. IDP-initiated logout, and we've just received a logout request -> profile is not null, the SAML transaction
260
266
// is still in progress, and we should redirect the user back to the IDP
261
267
// 2. SP-initiated logout, and we've just received a logout response -> profile is null, the SAML transaction
262
268
// is complete, and we should redirect the user to some meaningful page
263
- if ( samlMessage . profile ) {
269
+ if ( profile ) {
264
270
let user : unknown
265
271
const sessionUser = sessions . getUser ( req )
266
272
if ( sessionUser ) {
@@ -271,16 +277,16 @@ export function createSamlIntegration<T extends SessionType>(
271
277
} else {
272
278
// We're possibly doing SLO without a real session (e.g. browser has
273
279
// 3rd party cookies disabled)
274
- const logoutToken = createLogoutToken ( samlMessage . profile )
280
+ const logoutToken = createLogoutToken ( profile )
275
281
const sessionUser = await sessions . logoutWithToken ( logoutToken )
276
282
const userId = SamlProfileIdSchema . safeParse ( sessionUser )
277
283
user = userId . success ? userId . data : undefined
278
284
}
279
- const profileId = SamlProfileIdSchema . safeParse ( samlMessage . profile )
285
+ const profileId = SamlProfileIdSchema . safeParse ( profile )
280
286
const success = profileId . success && _ . isEqual ( user , profileId . data )
281
287
282
288
url = await saml . getLogoutResponseUrlAsync (
283
- samlMessage . profile ,
289
+ profile ,
284
290
// not validated, because the value and its format are specified by the IDP and we're supposed to
285
291
// just pass it back
286
292
getRawUnvalidatedRelayState ( req ) ?? '' ,
0 commit comments