@@ -2,6 +2,7 @@ import { LATEST_PROTOCOL_VERSION } from '../types.js';
2
2
import {
3
3
discoverOAuthMetadata ,
4
4
discoverAuthorizationServerMetadata ,
5
+ buildDiscoveryUrls ,
5
6
startAuthorization ,
6
7
exchangeAuthorization ,
7
8
refreshAuthorization ,
@@ -684,6 +685,55 @@ describe("OAuth Authorization", () => {
684
685
} ) ;
685
686
} ) ;
686
687
688
+ describe ( "buildDiscoveryUrls" , ( ) => {
689
+ it ( "generates correct URLs for server without path" , ( ) => {
690
+ const urls = buildDiscoveryUrls ( "https://auth.example.com" ) ;
691
+
692
+ expect ( urls ) . toHaveLength ( 2 ) ;
693
+ expect ( urls . map ( u => ( { url : u . url . toString ( ) , type : u . type } ) ) ) . toEqual ( [
694
+ {
695
+ url : "https://auth.example.com/.well-known/oauth-authorization-server" ,
696
+ type : "oauth"
697
+ } ,
698
+ {
699
+ url : "https://auth.example.com/.well-known/openid-configuration" ,
700
+ type : "oidc"
701
+ }
702
+ ] ) ;
703
+ } ) ;
704
+
705
+ it ( "generates correct URLs for server with path" , ( ) => {
706
+ const urls = buildDiscoveryUrls ( "https://auth.example.com/tenant1" ) ;
707
+
708
+ expect ( urls ) . toHaveLength ( 4 ) ;
709
+ expect ( urls . map ( u => ( { url : u . url . toString ( ) , type : u . type } ) ) ) . toEqual ( [
710
+ {
711
+ url : "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
712
+ type : "oauth"
713
+ } ,
714
+ {
715
+ url : "https://auth.example.com/.well-known/oauth-authorization-server" ,
716
+ type : "oauth"
717
+ } ,
718
+ {
719
+ url : "https://auth.example.com/.well-known/openid-configuration/tenant1" ,
720
+ type : "oidc"
721
+ } ,
722
+ {
723
+ url : "https://auth.example.com/tenant1/.well-known/openid-configuration" ,
724
+ type : "oidc"
725
+ }
726
+ ] ) ;
727
+ } ) ;
728
+
729
+ it ( "handles URL object input" , ( ) => {
730
+ const urls = buildDiscoveryUrls ( new URL ( "https://auth.example.com/tenant1" ) ) ;
731
+
732
+ expect ( urls ) . toHaveLength ( 4 ) ;
733
+ expect ( urls [ 0 ] . url . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
734
+ } ) ;
735
+ } ) ;
736
+
687
737
describe ( "discoverAuthorizationServerMetadata" , ( ) => {
688
738
const validOAuthMetadata = {
689
739
issuer : "https://auth.example.com" ,
@@ -705,138 +755,33 @@ describe("OAuth Authorization", () => {
705
755
code_challenge_methods_supported : [ "S256" ] ,
706
756
} ;
707
757
708
- describe ( "discovery sequence" , ( ) => {
709
- const testCases = [
710
- {
711
- description : "no path - direct OAuth success" ,
712
- serverUrl : "https://auth.example.com" ,
713
- responses : [ { success : true , metadata : validOAuthMetadata } ] ,
714
- expectedPaths : [
715
- "https://auth.example.com/.well-known/oauth-authorization-server"
716
- ]
717
- } ,
718
- {
719
- description : "no path - OAuth fails, OIDC succeeds" ,
720
- serverUrl : "https://auth.example.com" ,
721
- responses : [
722
- { success : false , status : 404 } ,
723
- { success : true , metadata : validOpenIdMetadata }
724
- ] ,
725
- expectedPaths : [
726
- "https://auth.example.com/.well-known/oauth-authorization-server" ,
727
- "https://auth.example.com/.well-known/openid-configuration"
728
- ]
729
- } ,
730
- {
731
- description : "with path - path OAuth succeeds" ,
732
- serverUrl : "https://auth.example.com/tenant1" ,
733
- responses : [ { success : true , metadata : validOAuthMetadata } ] ,
734
- expectedPaths : [
735
- "https://auth.example.com/.well-known/oauth-authorization-server/tenant1"
736
- ]
737
- } ,
738
- {
739
- description : "with path - path OAuth fails, root OAuth succeeds" ,
740
- serverUrl : "https://auth.example.com/tenant1" ,
741
- responses : [
742
- { success : false , status : 404 } ,
743
- { success : true , metadata : validOAuthMetadata }
744
- ] ,
745
- expectedPaths : [
746
- "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
747
- "https://auth.example.com/.well-known/oauth-authorization-server"
748
- ]
749
- } ,
750
- {
751
- description : "with path - OAuth fails, OIDC path insertion succeeds" ,
752
- serverUrl : "https://auth.example.com/tenant1" ,
753
- responses : [
754
- { success : false , status : 404 } , // OAuth path
755
- { success : false , status : 404 } , // OAuth root
756
- { success : true , metadata : validOpenIdMetadata } // OIDC path insertion
757
- ] ,
758
- expectedPaths : [
759
- "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
760
- "https://auth.example.com/.well-known/oauth-authorization-server" ,
761
- "https://auth.example.com/.well-known/openid-configuration/tenant1"
762
- ]
763
- } ,
764
- {
765
- description : "with path - OAuth fails, OIDC path appending succeeds" ,
766
- serverUrl : "https://auth.example.com/tenant1" ,
767
- responses : [
768
- { success : false , status : 404 } , // OAuth path
769
- { success : false , status : 404 } , // OAuth root
770
- { success : false , status : 404 } , // OIDC path insertion
771
- { success : true , metadata : validOpenIdMetadata } // OIDC path appending
772
- ] ,
773
- expectedPaths : [
774
- "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
775
- "https://auth.example.com/.well-known/oauth-authorization-server" ,
776
- "https://auth.example.com/.well-known/openid-configuration/tenant1" ,
777
- "https://auth.example.com/tenant1/.well-known/openid-configuration"
778
- ]
779
- } ,
780
- {
781
- description : "with path - all fails, returns undefined" ,
782
- serverUrl : "https://auth.example.com/tenant1" ,
783
- responses : [
784
- { success : false , status : 404 } , // OAuth path
785
- { success : false , status : 404 } , // OAuth root
786
- { success : false , status : 404 } , // OIDC path insertion
787
- { success : false , status : 404 } , // OIDC path appending
788
- ] ,
789
- expectedPaths : [
790
- "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ,
791
- "https://auth.example.com/.well-known/oauth-authorization-server" ,
792
- "https://auth.example.com/.well-known/openid-configuration/tenant1" ,
793
- "https://auth.example.com/tenant1/.well-known/openid-configuration"
794
- ]
795
- }
796
- ] ;
797
-
798
- testCases . forEach ( ( { description, serverUrl, responses, expectedPaths } ) => {
799
- it ( description , async ( ) => {
800
- // Set up mock responses
801
- responses . forEach ( response => {
802
- if ( response . success ) {
803
- mockFetch . mockResolvedValueOnce ( {
804
- ok : true ,
805
- status : 200 ,
806
- json : async ( ) => response . metadata
807
- } ) ;
808
- } else {
809
- mockFetch . mockResolvedValueOnce ( {
810
- ok : false ,
811
- status : response . status || 404
812
- } ) ;
813
- }
814
- } ) ;
815
-
816
- const metadata = await discoverAuthorizationServerMetadata (
817
- serverUrl
818
- ) ;
819
-
820
- // Verify result
821
- const successResponse = responses . find ( r => r . success ) ;
822
- if ( successResponse ) {
823
- expect ( metadata ) . toEqual ( successResponse . metadata ) ;
824
- } else {
825
- expect ( metadata ) . toBeUndefined ( ) ;
826
- }
758
+ it ( "tries URLs in order and returns first successful metadata" , async ( ) => {
759
+ // First OAuth URL fails with 404
760
+ mockFetch . mockResolvedValueOnce ( {
761
+ ok : false ,
762
+ status : 404 ,
763
+ } ) ;
764
+
765
+ // Second OAuth URL (root) succeeds
766
+ mockFetch . mockResolvedValueOnce ( {
767
+ ok : true ,
768
+ status : 200 ,
769
+ json : async ( ) => validOAuthMetadata ,
770
+ } ) ;
827
771
828
- // Verify discovery sequence
829
- const calls = mockFetch . mock . calls ;
830
- expect ( calls . length ) . toBe ( expectedPaths . length ) ;
772
+ const metadata = await discoverAuthorizationServerMetadata (
773
+ "https://auth.example.com/tenant1"
774
+ ) ;
831
775
832
- expectedPaths . forEach ( ( expectedPath , index ) => {
833
- expect ( calls [ index ] [ 0 ] . toString ( ) ) . toBe ( expectedPath ) ;
834
- } ) ;
835
- } ) ;
836
- } ) ;
776
+ expect ( metadata ) . toEqual ( validOAuthMetadata ) ;
777
+
778
+ // Verify it tried the URLs in the correct order
779
+ const calls = mockFetch . mock . calls ;
780
+ expect ( calls . length ) . toBe ( 2 ) ;
781
+ expect ( calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server/tenant1" ) ;
782
+ expect ( calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( "https://auth.example.com/.well-known/oauth-authorization-server" ) ;
837
783
} ) ;
838
784
839
-
840
785
it ( "throws error when OIDC provider does not support S256 PKCE" , async ( ) => {
841
786
// OAuth discovery fails
842
787
mockFetch . mockResolvedValueOnce ( {
0 commit comments