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