@@ -1596,10 +1596,81 @@ describe('OAuth Authorization', () => {
15961596 // First call should be to protected resource metadata
15971597 expect ( mockFetch . mock . calls [ 0 ] [ 0 ] . toString ( ) ) . toBe ( 'https://resource.example.com/.well-known/oauth-protected-resource' ) ;
15981598
1599- // Second call should be to oauth metadata
1599+ // Second call should be to oauth metadata at the root path
16001600 expect ( mockFetch . mock . calls [ 1 ] [ 0 ] . toString ( ) ) . toBe ( 'https://resource.example.com/.well-known/oauth-authorization-server' ) ;
16011601 } ) ;
16021602
1603+ it ( 'uses base URL (with root path) as authorization server when protected-resource-metadata discovery fails' , async ( ) => {
1604+ // Setup: First call to protected resource metadata fails (404)
1605+ // When no authorization_servers are found in protected resource metadata,
1606+ // the auth server URL should be set to the base URL with "/" path
1607+ let callCount = 0 ;
1608+ mockFetch . mockImplementation ( url => {
1609+ callCount ++ ;
1610+
1611+ const urlString = url . toString ( ) ;
1612+
1613+ if ( urlString . includes ( '/.well-known/oauth-protected-resource' ) ) {
1614+ // Protected resource metadata discovery attempts (both path-aware and root) fail with 404
1615+ return Promise . resolve ( {
1616+ ok : false ,
1617+ status : 404
1618+ } ) ;
1619+ } else if ( urlString === 'https://resource.example.com/.well-known/oauth-authorization-server' ) {
1620+ // Should fetch from base URL with root path, not the full serverUrl path
1621+ return Promise . resolve ( {
1622+ ok : true ,
1623+ status : 200 ,
1624+ json : async ( ) => ( {
1625+ issuer : 'https://resource.example.com/' ,
1626+ authorization_endpoint : 'https://resource.example.com/authorize' ,
1627+ token_endpoint : 'https://resource.example.com/token' ,
1628+ registration_endpoint : 'https://resource.example.com/register' ,
1629+ response_types_supported : [ 'code' ] ,
1630+ code_challenge_methods_supported : [ 'S256' ]
1631+ } )
1632+ } ) ;
1633+ } else if ( urlString . includes ( '/register' ) ) {
1634+ // Client registration succeeds
1635+ return Promise . resolve ( {
1636+ ok : true ,
1637+ status : 200 ,
1638+ json : async ( ) => ( {
1639+ client_id : 'test-client-id' ,
1640+ client_secret : 'test-client-secret' ,
1641+ client_id_issued_at : 1612137600 ,
1642+ client_secret_expires_at : 1612224000 ,
1643+ redirect_uris : [ 'http://localhost:3000/callback' ] ,
1644+ client_name : 'Test Client'
1645+ } )
1646+ } ) ;
1647+ }
1648+
1649+ return Promise . reject ( new Error ( `Unexpected fetch call #${ callCount } : ${ urlString } ` ) ) ;
1650+ } ) ;
1651+
1652+ // Mock provider methods
1653+ ( mockProvider . clientInformation as jest . Mock ) . mockResolvedValue ( undefined ) ;
1654+ ( mockProvider . tokens as jest . Mock ) . mockResolvedValue ( undefined ) ;
1655+ mockProvider . saveClientInformation = jest . fn ( ) ;
1656+
1657+ // Call the auth function with a server URL that has a path
1658+ const result = await auth ( mockProvider , {
1659+ serverUrl : 'https://resource.example.com/path/to/server'
1660+ } ) ;
1661+
1662+ // Verify the result
1663+ expect ( result ) . toBe ( 'REDIRECT' ) ;
1664+
1665+ // Verify that the oauth-authorization-server call uses the base URL
1666+ // This proves the fix: using new URL("/", serverUrl) instead of serverUrl
1667+ const authServerCall = mockFetch . mock . calls . find ( call =>
1668+ call [ 0 ] . toString ( ) . includes ( '/.well-known/oauth-authorization-server' )
1669+ ) ;
1670+ expect ( authServerCall ) . toBeDefined ( ) ;
1671+ expect ( authServerCall [ 0 ] . toString ( ) ) . toBe ( 'https://resource.example.com/.well-known/oauth-authorization-server' ) ;
1672+ } ) ;
1673+
16031674 it ( 'passes resource parameter through authorization flow' , async ( ) => {
16041675 // Mock successful metadata discovery - need to include protected resource metadata
16051676 mockFetch . mockImplementation ( url => {
0 commit comments