55 OAuthClientIdLoopback ,
66 OAuthClientMetadata ,
77 OAuthClientMetadataInput ,
8- isLoopbackHost ,
8+ isLocalHostname ,
99 isOAuthClientIdDiscoverable ,
1010 isOAuthClientIdLoopback ,
1111 oauthClientMetadataSchema ,
@@ -17,7 +17,6 @@ import {
1717 fetchJsonZodProcessor ,
1818 fetchOkProcessor ,
1919} from '@atproto-labs/fetch'
20- import { isLocalHostname } from '@atproto-labs/fetch-node'
2120import { pipe } from '@atproto-labs/pipe'
2221import {
2322 CachedGetter ,
@@ -232,6 +231,10 @@ export class ClientManager {
232231 clientId : ClientId ,
233232 metadata : OAuthClientMetadata ,
234233 ) : OAuthClientMetadata {
234+ // @TODO This method should only check for rules that are specific to this
235+ // implementation or the ATPROTO specification. All generic validation rules
236+ // should be moved to the @atproto /oauth-types package.
237+
235238 if ( metadata . jwks && metadata . jwks_uri ) {
236239 throw new InvalidClientMetadataError (
237240 'jwks_uri and jwks are mutually exclusive' ,
@@ -604,56 +607,12 @@ export class ClientManager {
604607 }
605608
606609 case isPrivateUseUriScheme ( url ) : {
607- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
608- //
609- // > When choosing a URI scheme to associate with the app, apps MUST
610- // > use a URI scheme based on a domain name under their control,
611- // > expressed in reverse order, as recommended by Section 3.8 of
612- // > [RFC7595] for private-use URI schemes.
613-
614610 if ( metadata . application_type !== 'native' ) {
615611 throw new InvalidRedirectUriError (
616612 `Private-Use URI Scheme redirect URI are only allowed for native apps` ,
617613 )
618614 }
619615
620- // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
621- //
622- // > In addition to the collision-resistant properties, requiring a
623- // > URI scheme based on a domain name that is under the control of
624- // > the app can help to prove ownership in the event of a dispute
625- // > where two apps claim the same private-use URI scheme (where one
626- // > app is acting maliciously).
627- //
628- // We can't check for ownership here (as there is no concept of
629- // proven ownership in the generic client validation), but we can
630- // check that the domain is a valid domain name.
631-
632- const urlDomain = reverseDomain ( url . protocol . slice ( 0 , - 1 ) )
633-
634- if ( isLocalHostname ( urlDomain ) ) {
635- throw new InvalidRedirectUriError (
636- `Private-use URI Scheme redirect URI must not be a local hostname` ,
637- )
638- }
639-
640- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
641- //
642- // > Following the requirements of Section 3.2 of [RFC3986], as there
643- // > is no naming authority for private-use URI scheme redirects, only
644- // > a single slash ("/") appears after the scheme component.
645- if (
646- url . href . startsWith ( `${ url . protocol } //` ) ||
647- url . username ||
648- url . password ||
649- url . hostname ||
650- url . port
651- ) {
652- throw new InvalidRedirectUriError (
653- `Private-Use URI Scheme must be in the form ${ url . protocol } /<path>` ,
654- )
655- }
656-
657616 break
658617 }
659618
@@ -700,22 +659,6 @@ export class ClientManager {
700659 )
701660 }
702661
703- for ( const redirectUri of metadata . redirect_uris ) {
704- const url = parseRedirectUri ( redirectUri )
705-
706- if ( url . protocol !== 'http:' ) {
707- throw new InvalidRedirectUriError (
708- `Loopback clients must use HTTP redirect URIs` ,
709- )
710- }
711-
712- if ( ! isLoopbackHost ( url . hostname ) ) {
713- throw new InvalidRedirectUriError (
714- `Loopback clients must use loopback redirect URIs` ,
715- )
716- }
717- }
718-
719662 return metadata
720663 }
721664
@@ -762,9 +705,19 @@ export class ClientManager {
762705 }
763706
764707 for ( const redirectUri of metadata . redirect_uris ) {
708+ // @NOTE at this point, all redirect URIs have already been validated by
709+ // oauthRedirectUriSchema
710+
765711 const url = parseRedirectUri ( redirectUri )
766712
767713 if ( isPrivateUseUriScheme ( url ) ) {
714+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
715+ //
716+ // > When choosing a URI scheme to associate with the app, apps MUST use
717+ // > a URI scheme based on a domain name under their control, expressed
718+ // > in reverse order, as recommended by Section 3.8 of [RFC7595] for
719+ // > private-use URI schemes.
720+
768721 // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
769722 //
770723 // > In addition to the collision-resistant properties, requiring a
@@ -773,11 +726,14 @@ export class ClientManager {
773726 // > where two apps claim the same private-use URI scheme (where one
774727 // > app is acting maliciously).
775728
776- // https://www.ietf.org/archive/id/draft-ietf- oauth-client-id-metadata-document-00.html
729+ // https://atproto.com/specs/ oauth
777730 //
778- // Fully qualified domain name (FQDN) of the client_id, in reverse
779- // order. This could be relaxed to allow same apex domain names, or
780- // parent domains, but for now we require an exact match.
731+ // > Any custom scheme must match the client_id hostname in
732+ // > reverse-domain order. The URI scheme must be followed by a single
733+ // > colon (:) then a single forward slash (/) and then a URI path
734+ // > component. For example, an app with client_id
735+ // > https://app.example.com/client-metadata.json could have a
736+ // > redirect_uri of com.example.app:/callback.
781737 const protocol = `${ reverseDomain ( clientIdUrl . hostname ) } :`
782738 if ( url . protocol !== protocol ) {
783739 throw new InvalidRedirectUriError (
0 commit comments