@@ -4214,6 +4214,250 @@ ca/T0LLtgmbMmxSv/MmzIg==
4214
4214
} ) ;
4215
4215
} ) ;
4216
4216
} ) ;
4217
+
4218
+ describe ( "startInteractiveLogin" , async ( ) => {
4219
+ const createAuthClient = async ( {
4220
+ pushedAuthorizationRequests = false ,
4221
+ signInReturnToPath = "/" ,
4222
+ authorizationParameters = { }
4223
+ } = { } ) => {
4224
+ const secret = await generateSecret ( 32 ) ;
4225
+ const transactionStore = new TransactionStore ( {
4226
+ secret
4227
+ } ) ;
4228
+ const sessionStore = new StatelessSessionStore ( {
4229
+ secret
4230
+ } ) ;
4231
+
4232
+ return new AuthClient ( {
4233
+ transactionStore,
4234
+ sessionStore,
4235
+
4236
+ domain : DEFAULT . domain ,
4237
+ clientId : DEFAULT . clientId ,
4238
+ clientSecret : DEFAULT . clientSecret ,
4239
+
4240
+ secret,
4241
+ appBaseUrl : DEFAULT . appBaseUrl ,
4242
+ signInReturnToPath,
4243
+ pushedAuthorizationRequests,
4244
+ authorizationParameters : {
4245
+ scope : "openid profile email" ,
4246
+ ...authorizationParameters
4247
+ } ,
4248
+
4249
+ fetch : getMockAuthorizationServer ( )
4250
+ } ) ;
4251
+ } ;
4252
+
4253
+ it ( "should use the default returnTo path when no returnTo is provided" , async ( ) => {
4254
+ const defaultReturnTo = "/default-path" ;
4255
+ const authClient = await createAuthClient ( {
4256
+ signInReturnToPath : defaultReturnTo
4257
+ } ) ;
4258
+
4259
+ // Mock the transactionStore.save method to verify the saved state
4260
+ const originalSave = authClient [ 'transactionStore' ] . save ;
4261
+ authClient [ 'transactionStore' ] . save = vi . fn ( async ( cookies , state ) => {
4262
+ expect ( state . returnTo ) . toBe ( defaultReturnTo ) ;
4263
+ return originalSave . call ( authClient [ 'transactionStore' ] , cookies , state ) ;
4264
+ } ) ;
4265
+
4266
+ await authClient . startInteractiveLogin ( ) ;
4267
+
4268
+ expect ( authClient [ 'transactionStore' ] . save ) . toHaveBeenCalled ( ) ;
4269
+ } ) ;
4270
+
4271
+ it ( "should sanitize and use the provided returnTo parameter" , async ( ) => {
4272
+ const authClient = await createAuthClient ( ) ;
4273
+ const returnTo = "/custom-return-path" ;
4274
+
4275
+ // Mock the transactionStore.save method to verify the saved state
4276
+ const originalSave = authClient [ 'transactionStore' ] . save ;
4277
+ authClient [ 'transactionStore' ] . save = vi . fn ( async ( cookies , state ) => {
4278
+ // The full URL is saved, not just the path
4279
+ expect ( state . returnTo ) . toBe ( "https://example.com/custom-return-path" ) ;
4280
+ return originalSave . call ( authClient [ 'transactionStore' ] , cookies , state ) ;
4281
+ } ) ;
4282
+
4283
+ await authClient . startInteractiveLogin ( { returnTo } ) ;
4284
+
4285
+ expect ( authClient [ 'transactionStore' ] . save ) . toHaveBeenCalled ( ) ;
4286
+ } ) ;
4287
+
4288
+ it ( "should reject unsafe returnTo URLs" , async ( ) => {
4289
+ const authClient = await createAuthClient ( {
4290
+ signInReturnToPath : "/safe-path"
4291
+ } ) ;
4292
+ const unsafeReturnTo = "https://malicious-site.com" ;
4293
+
4294
+ // Mock the transactionStore.save method to verify the saved state
4295
+ const originalSave = authClient [ 'transactionStore' ] . save ;
4296
+ authClient [ 'transactionStore' ] . save = vi . fn ( async ( cookies , state ) => {
4297
+ // Should use the default safe path instead of the malicious one
4298
+ expect ( state . returnTo ) . toBe ( "/safe-path" ) ;
4299
+ return originalSave . call ( authClient [ 'transactionStore' ] , cookies , state ) ;
4300
+ } ) ;
4301
+
4302
+ await authClient . startInteractiveLogin ( { returnTo : unsafeReturnTo } ) ;
4303
+
4304
+ expect ( authClient [ 'transactionStore' ] . save ) . toHaveBeenCalled ( ) ;
4305
+ } ) ;
4306
+
4307
+ it ( "should pass authorization parameters to the authorization URL" , async ( ) => {
4308
+ const authClient = await createAuthClient ( ) ;
4309
+ const authorizationParameters = {
4310
+ audience : "https://api.example.com" ,
4311
+ scope : "openid profile email custom_scope"
4312
+ } ;
4313
+
4314
+ // Spy on the authorizationUrl method to verify the passed params
4315
+ const originalAuthorizationUrl = authClient [ 'authorizationUrl' ] ;
4316
+ authClient [ 'authorizationUrl' ] = vi . fn ( async ( params ) => {
4317
+ // Verify the audience is set correctly
4318
+ expect ( params . get ( "audience" ) ) . toBe ( authorizationParameters . audience ) ;
4319
+ // Verify the scope is set correctly
4320
+ expect ( params . get ( "scope" ) ) . toBe ( authorizationParameters . scope ) ;
4321
+ return originalAuthorizationUrl . call ( authClient , params ) ;
4322
+ } ) ;
4323
+
4324
+ await authClient . startInteractiveLogin ( { authorizationParameters } ) ;
4325
+
4326
+ expect ( authClient [ 'authorizationUrl' ] ) . toHaveBeenCalled ( ) ;
4327
+ } ) ;
4328
+
4329
+ it ( "should handle pushed authorization requests (PAR) correctly" , async ( ) => {
4330
+ let parRequestCalled = false ;
4331
+ const mockFetch = getMockAuthorizationServer ( {
4332
+ onParRequest : async ( ) => {
4333
+ parRequestCalled = true ;
4334
+ }
4335
+ } ) ;
4336
+
4337
+ const secret = await generateSecret ( 32 ) ;
4338
+ const transactionStore = new TransactionStore ( { secret } ) ;
4339
+ const sessionStore = new StatelessSessionStore ( { secret } ) ;
4340
+
4341
+ const authClient = new AuthClient ( {
4342
+ transactionStore,
4343
+ sessionStore,
4344
+ domain : DEFAULT . domain ,
4345
+ clientId : DEFAULT . clientId ,
4346
+ clientSecret : DEFAULT . clientSecret ,
4347
+ secret,
4348
+ appBaseUrl : DEFAULT . appBaseUrl ,
4349
+ pushedAuthorizationRequests : true ,
4350
+ authorizationParameters : {
4351
+ scope : "openid profile email"
4352
+ } ,
4353
+ fetch : mockFetch
4354
+ } ) ;
4355
+
4356
+ await authClient . startInteractiveLogin ( ) ;
4357
+
4358
+ // Verify that PAR was used
4359
+ expect ( parRequestCalled ) . toBe ( true ) ;
4360
+ } ) ;
4361
+
4362
+ it ( "should save the transaction state with correct values" , async ( ) => {
4363
+ const authClient = await createAuthClient ( ) ;
4364
+ const returnTo = "/custom-path" ;
4365
+
4366
+ // Instead of mocking the oauth functions, we'll just check the structure of the transaction state
4367
+ const originalSave = authClient [ 'transactionStore' ] . save ;
4368
+ authClient [ 'transactionStore' ] . save = vi . fn ( async ( cookies , transactionState ) => {
4369
+ expect ( transactionState ) . toEqual ( expect . objectContaining ( {
4370
+ nonce : expect . any ( String ) ,
4371
+ codeVerifier : expect . any ( String ) ,
4372
+ responseType : "code" ,
4373
+ state : expect . any ( String ) ,
4374
+ returnTo : "https://example.com/custom-path"
4375
+ } ) ) ;
4376
+ return originalSave . call ( authClient [ 'transactionStore' ] , cookies , transactionState ) ;
4377
+ } ) ;
4378
+
4379
+ await authClient . startInteractiveLogin ( { returnTo } ) ;
4380
+
4381
+ expect ( authClient [ 'transactionStore' ] . save ) . toHaveBeenCalled ( ) ;
4382
+ } ) ;
4383
+
4384
+ it ( "should merge configuration authorizationParameters with method arguments" , async ( ) => {
4385
+ const configScope = "openid profile email" ;
4386
+ const configAudience = "https://default-api.example.com" ;
4387
+ const authClient = await createAuthClient ( {
4388
+ authorizationParameters : {
4389
+ scope : configScope ,
4390
+ audience : configAudience
4391
+ }
4392
+ } ) ;
4393
+
4394
+ const methodScope = "openid profile email custom_scope" ;
4395
+ const methodAudience = "https://custom-api.example.com" ;
4396
+
4397
+ // Spy on the authorizationUrl method to verify the passed params
4398
+ const originalAuthorizationUrl = authClient [ 'authorizationUrl' ] ;
4399
+ authClient [ 'authorizationUrl' ] = vi . fn ( async ( params ) => {
4400
+ // Method's authorization parameters should override config
4401
+ expect ( params . get ( "audience" ) ) . toBe ( methodAudience ) ;
4402
+ expect ( params . get ( "scope" ) ) . toBe ( methodScope ) ;
4403
+ return originalAuthorizationUrl . call ( authClient , params ) ;
4404
+ } ) ;
4405
+
4406
+ await authClient . startInteractiveLogin ( {
4407
+ authorizationParameters : {
4408
+ scope : methodScope ,
4409
+ audience : methodAudience
4410
+ }
4411
+ } ) ;
4412
+
4413
+ expect ( authClient [ 'authorizationUrl' ] ) . toHaveBeenCalled ( ) ;
4414
+ } ) ;
4415
+
4416
+ // Add tests for handleLogin method
4417
+ it ( "should create correct options in handleLogin with returnTo parameter" , async ( ) => {
4418
+ const authClient = await createAuthClient ( ) ;
4419
+
4420
+ // Mock startInteractiveLogin to check what options are passed to it
4421
+ const originalStartInteractiveLogin = authClient . startInteractiveLogin ;
4422
+ authClient . startInteractiveLogin = vi . fn ( async ( options ) => {
4423
+ expect ( options ) . toEqual ( {
4424
+ authorizationParameters : { foo : "bar" , returnTo : "custom-return" } ,
4425
+ returnTo : "custom-return"
4426
+ } ) ;
4427
+ return originalStartInteractiveLogin . call ( authClient , options ) ;
4428
+ } ) ;
4429
+
4430
+ const reqUrl = new URL ( "https://example.com/auth/login?foo=bar&returnTo=custom-return" ) ;
4431
+ const req = new NextRequest ( reqUrl , { method : "GET" } ) ;
4432
+
4433
+ await authClient . handleLogin ( req ) ;
4434
+
4435
+ expect ( authClient . startInteractiveLogin ) . toHaveBeenCalled ( ) ;
4436
+ } ) ;
4437
+
4438
+ it ( "should handle PAR correctly in handleLogin by not forwarding params" , async ( ) => {
4439
+ const authClient = await createAuthClient ( {
4440
+ pushedAuthorizationRequests : true
4441
+ } ) ;
4442
+
4443
+ // Mock startInteractiveLogin to check what options are passed to it
4444
+ const originalStartInteractiveLogin = authClient . startInteractiveLogin ;
4445
+ authClient . startInteractiveLogin = vi . fn ( async ( options ) => {
4446
+ expect ( options ) . toEqual ( {
4447
+ authorizationParameters : { } ,
4448
+ returnTo : "custom-return"
4449
+ } ) ;
4450
+ return originalStartInteractiveLogin . call ( authClient , options ) ;
4451
+ } ) ;
4452
+
4453
+ const reqUrl = new URL ( "https://example.com/auth/login?foo=bar&returnTo=custom-return" ) ;
4454
+ const req = new NextRequest ( reqUrl , { method : "GET" } ) ;
4455
+
4456
+ await authClient . handleLogin ( req ) ;
4457
+
4458
+ expect ( authClient . startInteractiveLogin ) . toHaveBeenCalled ( ) ;
4459
+ } ) ;
4460
+ } ) ;
4217
4461
} ) ;
4218
4462
4219
4463
const _authorizationServerMetadata = {
0 commit comments