@@ -18,7 +18,8 @@ import {
1818 refreshAuthorization ,
1919 registerClient ,
2020 selectClientAuthMethod ,
21- startAuthorization
21+ startAuthorization ,
22+ validateClientMetadataUrl
2223} from '../../src/client/auth.js' ;
2324import { createPrivateKeyJwtAuth } from '../../src/client/authExtensions.js' ;
2425
@@ -3833,6 +3834,80 @@ describe('OAuth Authorization', () => {
38333834 } ) ;
38343835 } ) ;
38353836
3837+ describe ( 'validateClientMetadataUrl' , ( ) => {
3838+ it ( 'passes for valid HTTPS URL with path' , ( ) => {
3839+ expect ( ( ) => validateClientMetadataUrl ( 'https://client.example.com/.well-known/oauth-client' ) ) . not . toThrow ( ) ;
3840+ } ) ;
3841+
3842+ it ( 'passes for valid HTTPS URL with multi-segment path' , ( ) => {
3843+ expect ( ( ) => validateClientMetadataUrl ( 'https://example.com/clients/metadata.json' ) ) . not . toThrow ( ) ;
3844+ } ) ;
3845+
3846+ it ( 'throws OAuthError for HTTP URL' , ( ) => {
3847+ expect ( ( ) => validateClientMetadataUrl ( 'http://client.example.com/.well-known/oauth-client' ) ) . toThrow ( OAuthError ) ;
3848+ try {
3849+ validateClientMetadataUrl ( 'http://client.example.com/.well-known/oauth-client' ) ;
3850+ } catch ( error ) {
3851+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3852+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3853+ expect ( ( error as OAuthError ) . message ) . toContain ( 'http://client.example.com/.well-known/oauth-client' ) ;
3854+ }
3855+ } ) ;
3856+
3857+ it ( 'throws OAuthError for non-URL string' , ( ) => {
3858+ expect ( ( ) => validateClientMetadataUrl ( 'not-a-url' ) ) . toThrow ( OAuthError ) ;
3859+ try {
3860+ validateClientMetadataUrl ( 'not-a-url' ) ;
3861+ } catch ( error ) {
3862+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3863+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3864+ expect ( ( error as OAuthError ) . message ) . toContain ( 'not-a-url' ) ;
3865+ }
3866+ } ) ;
3867+
3868+ it ( 'passes silently for empty string' , ( ) => {
3869+ expect ( ( ) => validateClientMetadataUrl ( '' ) ) . not . toThrow ( ) ;
3870+ } ) ;
3871+
3872+ it ( 'throws OAuthError for root-path HTTPS URL with trailing slash' , ( ) => {
3873+ expect ( ( ) => validateClientMetadataUrl ( 'https://client.example.com/' ) ) . toThrow ( OAuthError ) ;
3874+ try {
3875+ validateClientMetadataUrl ( 'https://client.example.com/' ) ;
3876+ } catch ( error ) {
3877+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3878+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3879+ expect ( ( error as OAuthError ) . message ) . toContain ( 'https://client.example.com/' ) ;
3880+ }
3881+ } ) ;
3882+
3883+ it ( 'throws OAuthError for root-path HTTPS URL without trailing slash' , ( ) => {
3884+ expect ( ( ) => validateClientMetadataUrl ( 'https://client.example.com' ) ) . toThrow ( OAuthError ) ;
3885+ try {
3886+ validateClientMetadataUrl ( 'https://client.example.com' ) ;
3887+ } catch ( error ) {
3888+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3889+ expect ( ( error as OAuthError ) . code ) . toBe ( OAuthErrorCode . InvalidClientMetadata ) ;
3890+ expect ( ( error as OAuthError ) . message ) . toContain ( 'https://client.example.com' ) ;
3891+ }
3892+ } ) ;
3893+
3894+ it ( 'passes silently for undefined' , ( ) => {
3895+ expect ( ( ) => validateClientMetadataUrl ( undefined ) ) . not . toThrow ( ) ;
3896+ } ) ;
3897+
3898+ it ( 'error message matches expected format' , ( ) => {
3899+ expect ( ( ) => validateClientMetadataUrl ( 'http://example.com/path' ) ) . toThrow ( OAuthError ) ;
3900+ try {
3901+ validateClientMetadataUrl ( 'http://example.com/path' ) ;
3902+ } catch ( error ) {
3903+ expect ( error ) . toBeInstanceOf ( OAuthError ) ;
3904+ expect ( ( error as OAuthError ) . message ) . toBe (
3905+ 'clientMetadataUrl must be a valid HTTPS URL with a non-root pathname, got: http://example.com/path'
3906+ ) ;
3907+ }
3908+ } ) ;
3909+ } ) ;
3910+
38363911 describe ( 'determineScope' , ( ) => {
38373912 const baseClientMetadata = {
38383913 redirect_uris : [ 'http://localhost:3000/callback' ] ,
0 commit comments