@@ -439,6 +439,22 @@ describe("OAuth Authorization", () => {
439
439
} ) ;
440
440
441
441
describe ( "exchangeAuthorization" , ( ) => {
442
+ const mockProvider : OAuthClientProvider = {
443
+ get redirectUrl ( ) { return "http://localhost:3000/callback" ; } ,
444
+ get clientMetadata ( ) {
445
+ return {
446
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
447
+ client_name : "Test Client" ,
448
+ } ;
449
+ } ,
450
+ clientInformation : jest . fn ( ) ,
451
+ tokens : jest . fn ( ) ,
452
+ saveTokens : jest . fn ( ) ,
453
+ redirectToAuthorization : jest . fn ( ) ,
454
+ saveCodeVerifier : jest . fn ( ) ,
455
+ codeVerifier : jest . fn ( ) ,
456
+ } ;
457
+
442
458
const validTokens = {
443
459
access_token : "access123" ,
444
460
token_type : "Bearer" ,
@@ -474,12 +490,11 @@ describe("OAuth Authorization", () => {
474
490
} ) ,
475
491
expect . objectContaining ( {
476
492
method : "POST" ,
477
- headers : {
478
- "Content-Type" : "application/x-www-form-urlencoded" ,
479
- } ,
480
493
} )
481
494
) ;
482
495
496
+ const headers = mockFetch . mock . calls [ 0 ] [ 1 ] . headers as Headers ;
497
+ expect ( headers . get ( "Content-Type" ) ) . toBe ( "application/x-www-form-urlencoded" ) ;
483
498
const body = mockFetch . mock . calls [ 0 ] [ 1 ] . body as URLSearchParams ;
484
499
expect ( body . get ( "grant_type" ) ) . toBe ( "authorization_code" ) ;
485
500
expect ( body . get ( "code" ) ) . toBe ( "code123" ) ;
@@ -489,6 +504,50 @@ describe("OAuth Authorization", () => {
489
504
expect ( body . get ( "redirect_uri" ) ) . toBe ( "http://localhost:3000/callback" ) ;
490
505
} ) ;
491
506
507
+ it ( "exchanges code for tokens with auth" , async ( ) => {
508
+ mockProvider . authToTokenEndpoint = function ( url : URL , headers : Headers , params : URLSearchParams ) {
509
+ headers . set ( "Authorization" , "Basic " + btoa ( validClientInfo . client_id + ":" + validClientInfo . client_secret ) ) ;
510
+ params . set ( "example_url" , url . toString ( ) ) ;
511
+ params . set ( "example_param" , "example_value" ) ;
512
+ } ;
513
+
514
+ mockFetch . mockResolvedValueOnce ( {
515
+ ok : true ,
516
+ status : 200 ,
517
+ json : async ( ) => validTokens ,
518
+ } ) ;
519
+
520
+ const tokens = await exchangeAuthorization ( "https://auth.example.com" , {
521
+ clientInformation : validClientInfo ,
522
+ authorizationCode : "code123" ,
523
+ codeVerifier : "verifier123" ,
524
+ redirectUri : "http://localhost:3000/callback" ,
525
+ } , mockProvider ) ;
526
+
527
+ expect ( tokens ) . toEqual ( validTokens ) ;
528
+ expect ( mockFetch ) . toHaveBeenCalledWith (
529
+ expect . objectContaining ( {
530
+ href : "https://auth.example.com/token" ,
531
+ } ) ,
532
+ expect . objectContaining ( {
533
+ method : "POST" ,
534
+ } )
535
+ ) ;
536
+
537
+ const headers = mockFetch . mock . calls [ 0 ] [ 1 ] . headers as Headers ;
538
+ expect ( headers . get ( "Content-Type" ) ) . toBe ( "application/x-www-form-urlencoded" ) ;
539
+ expect ( headers . get ( "Authorization" ) ) . toBe ( "Basic Y2xpZW50MTIzOnNlY3JldDEyMw==" ) ;
540
+ const body = mockFetch . mock . calls [ 0 ] [ 1 ] . body as URLSearchParams ;
541
+ expect ( body . get ( "grant_type" ) ) . toBe ( "authorization_code" ) ;
542
+ expect ( body . get ( "code" ) ) . toBe ( "code123" ) ;
543
+ expect ( body . get ( "code_verifier" ) ) . toBe ( "verifier123" ) ;
544
+ expect ( body . get ( "client_id" ) ) . toBe ( "client123" ) ;
545
+ expect ( body . get ( "redirect_uri" ) ) . toBe ( "http://localhost:3000/callback" ) ;
546
+ expect ( body . get ( "example_url" ) ) . toBe ( "https://auth.example.com/token" ) ;
547
+ expect ( body . get ( "example_param" ) ) . toBe ( "example_value" ) ;
548
+ expect ( body . get ( "client_secret" ) ) . toBeUndefined ;
549
+ } ) ;
550
+
492
551
it ( "validates token response schema" , async ( ) => {
493
552
mockFetch . mockResolvedValueOnce ( {
494
553
ok : true ,
@@ -527,6 +586,22 @@ describe("OAuth Authorization", () => {
527
586
} ) ;
528
587
529
588
describe ( "refreshAuthorization" , ( ) => {
589
+ const mockProvider : OAuthClientProvider = {
590
+ get redirectUrl ( ) { return "http://localhost:3000/callback" ; } ,
591
+ get clientMetadata ( ) {
592
+ return {
593
+ redirect_uris : [ "http://localhost:3000/callback" ] ,
594
+ client_name : "Test Client" ,
595
+ } ;
596
+ } ,
597
+ clientInformation : jest . fn ( ) ,
598
+ tokens : jest . fn ( ) ,
599
+ saveTokens : jest . fn ( ) ,
600
+ redirectToAuthorization : jest . fn ( ) ,
601
+ saveCodeVerifier : jest . fn ( ) ,
602
+ codeVerifier : jest . fn ( ) ,
603
+ } ;
604
+
530
605
const validTokens = {
531
606
access_token : "newaccess123" ,
532
607
token_type : "Bearer" ,
@@ -563,19 +638,58 @@ describe("OAuth Authorization", () => {
563
638
} ) ,
564
639
expect . objectContaining ( {
565
640
method : "POST" ,
566
- headers : {
567
- "Content-Type" : "application/x-www-form-urlencoded" ,
568
- } ,
569
641
} )
570
642
) ;
571
643
644
+ const headers = mockFetch . mock . calls [ 0 ] [ 1 ] . headers as Headers ;
645
+ expect ( headers . get ( "Content-Type" ) ) . toBe ( "application/x-www-form-urlencoded" ) ;
572
646
const body = mockFetch . mock . calls [ 0 ] [ 1 ] . body as URLSearchParams ;
573
647
expect ( body . get ( "grant_type" ) ) . toBe ( "refresh_token" ) ;
574
648
expect ( body . get ( "refresh_token" ) ) . toBe ( "refresh123" ) ;
575
649
expect ( body . get ( "client_id" ) ) . toBe ( "client123" ) ;
576
650
expect ( body . get ( "client_secret" ) ) . toBe ( "secret123" ) ;
577
651
} ) ;
578
652
653
+ it ( "exchanges refresh token for new tokens with auth" , async ( ) => {
654
+ mockProvider . authToTokenEndpoint = function ( url : URL , headers : Headers , params : URLSearchParams ) {
655
+ headers . set ( "Authorization" , "Basic " + btoa ( validClientInfo . client_id + ":" + validClientInfo . client_secret ) ) ;
656
+ params . set ( "example_url" , url . toString ( ) ) ;
657
+ params . set ( "example_param" , "example_value" ) ;
658
+ } ;
659
+
660
+ mockFetch . mockResolvedValueOnce ( {
661
+ ok : true ,
662
+ status : 200 ,
663
+ json : async ( ) => validTokensWithNewRefreshToken ,
664
+ } ) ;
665
+
666
+ const tokens = await refreshAuthorization ( "https://auth.example.com" , {
667
+ clientInformation : validClientInfo ,
668
+ refreshToken : "refresh123" ,
669
+ } , mockProvider ) ;
670
+
671
+ expect ( tokens ) . toEqual ( validTokensWithNewRefreshToken ) ;
672
+ expect ( mockFetch ) . toHaveBeenCalledWith (
673
+ expect . objectContaining ( {
674
+ href : "https://auth.example.com/token" ,
675
+ } ) ,
676
+ expect . objectContaining ( {
677
+ method : "POST" ,
678
+ } )
679
+ ) ;
680
+
681
+ const headers = mockFetch . mock . calls [ 0 ] [ 1 ] . headers as Headers ;
682
+ expect ( headers . get ( "Content-Type" ) ) . toBe ( "application/x-www-form-urlencoded" ) ;
683
+ expect ( headers . get ( "Authorization" ) ) . toBe ( "Basic Y2xpZW50MTIzOnNlY3JldDEyMw==" ) ;
684
+ const body = mockFetch . mock . calls [ 0 ] [ 1 ] . body as URLSearchParams ;
685
+ expect ( body . get ( "grant_type" ) ) . toBe ( "refresh_token" ) ;
686
+ expect ( body . get ( "refresh_token" ) ) . toBe ( "refresh123" ) ;
687
+ expect ( body . get ( "client_id" ) ) . toBe ( "client123" ) ;
688
+ expect ( body . get ( "example_url" ) ) . toBe ( "https://auth.example.com/token" ) ;
689
+ expect ( body . get ( "example_param" ) ) . toBe ( "example_value" ) ;
690
+ expect ( body . get ( "client_secret" ) ) . toBeUndefined ;
691
+ } ) ;
692
+
579
693
it ( "exchanges refresh token for new tokens and keep existing refresh token if none is returned" , async ( ) => {
580
694
mockFetch . mockResolvedValueOnce ( {
581
695
ok : true ,
0 commit comments