@atproto/oauth-types 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/atproto-loopback-client-id.d.ts.map +1 -1
  3. package/dist/atproto-loopback-client-id.js +1 -1
  4. package/dist/atproto-loopback-client-id.js.map +1 -1
  5. package/dist/oauth-authorization-server-metadata.js +2 -2
  6. package/dist/oauth-authorization-server-metadata.js.map +1 -1
  7. package/dist/oauth-client-id-discoverable.d.ts +1 -1
  8. package/dist/oauth-client-id-discoverable.js +1 -1
  9. package/dist/oauth-client-id-discoverable.js.map +1 -1
  10. package/dist/oauth-client-id-loopback.js +1 -1
  11. package/dist/oauth-client-id-loopback.js.map +1 -1
  12. package/dist/oauth-protected-resource-metadata.d.ts +1 -1
  13. package/dist/oauth-protected-resource-metadata.js +1 -1
  14. package/dist/oauth-protected-resource-metadata.js.map +1 -1
  15. package/dist/oauth-redirect-uri.d.ts +18 -6
  16. package/dist/oauth-redirect-uri.d.ts.map +1 -1
  17. package/dist/oauth-redirect-uri.js +18 -19
  18. package/dist/oauth-redirect-uri.js.map +1 -1
  19. package/dist/uri.d.ts.map +1 -1
  20. package/dist/uri.js +44 -17
  21. package/dist/uri.js.map +1 -1
  22. package/dist/util.d.ts +2 -1
  23. package/dist/util.d.ts.map +1 -1
  24. package/dist/util.js +25 -5
  25. package/dist/util.js.map +1 -1
  26. package/package.json +1 -1
  27. package/src/atproto-loopback-client-id.ts +5 -2
  28. package/src/oauth-authorization-server-metadata.ts +2 -2
  29. package/src/oauth-client-id-discoverable.ts +1 -1
  30. package/src/oauth-client-id-loopback.ts +2 -2
  31. package/src/oauth-protected-resource-metadata.ts +1 -1
  32. package/src/oauth-redirect-uri.ts +20 -26
  33. package/src/uri.ts +54 -18
  34. package/src/util.ts +25 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @atproto/oauth-types
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#4289](https://github.com/bluesky-social/atproto/pull/4289) [`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove exported `oauthHttpsRedirectURISchema` and `oauthPrivateUseRedirectURISchema` in favor of `oauthRedirectUriSchema` and `oauthLoopbackClientRedirectUriSchema`, which provide semantically meaningful groupings of redirect URIs based on OAuth client types.
8
+
9
+ `oauthHttpsRedirectURISchema` can still be accessed using `httpsUriSchema`.
10
+ `oauthPrivateUseRedirectURISchema` can still be accessed using `privateUseUriSchema`.
11
+
12
+ - [#4289](https://github.com/bluesky-social/atproto/pull/4289) [`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove unused `isLoopbackUrl` utility
13
+
14
+ ### Patch Changes
15
+
16
+ - [#4289](https://github.com/bluesky-social/atproto/pull/4289) [`8ff5ec4ca`](https://github.com/bluesky-social/atproto/commit/8ff5ec4caa9a1f5c1e453a416ba2af22d1ee4f58) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Enforce stronger validation of `privateUseUriSchema`
17
+
3
18
  ## 0.4.2
4
19
 
5
20
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"atproto-loopback-client-id.d.ts","sourceRoot":"","sources":["../src/atproto-loopback-client-id.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAIlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAEL,qBAAqB,EAEtB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,wBAAwB,EAEzB,MAAM,yBAAyB,CAAA;AAGhC,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,CAAC,EAAE,2BAA2B,GACnC,qBAAqB,CA4BvB;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,KAAK,EAAE,iBAAiB,CAAA;IACxB,aAAa,EAAE,CAAC,wBAAwB,EAAE,GAAG,wBAAwB,EAAE,CAAC,CAAA;CACzE,CAAA;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,GACf,6BAA6B,CAY/B"}
1
+ {"version":3,"file":"atproto-loopback-client-id.d.ts","sourceRoot":"","sources":["../src/atproto-loopback-client-id.ts"],"names":[],"mappings":"AACA,OAAO,EACL,iBAAiB,EAIlB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EAEL,qBAAqB,EAEtB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EACL,wBAAwB,EAEzB,MAAM,yBAAyB,CAAA;AAGhC,MAAM,MAAM,2BAA2B,GAAG;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;CACjC,CAAA;AAED,wBAAgB,4BAA4B,CAC1C,MAAM,CAAC,EAAE,2BAA2B,GACnC,qBAAqB,CA+BvB;AAED,MAAM,MAAM,6BAA6B,GAAG;IAC1C,KAAK,EAAE,iBAAiB,CAAA;IACxB,aAAa,EAAE,CAAC,wBAAwB,EAAE,GAAG,wBAAwB,EAAE,CAAC,CAAA;CACzE,CAAA;AAED,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,GACf,6BAA6B,CAY/B"}
@@ -21,7 +21,7 @@ function buildAtprotoLoopbackClientId(config) {
21
21
  throw new TypeError(`Unexpected empty "redirect_uris" config`);
22
22
  }
23
23
  for (const uri of redirectUris) {
24
- params.append('redirect_uri', oauth_redirect_uri_js_1.oauthLoopbackRedirectURISchema.parse(uri));
24
+ params.append('redirect_uri', oauth_redirect_uri_js_1.oauthLoopbackClientRedirectUriSchema.parse(uri));
25
25
  }
26
26
  }
27
27
  if (params.size) {
@@ -1 +1 @@
1
- {"version":3,"file":"atproto-loopback-client-id.js","sourceRoot":"","sources":["../src/atproto-loopback-client-id.ts"],"names":[],"mappings":";;AAuBA,oEA8BC;AAOD,oEAcC;AA1ED,yGAAkG;AAClG,qEAKiC;AACjC,+EAIsC;AACtC,mEAGgC;AAChC,uCAAoD;AAOpD,SAAgB,4BAA4B,CAC1C,MAAoC;IAEpC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;QAEpC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAA;QACxB,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,oDAA2B,EAAE,CAAC;YAC3D,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAA,4CAAmB,EAAC,KAAK,CAAC,CAAC,CAAA;QACjD,CAAC;QAED,MAAM,YAAY,GAAG,IAAA,iBAAO,EAAC,MAAM,CAAC,aAAa,CAAC,CAAA;QAClD,IACE,YAAY;YACZ,CAAC,IAAA,yBAAe,EAAC,YAAY,EAAE,gFAAqC,CAAC,EACrE,CAAC;YACD,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;gBACzB,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAA;YAChE,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAC/B,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,sDAA8B,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAA;YAC1E,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO,GAAG,uDAAyB,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;QAC5D,CAAC;IACH,CAAC;IAED,OAAO,uDAAyB,CAAA;AAClC,CAAC;AAOD,SAAgB,4BAA4B,CAC1C,QAAgB;IAEhB,MAAM,EAAE,KAAK,GAAG,oDAA2B,EAAE,aAAa,EAAE,GAC1D,IAAA,wDAA0B,EAAC,QAAQ,CAAC,CAAA;IACtC,IAAI,CAAC,IAAA,4CAAmB,EAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,wDAAwD,CACzD,CAAA;IACH,CAAC;IACD,OAAO;QACL,KAAK;QACL,aAAa,EAAE,aAAa,IAAI,CAAC,GAAG,gFAAqC,CAAC;KAC3E,CAAA;AACH,CAAC","sourcesContent":["import { DEFAULT_LOOPBACK_CLIENT_REDIRECT_URIS } from './atproto-loopback-client-redirect-uris.js'\nimport {\n AtprotoOAuthScope,\n DEFAULT_ATPROTO_OAUTH_SCOPE,\n asAtprotoOAuthScope,\n isAtprotoOAuthScope,\n} from './atproto-oauth-scope.js'\nimport {\n LOOPBACK_CLIENT_ID_ORIGIN,\n OAuthClientIdLoopback,\n parseOAuthLoopbackClientId,\n} from './oauth-client-id-loopback.js'\nimport {\n OAuthLoopbackRedirectURI,\n oauthLoopbackRedirectURISchema,\n} from './oauth-redirect-uri.js'\nimport { arrayEquivalent, asArray } from './util.js'\n\nexport type OAuthLoopbackClientIdConfig = {\n scope?: string\n redirect_uris?: Iterable<string>\n}\n\nexport function buildAtprotoLoopbackClientId(\n config?: OAuthLoopbackClientIdConfig,\n): OAuthClientIdLoopback {\n if (config) {\n const params = new URLSearchParams()\n\n const { scope } = config\n if (scope != null && scope !== DEFAULT_ATPROTO_OAUTH_SCOPE) {\n params.set('scope', asAtprotoOAuthScope(scope))\n }\n\n const redirectUris = asArray(config.redirect_uris)\n if (\n redirectUris &&\n !arrayEquivalent(redirectUris, DEFAULT_LOOPBACK_CLIENT_REDIRECT_URIS)\n ) {\n if (!redirectUris.length) {\n throw new TypeError(`Unexpected empty \"redirect_uris\" config`)\n }\n for (const uri of redirectUris) {\n params.append('redirect_uri', oauthLoopbackRedirectURISchema.parse(uri))\n }\n }\n\n if (params.size) {\n return `${LOOPBACK_CLIENT_ID_ORIGIN}?${params.toString()}`\n }\n }\n\n return LOOPBACK_CLIENT_ID_ORIGIN\n}\n\nexport type AtprotoLoopbackClientIdParams = {\n scope: AtprotoOAuthScope\n redirect_uris: [OAuthLoopbackRedirectURI, ...OAuthLoopbackRedirectURI[]]\n}\n\nexport function parseAtprotoLoopbackClientId(\n clientId: string,\n): AtprotoLoopbackClientIdParams {\n const { scope = DEFAULT_ATPROTO_OAUTH_SCOPE, redirect_uris } =\n parseOAuthLoopbackClientId(clientId)\n if (!isAtprotoOAuthScope(scope)) {\n throw new TypeError(\n 'ATProto Loopback ClientID must include \"atproto\" scope',\n )\n }\n return {\n scope,\n redirect_uris: redirect_uris ?? [...DEFAULT_LOOPBACK_CLIENT_REDIRECT_URIS],\n }\n}\n"]}
1
+ {"version":3,"file":"atproto-loopback-client-id.js","sourceRoot":"","sources":["../src/atproto-loopback-client-id.ts"],"names":[],"mappings":";;AAuBA,oEAiCC;AAOD,oEAcC;AA7ED,yGAAkG;AAClG,qEAKiC;AACjC,+EAIsC;AACtC,mEAGgC;AAChC,uCAAoD;AAOpD,SAAgB,4BAA4B,CAC1C,MAAoC;IAEpC,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAA;QAEpC,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAA;QACxB,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,KAAK,oDAA2B,EAAE,CAAC;YAC3D,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,IAAA,4CAAmB,EAAC,KAAK,CAAC,CAAC,CAAA;QACjD,CAAC;QAED,MAAM,YAAY,GAAG,IAAA,iBAAO,EAAC,MAAM,CAAC,aAAa,CAAC,CAAA;QAClD,IACE,YAAY;YACZ,CAAC,IAAA,yBAAe,EAAC,YAAY,EAAE,gFAAqC,CAAC,EACrE,CAAC;YACD,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,CAAC;gBACzB,MAAM,IAAI,SAAS,CAAC,yCAAyC,CAAC,CAAA;YAChE,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;gBAC/B,MAAM,CAAC,MAAM,CACX,cAAc,EACd,4DAAoC,CAAC,KAAK,CAAC,GAAG,CAAC,CAChD,CAAA;YACH,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,OAAO,GAAG,uDAAyB,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAA;QAC5D,CAAC;IACH,CAAC;IAED,OAAO,uDAAyB,CAAA;AAClC,CAAC;AAOD,SAAgB,4BAA4B,CAC1C,QAAgB;IAEhB,MAAM,EAAE,KAAK,GAAG,oDAA2B,EAAE,aAAa,EAAE,GAC1D,IAAA,wDAA0B,EAAC,QAAQ,CAAC,CAAA;IACtC,IAAI,CAAC,IAAA,4CAAmB,EAAC,KAAK,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,SAAS,CACjB,wDAAwD,CACzD,CAAA;IACH,CAAC;IACD,OAAO;QACL,KAAK;QACL,aAAa,EAAE,aAAa,IAAI,CAAC,GAAG,gFAAqC,CAAC;KAC3E,CAAA;AACH,CAAC","sourcesContent":["import { DEFAULT_LOOPBACK_CLIENT_REDIRECT_URIS } from './atproto-loopback-client-redirect-uris.js'\nimport {\n AtprotoOAuthScope,\n DEFAULT_ATPROTO_OAUTH_SCOPE,\n asAtprotoOAuthScope,\n isAtprotoOAuthScope,\n} from './atproto-oauth-scope.js'\nimport {\n LOOPBACK_CLIENT_ID_ORIGIN,\n OAuthClientIdLoopback,\n parseOAuthLoopbackClientId,\n} from './oauth-client-id-loopback.js'\nimport {\n OAuthLoopbackRedirectURI,\n oauthLoopbackClientRedirectUriSchema,\n} from './oauth-redirect-uri.js'\nimport { arrayEquivalent, asArray } from './util.js'\n\nexport type OAuthLoopbackClientIdConfig = {\n scope?: string\n redirect_uris?: Iterable<string>\n}\n\nexport function buildAtprotoLoopbackClientId(\n config?: OAuthLoopbackClientIdConfig,\n): OAuthClientIdLoopback {\n if (config) {\n const params = new URLSearchParams()\n\n const { scope } = config\n if (scope != null && scope !== DEFAULT_ATPROTO_OAUTH_SCOPE) {\n params.set('scope', asAtprotoOAuthScope(scope))\n }\n\n const redirectUris = asArray(config.redirect_uris)\n if (\n redirectUris &&\n !arrayEquivalent(redirectUris, DEFAULT_LOOPBACK_CLIENT_REDIRECT_URIS)\n ) {\n if (!redirectUris.length) {\n throw new TypeError(`Unexpected empty \"redirect_uris\" config`)\n }\n for (const uri of redirectUris) {\n params.append(\n 'redirect_uri',\n oauthLoopbackClientRedirectUriSchema.parse(uri),\n )\n }\n }\n\n if (params.size) {\n return `${LOOPBACK_CLIENT_ID_ORIGIN}?${params.toString()}`\n }\n }\n\n return LOOPBACK_CLIENT_ID_ORIGIN\n}\n\nexport type AtprotoLoopbackClientIdParams = {\n scope: AtprotoOAuthScope\n redirect_uris: [OAuthLoopbackRedirectURI, ...OAuthLoopbackRedirectURI[]]\n}\n\nexport function parseAtprotoLoopbackClientId(\n clientId: string,\n): AtprotoLoopbackClientIdParams {\n const { scope = DEFAULT_ATPROTO_OAUTH_SCOPE, redirect_uris } =\n parseOAuthLoopbackClientId(clientId)\n if (!isAtprotoOAuthScope(scope)) {\n throw new TypeError(\n 'ATProto Loopback ClientID must include \"atproto\" scope',\n )\n }\n return {\n scope,\n redirect_uris: redirect_uris ?? [...DEFAULT_LOOPBACK_CLIENT_REDIRECT_URIS],\n }\n}\n"]}
@@ -60,9 +60,9 @@ exports.oauthAuthorizationServerMetadataSchema = zod_1.z.object({
60
60
  registration_endpoint: uri_js_1.webUriSchema.optional(),
61
61
  // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1
62
62
  dpop_signing_alg_values_supported: zod_1.z.array(zod_1.z.string()).optional(),
63
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
63
+ // https://www.rfc-editor.org/rfc/rfc9728.html#section-4
64
64
  protected_resources: zod_1.z.array(uri_js_1.webUriSchema).optional(),
65
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
65
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
66
66
  client_id_metadata_document_supported: zod_1.z.boolean().optional(),
67
67
  });
68
68
  exports.oauthAuthorizationServerMetadataValidator = exports.oauthAuthorizationServerMetadataSchema
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-authorization-server-metadata.js","sourceRoot":"","sources":["../src/oauth-authorization-server-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\nimport { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'\nimport { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'\nimport { webUriSchema } from './uri.js'\n\n/**\n * @see {@link https://datatracker.ietf.org/doc/html/rfc8414}\n * @note we do not enforce https: scheme in URIs to support development\n * environments. Make sure to validate the URIs before using it in a production\n * environment.\n */\nexport const oauthAuthorizationServerMetadataSchema = z.object({\n issuer: oauthIssuerIdentifierSchema,\n\n claims_supported: z.array(z.string()).optional(),\n claims_locales_supported: z.array(z.string()).optional(),\n claims_parameter_supported: z.boolean().optional(),\n request_parameter_supported: z.boolean().optional(),\n request_uri_parameter_supported: z.boolean().optional(),\n require_request_uri_registration: z.boolean().optional(),\n scopes_supported: z.array(z.string()).optional(),\n subject_types_supported: z.array(z.string()).optional(),\n response_types_supported: z.array(z.string()).optional(),\n response_modes_supported: z.array(z.string()).optional(),\n grant_types_supported: z.array(z.string()).optional(),\n code_challenge_methods_supported: z\n .array(oauthCodeChallengeMethodSchema)\n .min(1)\n .optional(),\n ui_locales_supported: z.array(z.string()).optional(),\n id_token_signing_alg_values_supported: z.array(z.string()).optional(),\n display_values_supported: z.array(z.string()).optional(),\n request_object_signing_alg_values_supported: z.array(z.string()).optional(),\n authorization_response_iss_parameter_supported: z.boolean().optional(),\n authorization_details_types_supported: z.array(z.string()).optional(),\n request_object_encryption_alg_values_supported: z\n .array(z.string())\n .optional(),\n request_object_encryption_enc_values_supported: z\n .array(z.string())\n .optional(),\n\n jwks_uri: webUriSchema.optional(),\n\n authorization_endpoint: webUriSchema, // .optional(),\n\n token_endpoint: webUriSchema, // .optional(),\n // https://www.rfc-editor.org/rfc/rfc8414.html#section-2\n token_endpoint_auth_methods_supported: z\n .array(z.string())\n // > If omitted, the default is \"client_secret_basic\" [...].\n .default(['client_secret_basic']),\n token_endpoint_auth_signing_alg_values_supported: z\n .array(z.string())\n .optional(),\n\n revocation_endpoint: webUriSchema.optional(),\n introspection_endpoint: webUriSchema.optional(),\n pushed_authorization_request_endpoint: webUriSchema.optional(),\n\n require_pushed_authorization_requests: z.boolean().optional(),\n\n userinfo_endpoint: webUriSchema.optional(),\n end_session_endpoint: webUriSchema.optional(),\n registration_endpoint: webUriSchema.optional(),\n\n // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1\n dpop_signing_alg_values_supported: z.array(z.string()).optional(),\n\n // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4\n protected_resources: z.array(webUriSchema).optional(),\n\n // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html\n client_id_metadata_document_supported: z.boolean().optional(),\n})\n\nexport type OAuthAuthorizationServerMetadata = z.infer<\n typeof oauthAuthorizationServerMetadataSchema\n>\n\nexport const oauthAuthorizationServerMetadataValidator =\n oauthAuthorizationServerMetadataSchema\n .superRefine((data, ctx) => {\n if (\n data.require_pushed_authorization_requests &&\n !data.pushed_authorization_request_endpoint\n ) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n '\"pushed_authorization_request_endpoint\" required when \"require_pushed_authorization_requests\" is true',\n })\n }\n })\n .superRefine((data, ctx) => {\n if (data.response_types_supported) {\n if (!data.response_types_supported.includes('code')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Response type \"code\" is required',\n })\n }\n }\n })\n .superRefine((data, ctx) => {\n if (\n data.token_endpoint_auth_signing_alg_values_supported?.includes('none')\n ) {\n // https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3\n // > The value `none` MUST NOT be used.\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Client authentication method \"none\" is not allowed',\n })\n }\n })\n"]}
1
+ {"version":3,"file":"oauth-authorization-server-metadata.js","sourceRoot":"","sources":["../src/oauth-authorization-server-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\nimport { oauthCodeChallengeMethodSchema } from './oauth-code-challenge-method.js'\nimport { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'\nimport { webUriSchema } from './uri.js'\n\n/**\n * @see {@link https://datatracker.ietf.org/doc/html/rfc8414}\n * @note we do not enforce https: scheme in URIs to support development\n * environments. Make sure to validate the URIs before using it in a production\n * environment.\n */\nexport const oauthAuthorizationServerMetadataSchema = z.object({\n issuer: oauthIssuerIdentifierSchema,\n\n claims_supported: z.array(z.string()).optional(),\n claims_locales_supported: z.array(z.string()).optional(),\n claims_parameter_supported: z.boolean().optional(),\n request_parameter_supported: z.boolean().optional(),\n request_uri_parameter_supported: z.boolean().optional(),\n require_request_uri_registration: z.boolean().optional(),\n scopes_supported: z.array(z.string()).optional(),\n subject_types_supported: z.array(z.string()).optional(),\n response_types_supported: z.array(z.string()).optional(),\n response_modes_supported: z.array(z.string()).optional(),\n grant_types_supported: z.array(z.string()).optional(),\n code_challenge_methods_supported: z\n .array(oauthCodeChallengeMethodSchema)\n .min(1)\n .optional(),\n ui_locales_supported: z.array(z.string()).optional(),\n id_token_signing_alg_values_supported: z.array(z.string()).optional(),\n display_values_supported: z.array(z.string()).optional(),\n request_object_signing_alg_values_supported: z.array(z.string()).optional(),\n authorization_response_iss_parameter_supported: z.boolean().optional(),\n authorization_details_types_supported: z.array(z.string()).optional(),\n request_object_encryption_alg_values_supported: z\n .array(z.string())\n .optional(),\n request_object_encryption_enc_values_supported: z\n .array(z.string())\n .optional(),\n\n jwks_uri: webUriSchema.optional(),\n\n authorization_endpoint: webUriSchema, // .optional(),\n\n token_endpoint: webUriSchema, // .optional(),\n // https://www.rfc-editor.org/rfc/rfc8414.html#section-2\n token_endpoint_auth_methods_supported: z\n .array(z.string())\n // > If omitted, the default is \"client_secret_basic\" [...].\n .default(['client_secret_basic']),\n token_endpoint_auth_signing_alg_values_supported: z\n .array(z.string())\n .optional(),\n\n revocation_endpoint: webUriSchema.optional(),\n introspection_endpoint: webUriSchema.optional(),\n pushed_authorization_request_endpoint: webUriSchema.optional(),\n\n require_pushed_authorization_requests: z.boolean().optional(),\n\n userinfo_endpoint: webUriSchema.optional(),\n end_session_endpoint: webUriSchema.optional(),\n registration_endpoint: webUriSchema.optional(),\n\n // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1\n dpop_signing_alg_values_supported: z.array(z.string()).optional(),\n\n // https://www.rfc-editor.org/rfc/rfc9728.html#section-4\n protected_resources: z.array(webUriSchema).optional(),\n\n // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html\n client_id_metadata_document_supported: z.boolean().optional(),\n})\n\nexport type OAuthAuthorizationServerMetadata = z.infer<\n typeof oauthAuthorizationServerMetadataSchema\n>\n\nexport const oauthAuthorizationServerMetadataValidator =\n oauthAuthorizationServerMetadataSchema\n .superRefine((data, ctx) => {\n if (\n data.require_pushed_authorization_requests &&\n !data.pushed_authorization_request_endpoint\n ) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n '\"pushed_authorization_request_endpoint\" required when \"require_pushed_authorization_requests\" is true',\n })\n }\n })\n .superRefine((data, ctx) => {\n if (data.response_types_supported) {\n if (!data.response_types_supported.includes('code')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Response type \"code\" is required',\n })\n }\n }\n })\n .superRefine((data, ctx) => {\n if (\n data.token_endpoint_auth_signing_alg_values_supported?.includes('none')\n ) {\n // https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.3\n // > The value `none` MUST NOT be used.\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'Client authentication method \"none\" is not allowed',\n })\n }\n })\n"]}
@@ -1,6 +1,6 @@
1
1
  import { TypeOf, z } from 'zod';
2
2
  /**
3
- * @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}
3
+ * @see {@link https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html}
4
4
  */
5
5
  export declare const oauthClientIdDiscoverableSchema: z.ZodEffects<z.ZodIntersection<z.ZodString, z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `https://${string}`, string>>, `https://${string}/${string}`, string>;
6
6
  export type OAuthClientIdDiscoverable = TypeOf<typeof oauthClientIdDiscoverableSchema>;
@@ -10,7 +10,7 @@ const oauth_client_id_js_1 = require("./oauth-client-id.js");
10
10
  const uri_js_1 = require("./uri.js");
11
11
  const util_js_1 = require("./util.js");
12
12
  /**
13
- * @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}
13
+ * @see {@link https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html}
14
14
  */
15
15
  exports.oauthClientIdDiscoverableSchema = zod_1.z
16
16
  .intersection(oauth_client_id_js_1.oauthClientIdSchema, uri_js_1.httpsUriSchema)
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-client-id-discoverable.js","sourceRoot":"","sources":["../src/oauth-client-id-discoverable.ts"],"names":[],"mappings":"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","sourcesContent":["import { TypeOf, z } from 'zod'\nimport { oauthClientIdSchema } from './oauth-client-id.js'\nimport { httpsUriSchema } from './uri.js'\nimport { extractUrlPath, isHostnameIP } from './util.js'\n\n/**\n * @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}\n */\nexport const oauthClientIdDiscoverableSchema = z\n .intersection(oauthClientIdSchema, httpsUriSchema)\n .superRefine((value, ctx): value is `https://${string}/${string}` => {\n const url = new URL(value)\n\n if (url.username || url.password) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain credentials',\n })\n return false\n }\n\n if (url.hash) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain a fragment',\n })\n return false\n }\n\n if (url.pathname === '/') {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'ClientID must contain a path component (e.g. \"/client-metadata.json\")',\n })\n return false\n }\n\n if (url.pathname.endsWith('/')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID path must not end with a trailing slash',\n })\n return false\n }\n\n if (isHostnameIP(url.hostname)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID hostname must not be an IP address',\n })\n return false\n }\n\n // URL constructor normalizes the URL, so we extract the path manually to\n // avoid normalization, then compare it to the normalized path to ensure\n // that the URL does not contain path traversal or other unexpected characters\n if (extractUrlPath(value) !== url.pathname) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `ClientID must be in canonical form (\"${url.href}\", got \"${value}\")`,\n })\n return false\n }\n\n return true\n })\n\nexport type OAuthClientIdDiscoverable = TypeOf<\n typeof oauthClientIdDiscoverableSchema\n>\n\nexport function isOAuthClientIdDiscoverable(\n clientId: string,\n): clientId is OAuthClientIdDiscoverable {\n return oauthClientIdDiscoverableSchema.safeParse(clientId).success\n}\n\nexport const conventionalOAuthClientIdSchema =\n oauthClientIdDiscoverableSchema.superRefine(\n (value, ctx): value is `https://${string}/oauth-client-metadata.json` => {\n const url = new URL(value)\n\n if (url.port) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain a port',\n })\n return false\n }\n\n if (url.search) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain a query string',\n })\n return false\n }\n\n if (url.pathname !== '/oauth-client-metadata.json') {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must be \"/oauth-client-metadata.json\"',\n })\n return false\n }\n\n return true\n },\n )\n\nexport type ConventionalOAuthClientId = TypeOf<\n typeof conventionalOAuthClientIdSchema\n>\n\nexport function isConventionalOAuthClientId(\n clientId: string,\n): clientId is ConventionalOAuthClientId {\n return conventionalOAuthClientIdSchema.safeParse(clientId).success\n}\n\nexport function assertOAuthDiscoverableClientId(\n value: string,\n): asserts value is OAuthClientIdDiscoverable {\n void oauthClientIdDiscoverableSchema.parse(value)\n}\n\nexport function parseOAuthDiscoverableClientId(clientId: string): URL {\n return new URL(oauthClientIdDiscoverableSchema.parse(clientId))\n}\n"]}
1
+ {"version":3,"file":"oauth-client-id-discoverable.js","sourceRoot":"","sources":["../src/oauth-client-id-discoverable.ts"],"names":[],"mappings":"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","sourcesContent":["import { TypeOf, z } from 'zod'\nimport { oauthClientIdSchema } from './oauth-client-id.js'\nimport { httpsUriSchema } from './uri.js'\nimport { extractUrlPath, isHostnameIP } from './util.js'\n\n/**\n * @see {@link https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html}\n */\nexport const oauthClientIdDiscoverableSchema = z\n .intersection(oauthClientIdSchema, httpsUriSchema)\n .superRefine((value, ctx): value is `https://${string}/${string}` => {\n const url = new URL(value)\n\n if (url.username || url.password) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain credentials',\n })\n return false\n }\n\n if (url.hash) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain a fragment',\n })\n return false\n }\n\n if (url.pathname === '/') {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message:\n 'ClientID must contain a path component (e.g. \"/client-metadata.json\")',\n })\n return false\n }\n\n if (url.pathname.endsWith('/')) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID path must not end with a trailing slash',\n })\n return false\n }\n\n if (isHostnameIP(url.hostname)) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID hostname must not be an IP address',\n })\n return false\n }\n\n // URL constructor normalizes the URL, so we extract the path manually to\n // avoid normalization, then compare it to the normalized path to ensure\n // that the URL does not contain path traversal or other unexpected characters\n if (extractUrlPath(value) !== url.pathname) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: `ClientID must be in canonical form (\"${url.href}\", got \"${value}\")`,\n })\n return false\n }\n\n return true\n })\n\nexport type OAuthClientIdDiscoverable = TypeOf<\n typeof oauthClientIdDiscoverableSchema\n>\n\nexport function isOAuthClientIdDiscoverable(\n clientId: string,\n): clientId is OAuthClientIdDiscoverable {\n return oauthClientIdDiscoverableSchema.safeParse(clientId).success\n}\n\nexport const conventionalOAuthClientIdSchema =\n oauthClientIdDiscoverableSchema.superRefine(\n (value, ctx): value is `https://${string}/oauth-client-metadata.json` => {\n const url = new URL(value)\n\n if (url.port) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain a port',\n })\n return false\n }\n\n if (url.search) {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must not contain a query string',\n })\n return false\n }\n\n if (url.pathname !== '/oauth-client-metadata.json') {\n ctx.addIssue({\n code: z.ZodIssueCode.custom,\n message: 'ClientID must be \"/oauth-client-metadata.json\"',\n })\n return false\n }\n\n return true\n },\n )\n\nexport type ConventionalOAuthClientId = TypeOf<\n typeof conventionalOAuthClientIdSchema\n>\n\nexport function isConventionalOAuthClientId(\n clientId: string,\n): clientId is ConventionalOAuthClientId {\n return conventionalOAuthClientIdSchema.safeParse(clientId).success\n}\n\nexport function assertOAuthDiscoverableClientId(\n value: string,\n): asserts value is OAuthClientIdDiscoverable {\n void oauthClientIdDiscoverableSchema.parse(value)\n}\n\nexport function parseOAuthDiscoverableClientId(clientId: string): URL {\n return new URL(oauthClientIdDiscoverableSchema.parse(clientId))\n}\n"]}
@@ -91,7 +91,7 @@ function safeParseOAuthLoopbackClientIdQueryString(input) {
91
91
  params.scope = res.data;
92
92
  }
93
93
  else if (key === 'redirect_uri') {
94
- const res = oauth_redirect_uri_js_1.oauthLoopbackRedirectURISchema.safeParse(value);
94
+ const res = oauth_redirect_uri_js_1.oauthLoopbackClientRedirectUriSchema.safeParse(value);
95
95
  if (!res.success) {
96
96
  const reason = res.error.issues.map((i) => i.message).join(', ');
97
97
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-client-id-loopback.js","sourceRoot":"","sources":["../src/oauth-client-id-loopback.ts"],"names":[],"mappings":";;;AAkCA,kEAIC;AAED,0DAIC;AAED,0DAGC;AAED,gEAOC;AAUD,wEA2CC;AAED,8FAkDC;AAnKD,6DAA0D;AAC1D,mEAGgC;AAChC,qDAA+D;AAElD,QAAA,yBAAyB,GAAG,kBAAkB,CAAA;AAiB9C,QAAA,2BAA2B,GAAG,wCAAmB,CAAC,WAAW,CACxE,CAAC,KAAK,EAAE,GAAG,EAAkC,EAAE;IAC7C,MAAM,MAAM,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAA;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAA;AACvB,CAAC,CACF,CAAA;AAED,SAAgB,2BAA2B,CACzC,KAAa;IAEb,KAAK,0BAA0B,CAAC,KAAK,CAAC,CAAA;AACxC,CAAC;AAED,SAAgB,uBAAuB,CACrC,KAAQ;IAER,OAAO,8BAA8B,CAAC,KAAK,CAAC,CAAC,OAAO,CAAA;AACtD,CAAC;AAED,SAAgB,uBAAuB,CAAmB,KAAQ;IAChE,2BAA2B,CAAC,KAAK,CAAC,CAAA;IAClC,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAgB,0BAA0B,CACxC,KAAa;IAEb,MAAM,MAAM,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAA;IACpD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,KAAK,CAAA;IAEvC,MAAM,IAAI,SAAS,CAAC,+BAA+B,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;AACtE,CAAC;AAUD,SAAgB,8BAA8B,CAC5C,KAAa;IAEb,oEAAoE;IACpE,wBAAwB;IAExB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,iCAAyB,CAAC,EAAE,CAAC;QACjD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,0BAA0B,iCAAyB,GAAG;SAChE,CAAA;IACH,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,iCAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,yCAAyC;SACnD,CAAA;IACH,CAAC;IAED,4EAA4E;IAC5E,yDAAyD;IACzD,MAAM,cAAc,GAClB,KAAK,CAAC,MAAM,GAAG,iCAAyB,CAAC,MAAM;QAC/C,KAAK,CAAC,UAAU,CAAC,iCAAyB,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS;QACnE,CAAC,CAAC,iCAAyB,CAAC,MAAM,GAAG,CAAC;QACtC,CAAC,CAAC,iCAAyB,CAAC,MAAM,CAAA;IAEtC,2EAA2E;IAC3E,wEAAwE;IACxE,qDAAqD;IACrD,IACE,KAAK,CAAC,MAAM,KAAK,cAAc;QAC/B,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC,SAAS,EACnD,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,yCAAyC;SACnD,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAA;IACnD,OAAO,yCAAyC,CAAC,WAAW,CAAC,CAAA;AAC/D,CAAC;AAED,SAAgB,yCAAyC,CACvD,KAAsD;IAEtD,qBAAqB;IACrB,MAAM,MAAM,GAAgC,EAAE,CAAA;IAE9C,MAAM,EAAE,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACzE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9B,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;gBACtB,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,mCAAmC;iBAC7C,CAAA;YACH,CAAC;YAED,MAAM,GAAG,GAAG,iCAAgB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAChE,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,oCAAoC,MAAM,IAAI,mBAAmB,EAAE;iBAC7E,CAAA;YACH,CAAC;YAED,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CAAA;QACzB,CAAC;aAAM,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,sDAA8B,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YAC3D,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAChE,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,2CAA2C,MAAM,IAAI,mBAAmB,EAAE;iBACpF,CAAA;YACH,CAAC;YAED,IAAI,MAAM,CAAC,aAAa,IAAI,IAAI;gBAAE,MAAM,CAAC,aAAa,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;;gBAC9D,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC1C,CAAC;aAAM,CAAC;YACN,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,+BAA+B,GAAG,GAAG;aAC/C,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,MAAM;KACd,CAAA;AACH,CAAC","sourcesContent":["import { oauthClientIdSchema } from './oauth-client-id.js'\nimport {\n OAuthLoopbackRedirectURI,\n oauthLoopbackRedirectURISchema,\n} from './oauth-redirect-uri.js'\nimport { OAuthScope, oauthScopeSchema } from './oauth-scope.js'\n\nexport const LOOPBACK_CLIENT_ID_ORIGIN = 'http://localhost'\n\n// @NOTE This is not actually based on a standard, but rather a convention\n// established by Bluesky in the Atproto specs and implementation. As such, and\n// in order to respect the convention from this package, these should be\n// prefixed with \"Atproto\" instead of \"OAuth\". For legacy reasons, we keep the\n// current names, but we should rename them in a future major release, unless\n// loopback client ids have since then been standardized.\n\nexport type OAuthClientIdLoopback =\n `http://localhost${'' | `/`}${'' | `?${string}`}`\n\nexport type OAuthLoopbackClientIdParams = {\n scope?: OAuthScope\n redirect_uris?: [OAuthLoopbackRedirectURI, ...OAuthLoopbackRedirectURI[]]\n}\n\nexport const oauthClientIdLoopbackSchema = oauthClientIdSchema.superRefine(\n (input, ctx): input is OAuthClientIdLoopback => {\n const result = safeParseOAuthLoopbackClientId(input)\n if (!result.success) {\n ctx.addIssue({ code: 'custom', message: result.message })\n }\n return result.success\n },\n)\n\nexport function assertOAuthLoopbackClientId(\n input: string,\n): asserts input is OAuthClientIdLoopback {\n void parseOAuthLoopbackClientId(input)\n}\n\nexport function isOAuthClientIdLoopback<T extends string>(\n input: T,\n): input is T & OAuthClientIdLoopback {\n return safeParseOAuthLoopbackClientId(input).success\n}\n\nexport function asOAuthClientIdLoopback<T extends string>(input: T) {\n assertOAuthLoopbackClientId(input)\n return input\n}\n\nexport function parseOAuthLoopbackClientId(\n input: string,\n): OAuthLoopbackClientIdParams {\n const result = safeParseOAuthLoopbackClientId(input)\n if (result.success) return result.value\n\n throw new TypeError(`Invalid loopback client ID: ${result.message}`)\n}\n\n/**\n * Similar to Zod's {@link SafeParseReturnType} but uses a simple \"message\"\n * string instead of an \"error\" Error object.\n */\ntype LightParseReturnType<T> =\n | { success: true; value: T }\n | { success: false; message: string }\n\nexport function safeParseOAuthLoopbackClientId(\n input: string,\n): LightParseReturnType<OAuthLoopbackClientIdParams> {\n // @NOTE Not using \"new URL\" to ensure input indeed matches the type\n // OAuthClientIdLoopback\n\n if (!input.startsWith(LOOPBACK_CLIENT_ID_ORIGIN)) {\n return {\n success: false,\n message: `Value must start with \"${LOOPBACK_CLIENT_ID_ORIGIN}\"`,\n }\n }\n\n if (input.includes('#', LOOPBACK_CLIENT_ID_ORIGIN.length)) {\n return {\n success: false,\n message: 'Value must not contain a hash component',\n }\n }\n\n // Since we don't allow a path component (except for a single \"/\") the query\n // string starts after the origin (+ 1 if there is a \"/\")\n const queryStringIdx =\n input.length > LOOPBACK_CLIENT_ID_ORIGIN.length &&\n input.charCodeAt(LOOPBACK_CLIENT_ID_ORIGIN.length) === 0x2f /* '/' */\n ? LOOPBACK_CLIENT_ID_ORIGIN.length + 1\n : LOOPBACK_CLIENT_ID_ORIGIN.length\n\n // Since we determined the position of the query string based on the origin\n // length (instead of looking for a \"?\"), we need to make sure the query\n // string position (if any) indeed starts with a \"?\".\n if (\n input.length !== queryStringIdx &&\n input.charCodeAt(queryStringIdx) !== 0x3f /* '?' */\n ) {\n return {\n success: false,\n message: 'Value must not contain a path component',\n }\n }\n\n const queryString = input.slice(queryStringIdx + 1)\n return safeParseOAuthLoopbackClientIdQueryString(queryString)\n}\n\nexport function safeParseOAuthLoopbackClientIdQueryString(\n input: string | Iterable<[key: string, value: string]>,\n): LightParseReturnType<OAuthLoopbackClientIdParams> {\n // Parse query params\n const params: OAuthLoopbackClientIdParams = {}\n\n const it = typeof input === 'string' ? new URLSearchParams(input) : input\n for (const [key, value] of it) {\n if (key === 'scope') {\n if ('scope' in params) {\n return {\n success: false,\n message: 'Duplicate \"scope\" query parameter',\n }\n }\n\n const res = oauthScopeSchema.safeParse(value)\n if (!res.success) {\n const reason = res.error.issues.map((i) => i.message).join(', ')\n return {\n success: false,\n message: `Invalid \"scope\" query parameter: ${reason || 'Validation failed'}`,\n }\n }\n\n params.scope = res.data\n } else if (key === 'redirect_uri') {\n const res = oauthLoopbackRedirectURISchema.safeParse(value)\n if (!res.success) {\n const reason = res.error.issues.map((i) => i.message).join(', ')\n return {\n success: false,\n message: `Invalid \"redirect_uri\" query parameter: ${reason || 'Validation failed'}`,\n }\n }\n\n if (params.redirect_uris == null) params.redirect_uris = [res.data]\n else params.redirect_uris.push(res.data)\n } else {\n return {\n success: false,\n message: `Unexpected query parameter \"${key}\"`,\n }\n }\n }\n\n return {\n success: true,\n value: params,\n }\n}\n"]}
1
+ {"version":3,"file":"oauth-client-id-loopback.js","sourceRoot":"","sources":["../src/oauth-client-id-loopback.ts"],"names":[],"mappings":";;;AAkCA,kEAIC;AAED,0DAIC;AAED,0DAGC;AAED,gEAOC;AAUD,wEA2CC;AAED,8FAkDC;AAnKD,6DAA0D;AAC1D,mEAGgC;AAChC,qDAA+D;AAElD,QAAA,yBAAyB,GAAG,kBAAkB,CAAA;AAiB9C,QAAA,2BAA2B,GAAG,wCAAmB,CAAC,WAAW,CACxE,CAAC,KAAK,EAAE,GAAG,EAAkC,EAAE;IAC7C,MAAM,MAAM,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAA;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,GAAG,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,MAAM,CAAC,OAAO,CAAA;AACvB,CAAC,CACF,CAAA;AAED,SAAgB,2BAA2B,CACzC,KAAa;IAEb,KAAK,0BAA0B,CAAC,KAAK,CAAC,CAAA;AACxC,CAAC;AAED,SAAgB,uBAAuB,CACrC,KAAQ;IAER,OAAO,8BAA8B,CAAC,KAAK,CAAC,CAAC,OAAO,CAAA;AACtD,CAAC;AAED,SAAgB,uBAAuB,CAAmB,KAAQ;IAChE,2BAA2B,CAAC,KAAK,CAAC,CAAA;IAClC,OAAO,KAAK,CAAA;AACd,CAAC;AAED,SAAgB,0BAA0B,CACxC,KAAa;IAEb,MAAM,MAAM,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAA;IACpD,IAAI,MAAM,CAAC,OAAO;QAAE,OAAO,MAAM,CAAC,KAAK,CAAA;IAEvC,MAAM,IAAI,SAAS,CAAC,+BAA+B,MAAM,CAAC,OAAO,EAAE,CAAC,CAAA;AACtE,CAAC;AAUD,SAAgB,8BAA8B,CAC5C,KAAa;IAEb,oEAAoE;IACpE,wBAAwB;IAExB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,iCAAyB,CAAC,EAAE,CAAC;QACjD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,0BAA0B,iCAAyB,GAAG;SAChE,CAAA;IACH,CAAC;IAED,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,iCAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC1D,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,yCAAyC;SACnD,CAAA;IACH,CAAC;IAED,4EAA4E;IAC5E,yDAAyD;IACzD,MAAM,cAAc,GAClB,KAAK,CAAC,MAAM,GAAG,iCAAyB,CAAC,MAAM;QAC/C,KAAK,CAAC,UAAU,CAAC,iCAAyB,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,SAAS;QACnE,CAAC,CAAC,iCAAyB,CAAC,MAAM,GAAG,CAAC;QACtC,CAAC,CAAC,iCAAyB,CAAC,MAAM,CAAA;IAEtC,2EAA2E;IAC3E,wEAAwE;IACxE,qDAAqD;IACrD,IACE,KAAK,CAAC,MAAM,KAAK,cAAc;QAC/B,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC,SAAS,EACnD,CAAC;QACD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,yCAAyC;SACnD,CAAA;IACH,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,cAAc,GAAG,CAAC,CAAC,CAAA;IACnD,OAAO,yCAAyC,CAAC,WAAW,CAAC,CAAA;AAC/D,CAAC;AAED,SAAgB,yCAAyC,CACvD,KAAsD;IAEtD,qBAAqB;IACrB,MAAM,MAAM,GAAgC,EAAE,CAAA;IAE9C,MAAM,EAAE,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;IACzE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC9B,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,IAAI,OAAO,IAAI,MAAM,EAAE,CAAC;gBACtB,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,mCAAmC;iBAC7C,CAAA;YACH,CAAC;YAED,MAAM,GAAG,GAAG,iCAAgB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAChE,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,oCAAoC,MAAM,IAAI,mBAAmB,EAAE;iBAC7E,CAAA;YACH,CAAC;YAED,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,IAAI,CAAA;QACzB,CAAC;aAAM,IAAI,GAAG,KAAK,cAAc,EAAE,CAAC;YAClC,MAAM,GAAG,GAAG,4DAAoC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;YACjE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjB,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBAChE,OAAO;oBACL,OAAO,EAAE,KAAK;oBACd,OAAO,EAAE,2CAA2C,MAAM,IAAI,mBAAmB,EAAE;iBACpF,CAAA;YACH,CAAC;YAED,IAAI,MAAM,CAAC,aAAa,IAAI,IAAI;gBAAE,MAAM,CAAC,aAAa,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;;gBAC9D,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAC1C,CAAC;aAAM,CAAC;YACN,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,+BAA+B,GAAG,GAAG;aAC/C,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,MAAM;KACd,CAAA;AACH,CAAC","sourcesContent":["import { oauthClientIdSchema } from './oauth-client-id.js'\nimport {\n OAuthLoopbackRedirectURI,\n oauthLoopbackClientRedirectUriSchema,\n} from './oauth-redirect-uri.js'\nimport { OAuthScope, oauthScopeSchema } from './oauth-scope.js'\n\nexport const LOOPBACK_CLIENT_ID_ORIGIN = 'http://localhost'\n\n// @NOTE This is not actually based on a standard, but rather a convention\n// established by Bluesky in the Atproto specs and implementation. As such, and\n// in order to respect the convention from this package, these should be\n// prefixed with \"Atproto\" instead of \"OAuth\". For legacy reasons, we keep the\n// current names, but we should rename them in a future major release, unless\n// loopback client ids have since then been standardized.\n\nexport type OAuthClientIdLoopback =\n `http://localhost${'' | `/`}${'' | `?${string}`}`\n\nexport type OAuthLoopbackClientIdParams = {\n scope?: OAuthScope\n redirect_uris?: [OAuthLoopbackRedirectURI, ...OAuthLoopbackRedirectURI[]]\n}\n\nexport const oauthClientIdLoopbackSchema = oauthClientIdSchema.superRefine(\n (input, ctx): input is OAuthClientIdLoopback => {\n const result = safeParseOAuthLoopbackClientId(input)\n if (!result.success) {\n ctx.addIssue({ code: 'custom', message: result.message })\n }\n return result.success\n },\n)\n\nexport function assertOAuthLoopbackClientId(\n input: string,\n): asserts input is OAuthClientIdLoopback {\n void parseOAuthLoopbackClientId(input)\n}\n\nexport function isOAuthClientIdLoopback<T extends string>(\n input: T,\n): input is T & OAuthClientIdLoopback {\n return safeParseOAuthLoopbackClientId(input).success\n}\n\nexport function asOAuthClientIdLoopback<T extends string>(input: T) {\n assertOAuthLoopbackClientId(input)\n return input\n}\n\nexport function parseOAuthLoopbackClientId(\n input: string,\n): OAuthLoopbackClientIdParams {\n const result = safeParseOAuthLoopbackClientId(input)\n if (result.success) return result.value\n\n throw new TypeError(`Invalid loopback client ID: ${result.message}`)\n}\n\n/**\n * Similar to Zod's {@link SafeParseReturnType} but uses a simple \"message\"\n * string instead of an \"error\" Error object.\n */\ntype LightParseReturnType<T> =\n | { success: true; value: T }\n | { success: false; message: string }\n\nexport function safeParseOAuthLoopbackClientId(\n input: string,\n): LightParseReturnType<OAuthLoopbackClientIdParams> {\n // @NOTE Not using \"new URL\" to ensure input indeed matches the type\n // OAuthClientIdLoopback\n\n if (!input.startsWith(LOOPBACK_CLIENT_ID_ORIGIN)) {\n return {\n success: false,\n message: `Value must start with \"${LOOPBACK_CLIENT_ID_ORIGIN}\"`,\n }\n }\n\n if (input.includes('#', LOOPBACK_CLIENT_ID_ORIGIN.length)) {\n return {\n success: false,\n message: 'Value must not contain a hash component',\n }\n }\n\n // Since we don't allow a path component (except for a single \"/\") the query\n // string starts after the origin (+ 1 if there is a \"/\")\n const queryStringIdx =\n input.length > LOOPBACK_CLIENT_ID_ORIGIN.length &&\n input.charCodeAt(LOOPBACK_CLIENT_ID_ORIGIN.length) === 0x2f /* '/' */\n ? LOOPBACK_CLIENT_ID_ORIGIN.length + 1\n : LOOPBACK_CLIENT_ID_ORIGIN.length\n\n // Since we determined the position of the query string based on the origin\n // length (instead of looking for a \"?\"), we need to make sure the query\n // string position (if any) indeed starts with a \"?\".\n if (\n input.length !== queryStringIdx &&\n input.charCodeAt(queryStringIdx) !== 0x3f /* '?' */\n ) {\n return {\n success: false,\n message: 'Value must not contain a path component',\n }\n }\n\n const queryString = input.slice(queryStringIdx + 1)\n return safeParseOAuthLoopbackClientIdQueryString(queryString)\n}\n\nexport function safeParseOAuthLoopbackClientIdQueryString(\n input: string | Iterable<[key: string, value: string]>,\n): LightParseReturnType<OAuthLoopbackClientIdParams> {\n // Parse query params\n const params: OAuthLoopbackClientIdParams = {}\n\n const it = typeof input === 'string' ? new URLSearchParams(input) : input\n for (const [key, value] of it) {\n if (key === 'scope') {\n if ('scope' in params) {\n return {\n success: false,\n message: 'Duplicate \"scope\" query parameter',\n }\n }\n\n const res = oauthScopeSchema.safeParse(value)\n if (!res.success) {\n const reason = res.error.issues.map((i) => i.message).join(', ')\n return {\n success: false,\n message: `Invalid \"scope\" query parameter: ${reason || 'Validation failed'}`,\n }\n }\n\n params.scope = res.data\n } else if (key === 'redirect_uri') {\n const res = oauthLoopbackClientRedirectUriSchema.safeParse(value)\n if (!res.success) {\n const reason = res.error.issues.map((i) => i.message).join(', ')\n return {\n success: false,\n message: `Invalid \"redirect_uri\" query parameter: ${reason || 'Validation failed'}`,\n }\n }\n\n if (params.redirect_uris == null) params.redirect_uris = [res.data]\n else params.redirect_uris.push(res.data)\n } else {\n return {\n success: false,\n message: `Unexpected query parameter \"${key}\"`,\n }\n }\n }\n\n return {\n success: true,\n value: params,\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  /**
3
- * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#name-protected-resource-metadata-r}
3
+ * @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2}
4
4
  */
5
5
  export declare const oauthProtectedResourceMetadataSchema: z.ZodObject<{
6
6
  /**
@@ -5,7 +5,7 @@ const zod_1 = require("zod");
5
5
  const oauth_issuer_identifier_js_1 = require("./oauth-issuer-identifier.js");
6
6
  const uri_js_1 = require("./uri.js");
7
7
  /**
8
- * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#name-protected-resource-metadata-r}
8
+ * @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2}
9
9
  */
10
10
  exports.oauthProtectedResourceMetadataSchema = zod_1.z.object({
11
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-protected-resource-metadata.js","sourceRoot":"","sources":["../src/oauth-protected-resource-metadata.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AACvB,6EAA0E;AAC1E,qCAAuC;AAEvC;;GAEG;AACU,QAAA,oCAAoC,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3D;;;;;;;OAOG;IACH,QAAQ,EAAE,qBAAY;SACnB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QACnC,OAAO,EAAE,gDAAgD;KAC1D,CAAC;SACD,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QACnC,OAAO,EAAE,0CAA0C;KACpD,CAAC;IAEJ;;;;;;;OAOG;IACH,qBAAqB,EAAE,OAAC,CAAC,KAAK,CAAC,wDAA2B,CAAC,CAAC,QAAQ,EAAE;IAEtE;;;;;;;OAOG;IACH,QAAQ,EAAE,qBAAY,CAAC,QAAQ,EAAE;IAEjC;;;;;OAKG;IACH,gBAAgB,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAEhD;;;;;OAKG;IACH,wBAAwB,EAAE,OAAC;SACxB,KAAK,CAAC,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;SAC1C,QAAQ,EAAE;IAEb;;;;;;OAMG;IACH,qCAAqC,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAErE;;;OAGG;IACH,sBAAsB,EAAE,qBAAY,CAAC,QAAQ,EAAE;IAE/C;;;;OAIG;IACH,mBAAmB,EAAE,qBAAY,CAAC,QAAQ,EAAE;IAE5C;;;OAGG;IACH,gBAAgB,EAAE,qBAAY,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAA","sourcesContent":["import { z } from 'zod'\nimport { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'\nimport { webUriSchema } from './uri.js'\n\n/**\n * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#name-protected-resource-metadata-r}\n */\nexport const oauthProtectedResourceMetadataSchema = z.object({\n /**\n * REQUIRED. The protected resource's resource identifier, which is a URL that\n * uses the https scheme and has no query or fragment components. Using these\n * well-known resources is described in Section 3.\n *\n * @note This schema allows non https URLs for testing & development purposes.\n * Make sure to validate the URL before using it in a production environment.\n */\n resource: webUriSchema\n .refine((url) => !url.includes('?'), {\n message: 'Resource URL must not contain query parameters',\n })\n .refine((url) => !url.includes('#'), {\n message: 'Resource URL must not contain a fragment',\n }),\n\n /**\n * OPTIONAL. JSON array containing a list of OAuth authorization server issuer\n * identifiers, as defined in [RFC8414], for authorization servers that can be\n * used with this protected resource. Protected resources MAY choose not to\n * advertise some supported authorization servers even when this parameter is\n * used. In some use cases, the set of authorization servers will not be\n * enumerable, in which case this metadata parameter would not be used.\n */\n authorization_servers: z.array(oauthIssuerIdentifierSchema).optional(),\n\n /**\n * OPTIONAL. URL of the protected resource's JWK Set [JWK] document. This\n * contains public keys belonging to the protected resource, such as signing\n * key(s) that the resource server uses to sign resource responses. This URL\n * MUST use the https scheme. When both signing and encryption keys are made\n * available, a use (public key use) parameter value is REQUIRED for all keys\n * in the referenced JWK Set to indicate each key's intended usage.\n */\n jwks_uri: webUriSchema.optional(),\n\n /**\n * RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope\n * values that are used in authorization requests to request access to this\n * protected resource. Protected resources MAY choose not to advertise some\n * scope values supported even when this parameter is used.\n */\n scopes_supported: z.array(z.string()).optional(),\n\n /**\n * OPTIONAL. JSON array containing a list of the supported methods of sending\n * an OAuth 2.0 Bearer Token [RFC6750] to the protected resource. Defined\n * values are [\"header\", \"body\", \"query\"], corresponding to Sections 2.1, 2.2,\n * and 2.3 of RFC 6750.\n */\n bearer_methods_supported: z\n .array(z.enum(['header', 'body', 'query']))\n .optional(),\n\n /**\n * OPTIONAL. JSON array containing a list of the JWS [JWS] signing algorithms\n * (alg values) [JWA] supported by the protected resource for signing resource\n * responses, for instance, as described in [FAPI.MessageSigning]. No default\n * algorithms are implied if this entry is omitted. The value none MUST NOT be\n * used.\n */\n resource_signing_alg_values_supported: z.array(z.string()).optional(),\n\n /**\n * OPTIONAL. URL of a page containing human-readable information that\n * developers might want or need to know when using the protected resource\n */\n resource_documentation: webUriSchema.optional(),\n\n /**\n * OPTIONAL. URL that the protected resource provides to read about the\n * protected resource's requirements on how the client can use the data\n * provided by the protected resource\n */\n resource_policy_uri: webUriSchema.optional(),\n\n /**\n * OPTIONAL. URL that the protected resource provides to read about the\n * protected resource's terms of service\n */\n resource_tos_uri: webUriSchema.optional(),\n})\n\nexport type OAuthProtectedResourceMetadata = z.infer<\n typeof oauthProtectedResourceMetadataSchema\n>\n"]}
1
+ {"version":3,"file":"oauth-protected-resource-metadata.js","sourceRoot":"","sources":["../src/oauth-protected-resource-metadata.ts"],"names":[],"mappings":";;;AAAA,6BAAuB;AACvB,6EAA0E;AAC1E,qCAAuC;AAEvC;;GAEG;AACU,QAAA,oCAAoC,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3D;;;;;;;OAOG;IACH,QAAQ,EAAE,qBAAY;SACnB,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QACnC,OAAO,EAAE,gDAAgD;KAC1D,CAAC;SACD,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;QACnC,OAAO,EAAE,0CAA0C;KACpD,CAAC;IAEJ;;;;;;;OAOG;IACH,qBAAqB,EAAE,OAAC,CAAC,KAAK,CAAC,wDAA2B,CAAC,CAAC,QAAQ,EAAE;IAEtE;;;;;;;OAOG;IACH,QAAQ,EAAE,qBAAY,CAAC,QAAQ,EAAE;IAEjC;;;;;OAKG;IACH,gBAAgB,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAEhD;;;;;OAKG;IACH,wBAAwB,EAAE,OAAC;SACxB,KAAK,CAAC,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;SAC1C,QAAQ,EAAE;IAEb;;;;;;OAMG;IACH,qCAAqC,EAAE,OAAC,CAAC,KAAK,CAAC,OAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IAErE;;;OAGG;IACH,sBAAsB,EAAE,qBAAY,CAAC,QAAQ,EAAE;IAE/C;;;;OAIG;IACH,mBAAmB,EAAE,qBAAY,CAAC,QAAQ,EAAE;IAE5C;;;OAGG;IACH,gBAAgB,EAAE,qBAAY,CAAC,QAAQ,EAAE;CAC1C,CAAC,CAAA","sourcesContent":["import { z } from 'zod'\nimport { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'\nimport { webUriSchema } from './uri.js'\n\n/**\n * @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2}\n */\nexport const oauthProtectedResourceMetadataSchema = z.object({\n /**\n * REQUIRED. The protected resource's resource identifier, which is a URL that\n * uses the https scheme and has no query or fragment components. Using these\n * well-known resources is described in Section 3.\n *\n * @note This schema allows non https URLs for testing & development purposes.\n * Make sure to validate the URL before using it in a production environment.\n */\n resource: webUriSchema\n .refine((url) => !url.includes('?'), {\n message: 'Resource URL must not contain query parameters',\n })\n .refine((url) => !url.includes('#'), {\n message: 'Resource URL must not contain a fragment',\n }),\n\n /**\n * OPTIONAL. JSON array containing a list of OAuth authorization server issuer\n * identifiers, as defined in [RFC8414], for authorization servers that can be\n * used with this protected resource. Protected resources MAY choose not to\n * advertise some supported authorization servers even when this parameter is\n * used. In some use cases, the set of authorization servers will not be\n * enumerable, in which case this metadata parameter would not be used.\n */\n authorization_servers: z.array(oauthIssuerIdentifierSchema).optional(),\n\n /**\n * OPTIONAL. URL of the protected resource's JWK Set [JWK] document. This\n * contains public keys belonging to the protected resource, such as signing\n * key(s) that the resource server uses to sign resource responses. This URL\n * MUST use the https scheme. When both signing and encryption keys are made\n * available, a use (public key use) parameter value is REQUIRED for all keys\n * in the referenced JWK Set to indicate each key's intended usage.\n */\n jwks_uri: webUriSchema.optional(),\n\n /**\n * RECOMMENDED. JSON array containing a list of the OAuth 2.0 [RFC6749] scope\n * values that are used in authorization requests to request access to this\n * protected resource. Protected resources MAY choose not to advertise some\n * scope values supported even when this parameter is used.\n */\n scopes_supported: z.array(z.string()).optional(),\n\n /**\n * OPTIONAL. JSON array containing a list of the supported methods of sending\n * an OAuth 2.0 Bearer Token [RFC6750] to the protected resource. Defined\n * values are [\"header\", \"body\", \"query\"], corresponding to Sections 2.1, 2.2,\n * and 2.3 of RFC 6750.\n */\n bearer_methods_supported: z\n .array(z.enum(['header', 'body', 'query']))\n .optional(),\n\n /**\n * OPTIONAL. JSON array containing a list of the JWS [JWS] signing algorithms\n * (alg values) [JWA] supported by the protected resource for signing resource\n * responses, for instance, as described in [FAPI.MessageSigning]. No default\n * algorithms are implied if this entry is omitted. The value none MUST NOT be\n * used.\n */\n resource_signing_alg_values_supported: z.array(z.string()).optional(),\n\n /**\n * OPTIONAL. URL of a page containing human-readable information that\n * developers might want or need to know when using the protected resource\n */\n resource_documentation: webUriSchema.optional(),\n\n /**\n * OPTIONAL. URL that the protected resource provides to read about the\n * protected resource's requirements on how the client can use the data\n * provided by the protected resource\n */\n resource_policy_uri: webUriSchema.optional(),\n\n /**\n * OPTIONAL. URL that the protected resource provides to read about the\n * protected resource's terms of service\n */\n resource_tos_uri: webUriSchema.optional(),\n})\n\nexport type OAuthProtectedResourceMetadata = z.infer<\n typeof oauthProtectedResourceMetadataSchema\n>\n"]}
@@ -1,10 +1,22 @@
1
1
  import { TypeOf, z } from 'zod';
2
- export declare const oauthLoopbackRedirectURISchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `http://[::1]${string}` | "http://localhost" | `http://localhost#${string}` | `http://localhost?${string}` | `http://localhost/${string}` | `http://localhost:${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>, `http://[::1]${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>;
3
- export type OAuthLoopbackRedirectURI = TypeOf<typeof oauthLoopbackRedirectURISchema>;
4
- export declare const oauthHttpsRedirectURISchema: z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `https://${string}`, string>;
5
- export type OAuthHttpsRedirectURI = TypeOf<typeof oauthHttpsRedirectURISchema>;
6
- export declare const oauthPrivateUseRedirectURISchema: z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `${string}.${string}:/${string}`, string>;
7
- export type OAuthPrivateUseRedirectURI = TypeOf<typeof oauthPrivateUseRedirectURISchema>;
2
+ /**
3
+ * This is a {@link loopbackUriSchema} with the additional restriction that
4
+ * the hostname `localhost` is not allowed.
5
+ *
6
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc8252#section-8.3 Loopback Redirect Considerations} RFC8252
7
+ *
8
+ * > While redirect URIs using localhost (i.e.,
9
+ * > "http://localhost:{port}/{path}") function similarly to loopback IP
10
+ * > redirects described in Section 7.3, the use of localhost is NOT
11
+ * > RECOMMENDED. Specifying a redirect URI with the loopback IP literal rather
12
+ * > than localhost avoids inadvertently listening on network interfaces other
13
+ * > than the loopback interface. It is also less susceptible to client-side
14
+ * > firewalls and misconfigured host name resolution on the user's device.
15
+ */
16
+ export declare const loopbackRedirectURISchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `http://[::1]${string}` | "http://localhost" | `http://localhost#${string}` | `http://localhost?${string}` | `http://localhost/${string}` | `http://localhost:${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>, `http://[::1]${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>;
17
+ export type LoopbackRedirectURI = TypeOf<typeof loopbackRedirectURISchema>;
18
+ export declare const oauthLoopbackClientRedirectUriSchema: z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `http://[::1]${string}` | "http://localhost" | `http://localhost#${string}` | `http://localhost?${string}` | `http://localhost/${string}` | `http://localhost:${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>, `http://[::1]${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>;
19
+ export type OAuthLoopbackRedirectURI = TypeOf<typeof oauthLoopbackClientRedirectUriSchema>;
8
20
  export declare const oauthRedirectUriSchema: z.ZodUnion<[z.ZodEffects<z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `http://[::1]${string}` | "http://localhost" | `http://localhost#${string}` | `http://localhost?${string}` | `http://localhost/${string}` | `http://localhost:${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>, `http://[::1]${string}` | "http://127.0.0.1" | `http://127.0.0.1#${string}` | `http://127.0.0.1?${string}` | `http://127.0.0.1/${string}` | `http://127.0.0.1:${string}`, string>, z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `https://${string}`, string>, z.ZodEffects<z.ZodEffects<z.ZodString, `${string}:${string}`, string>, `${string}.${string}:/${string}`, string>]>;
9
21
  export type OAuthRedirectUri = TypeOf<typeof oauthRedirectUriSchema>;
10
22
  //# sourceMappingURL=oauth-redirect-uri.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-redirect-uri.d.ts","sourceRoot":"","sources":["../src/oauth-redirect-uri.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAA;AAQ7C,eAAO,MAAM,8BAA8B,2kBAuB1C,CAAA;AACD,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAC3C,OAAO,8BAA8B,CACtC,CAAA;AAED,eAAO,MAAM,2BAA2B,qGAAiB,CAAA;AACzD,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,OAAO,2BAA2B,CAAC,CAAA;AAE9E,eAAO,MAAM,gCAAgC,kHAAsB,CAAA;AACnE,MAAM,MAAM,0BAA0B,GAAG,MAAM,CAC7C,OAAO,gCAAgC,CACxC,CAAA;AAED,eAAO,MAAM,sBAAsB,gzBASlC,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAA"}
1
+ {"version":3,"file":"oauth-redirect-uri.d.ts","sourceRoot":"","sources":["../src/oauth-redirect-uri.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAA;AAQ7C;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,2kBAarC,CAAA;AACD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,OAAO,yBAAyB,CAAC,CAAA;AAE1E,eAAO,MAAM,oCAAoC,2kBAA4B,CAAA;AAC7E,MAAM,MAAM,wBAAwB,GAAG,MAAM,CAC3C,OAAO,oCAAoC,CAC5C,CAAA;AAED,eAAO,MAAM,sBAAsB,gzBAKlC,CAAA;AACD,MAAM,MAAM,gBAAgB,GAAG,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAA"}
@@ -1,20 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.oauthRedirectUriSchema = exports.oauthPrivateUseRedirectURISchema = exports.oauthHttpsRedirectURISchema = exports.oauthLoopbackRedirectURISchema = void 0;
3
+ exports.oauthRedirectUriSchema = exports.oauthLoopbackClientRedirectUriSchema = exports.loopbackRedirectURISchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const uri_js_1 = require("./uri.js");
6
- exports.oauthLoopbackRedirectURISchema = uri_js_1.loopbackUriSchema.superRefine((value, ctx) => {
6
+ /**
7
+ * This is a {@link loopbackUriSchema} with the additional restriction that
8
+ * the hostname `localhost` is not allowed.
9
+ *
10
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc8252#section-8.3 Loopback Redirect Considerations} RFC8252
11
+ *
12
+ * > While redirect URIs using localhost (i.e.,
13
+ * > "http://localhost:{port}/{path}") function similarly to loopback IP
14
+ * > redirects described in Section 7.3, the use of localhost is NOT
15
+ * > RECOMMENDED. Specifying a redirect URI with the loopback IP literal rather
16
+ * > than localhost avoids inadvertently listening on network interfaces other
17
+ * > than the loopback interface. It is also less susceptible to client-side
18
+ * > firewalls and misconfigured host name resolution on the user's device.
19
+ */
20
+ exports.loopbackRedirectURISchema = uri_js_1.loopbackUriSchema.superRefine((value, ctx) => {
7
21
  if (value.startsWith('http://localhost')) {
8
- // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3
9
- //
10
- // > While redirect URIs using localhost (i.e.,
11
- // > "http://localhost:{port}/{path}") function similarly to loopback IP
12
- // > redirects described in Section 7.3, the use of localhost is NOT
13
- // > RECOMMENDED. Specifying a redirect URI with the loopback IP literal
14
- // > rather than localhost avoids inadvertently listening on network
15
- // > interfaces other than the loopback interface. It is also less
16
- // > susceptible to client-side firewalls and misconfigured host name
17
- // > resolution on the user's device.
18
22
  ctx.addIssue({
19
23
  code: zod_1.ZodIssueCode.custom,
20
24
  message: 'Use of "localhost" hostname is not allowed (RFC 8252), use a loopback IP such as "127.0.0.1" instead',
@@ -23,13 +27,8 @@ exports.oauthLoopbackRedirectURISchema = uri_js_1.loopbackUriSchema.superRefine(
23
27
  }
24
28
  return true;
25
29
  });
26
- exports.oauthHttpsRedirectURISchema = uri_js_1.httpsUriSchema;
27
- exports.oauthPrivateUseRedirectURISchema = uri_js_1.privateUseUriSchema;
28
- exports.oauthRedirectUriSchema = zod_1.z.union([
29
- exports.oauthLoopbackRedirectURISchema,
30
- exports.oauthHttpsRedirectURISchema,
31
- exports.oauthPrivateUseRedirectURISchema,
32
- ], {
30
+ exports.oauthLoopbackClientRedirectUriSchema = exports.loopbackRedirectURISchema;
31
+ exports.oauthRedirectUriSchema = zod_1.z.union([exports.loopbackRedirectURISchema, uri_js_1.httpsUriSchema, uri_js_1.privateUseUriSchema], {
33
32
  message: `URL must use the "https:" or "http:" protocol, or a private-use URI scheme (RFC 8252)`,
34
33
  });
35
34
  //# sourceMappingURL=oauth-redirect-uri.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-redirect-uri.js","sourceRoot":"","sources":["../src/oauth-redirect-uri.ts"],"names":[],"mappings":";;;AAAA,6BAA6C;AAC7C,qCAKiB;AAEJ,QAAA,8BAA8B,GAAG,0BAAiB,CAAC,WAAW,CACzE,CAAC,KAAK,EAAE,GAAG,EAA8D,EAAE;IACzE,IAAI,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACzC,4DAA4D;QAC5D,EAAE;QACF,+CAA+C;QAC/C,wEAAwE;QACxE,oEAAoE;QACpE,yEAAyE;QACzE,oEAAoE;QACpE,mEAAmE;QACnE,qEAAqE;QACrE,qCAAqC;QACrC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,kBAAY,CAAC,MAAM;YACzB,OAAO,EACL,sGAAsG;SACzG,CAAC,CAAA;QACF,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CACF,CAAA;AAKY,QAAA,2BAA2B,GAAG,uBAAc,CAAA;AAG5C,QAAA,gCAAgC,GAAG,4BAAmB,CAAA;AAKtD,QAAA,sBAAsB,GAAG,OAAC,CAAC,KAAK,CAC3C;IACE,sCAA8B;IAC9B,mCAA2B;IAC3B,wCAAgC;CACjC,EACD;IACE,OAAO,EAAE,uFAAuF;CACjG,CACF,CAAA","sourcesContent":["import { TypeOf, ZodIssueCode, z } from 'zod'\nimport {\n LoopbackUri,\n httpsUriSchema,\n loopbackUriSchema,\n privateUseUriSchema,\n} from './uri.js'\n\nexport const oauthLoopbackRedirectURISchema = loopbackUriSchema.superRefine(\n (value, ctx): value is Exclude<LoopbackUri, `http://localhost${string}`> => {\n if (value.startsWith('http://localhost')) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3\n //\n // > While redirect URIs using localhost (i.e.,\n // > \"http://localhost:{port}/{path}\") function similarly to loopback IP\n // > redirects described in Section 7.3, the use of localhost is NOT\n // > RECOMMENDED. Specifying a redirect URI with the loopback IP literal\n // > rather than localhost avoids inadvertently listening on network\n // > interfaces other than the loopback interface. It is also less\n // > susceptible to client-side firewalls and misconfigured host name\n // > resolution on the user's device.\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message:\n 'Use of \"localhost\" hostname is not allowed (RFC 8252), use a loopback IP such as \"127.0.0.1\" instead',\n })\n return false\n }\n\n return true\n },\n)\nexport type OAuthLoopbackRedirectURI = TypeOf<\n typeof oauthLoopbackRedirectURISchema\n>\n\nexport const oauthHttpsRedirectURISchema = httpsUriSchema\nexport type OAuthHttpsRedirectURI = TypeOf<typeof oauthHttpsRedirectURISchema>\n\nexport const oauthPrivateUseRedirectURISchema = privateUseUriSchema\nexport type OAuthPrivateUseRedirectURI = TypeOf<\n typeof oauthPrivateUseRedirectURISchema\n>\n\nexport const oauthRedirectUriSchema = z.union(\n [\n oauthLoopbackRedirectURISchema,\n oauthHttpsRedirectURISchema,\n oauthPrivateUseRedirectURISchema,\n ],\n {\n message: `URL must use the \"https:\" or \"http:\" protocol, or a private-use URI scheme (RFC 8252)`,\n },\n)\n\nexport type OAuthRedirectUri = TypeOf<typeof oauthRedirectUriSchema>\n"]}
1
+ {"version":3,"file":"oauth-redirect-uri.js","sourceRoot":"","sources":["../src/oauth-redirect-uri.ts"],"names":[],"mappings":";;;AAAA,6BAA6C;AAC7C,qCAKiB;AAEjB;;;;;;;;;;;;;GAaG;AACU,QAAA,yBAAyB,GAAG,0BAAiB,CAAC,WAAW,CACpE,CAAC,KAAK,EAAE,GAAG,EAA8D,EAAE;IACzE,IAAI,KAAK,CAAC,UAAU,CAAC,kBAAkB,CAAC,EAAE,CAAC;QACzC,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,kBAAY,CAAC,MAAM;YACzB,OAAO,EACL,sGAAsG;SACzG,CAAC,CAAA;QACF,OAAO,KAAK,CAAA;IACd,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC,CACF,CAAA;AAGY,QAAA,oCAAoC,GAAG,iCAAyB,CAAA;AAKhE,QAAA,sBAAsB,GAAG,OAAC,CAAC,KAAK,CAC3C,CAAC,iCAAyB,EAAE,uBAAc,EAAE,4BAAmB,CAAC,EAChE;IACE,OAAO,EAAE,uFAAuF;CACjG,CACF,CAAA","sourcesContent":["import { TypeOf, ZodIssueCode, z } from 'zod'\nimport {\n LoopbackUri,\n httpsUriSchema,\n loopbackUriSchema,\n privateUseUriSchema,\n} from './uri.js'\n\n/**\n * This is a {@link loopbackUriSchema} with the additional restriction that\n * the hostname `localhost` is not allowed.\n *\n * @see {@link https://datatracker.ietf.org/doc/html/rfc8252#section-8.3 Loopback Redirect Considerations} RFC8252\n *\n * > While redirect URIs using localhost (i.e.,\n * > \"http://localhost:{port}/{path}\") function similarly to loopback IP\n * > redirects described in Section 7.3, the use of localhost is NOT\n * > RECOMMENDED. Specifying a redirect URI with the loopback IP literal rather\n * > than localhost avoids inadvertently listening on network interfaces other\n * > than the loopback interface. It is also less susceptible to client-side\n * > firewalls and misconfigured host name resolution on the user's device.\n */\nexport const loopbackRedirectURISchema = loopbackUriSchema.superRefine(\n (value, ctx): value is Exclude<LoopbackUri, `http://localhost${string}`> => {\n if (value.startsWith('http://localhost')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message:\n 'Use of \"localhost\" hostname is not allowed (RFC 8252), use a loopback IP such as \"127.0.0.1\" instead',\n })\n return false\n }\n\n return true\n },\n)\nexport type LoopbackRedirectURI = TypeOf<typeof loopbackRedirectURISchema>\n\nexport const oauthLoopbackClientRedirectUriSchema = loopbackRedirectURISchema\nexport type OAuthLoopbackRedirectURI = TypeOf<\n typeof oauthLoopbackClientRedirectUriSchema\n>\n\nexport const oauthRedirectUriSchema = z.union(\n [loopbackRedirectURISchema, httpsUriSchema, privateUseUriSchema],\n {\n message: `URL must use the \"https:\" or \"http:\" protocol, or a private-use URI scheme (RFC 8252)`,\n },\n)\nexport type OAuthRedirectUri = TypeOf<typeof oauthRedirectUriSchema>\n"]}
package/dist/uri.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAA;AAgB7C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,0DAQ5B,CAAA;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE5D,eAAO,MAAM,iBAAiB,2YA6B7B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc,qGA6C1B,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAA;AAEpD,eAAO,MAAM,YAAY,oXAqBrB,CAAA;AAEJ,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAA;AAEhD,eAAO,MAAM,mBAAmB,kHAsC/B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
1
+ {"version":3,"file":"uri.d.ts","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAA;AAQ7C;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,0DAQ5B,CAAA;AAEH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAE5D,eAAO,MAAM,iBAAiB,2YA6B7B,CAAA;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,iBAAiB,CAAC,CAAA;AAE1D,eAAO,MAAM,cAAc,qGA6C1B,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,cAAc,CAAC,CAAA;AAEpD,eAAO,MAAM,YAAY,oXAqBrB,CAAA;AAEJ,MAAM,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,YAAY,CAAC,CAAA;AAEhD,eAAO,MAAM,mBAAmB,kHAkF/B,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,OAAO,mBAAmB,CAAC,CAAA"}
package/dist/uri.js CHANGED
@@ -3,19 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.privateUseUriSchema = exports.webUriSchema = exports.httpsUriSchema = exports.loopbackUriSchema = exports.dangerousUriSchema = void 0;
4
4
  const zod_1 = require("zod");
5
5
  const util_js_1 = require("./util.js");
6
- const canParseUrl =
7
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
8
- URL.canParse?.bind(URL) ??
9
- // URL.canParse is not available in Node.js < 18.7.0
10
- ((urlStr) => {
11
- try {
12
- new URL(urlStr);
13
- return true;
14
- }
15
- catch {
16
- return false;
17
- }
18
- });
19
6
  /**
20
7
  * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
21
8
  *
@@ -23,7 +10,7 @@ URL.canParse?.bind(URL) ??
23
10
  */
24
11
  exports.dangerousUriSchema = zod_1.z
25
12
  .string()
26
- .refine((data) => data.includes(':') && canParseUrl(data), {
13
+ .refine((data) => data.includes(':') && (0, util_js_1.canParseUrl)(data), {
27
14
  message: 'Invalid URL',
28
15
  });
29
16
  exports.loopbackUriSchema = exports.dangerousUriSchema.superRefine((value, ctx) => {
@@ -127,11 +114,51 @@ exports.privateUseUriSchema = exports.dangerousUriSchema.superRefine((value, ctx
127
114
  });
128
115
  return false;
129
116
  }
130
- if (url.hostname) {
131
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
117
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
118
+ //
119
+ // > When choosing a URI scheme to associate with the app, apps MUST use a
120
+ // > URI scheme based on a domain name under their control, expressed in
121
+ // > reverse order
122
+ //
123
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
124
+ //
125
+ // > In addition to the collision-resistant properties, requiring a URI
126
+ // > scheme based on a domain name that is under the control of the app can
127
+ // > help to prove ownership in the event of a dispute where two apps claim
128
+ // > the same private-use URI scheme (where one app is acting maliciously).
129
+ //
130
+ // We can't check for ownership here (as there is no concept of proven
131
+ // ownership in a generic validation logic), besides excluding local domains
132
+ // as they can't be controlled/owned by the app.
133
+ //
134
+ // https://atproto.com/specs/oauth
135
+ //
136
+ // > Any custom scheme must match the `client_id` hostname in reverse-domain
137
+ // > order.
138
+ //
139
+ // This ATPROTO specific requirement cannot be enforced here, (as there is
140
+ // no concept of `client_id` in this context).
141
+ const uriScheme = url.protocol.slice(0, -1); // remove trailing ":"
142
+ const urlDomain = uriScheme.split('.').reverse().join('.');
143
+ if ((0, util_js_1.isLocalHostname)(urlDomain)) {
144
+ ctx.addIssue({
145
+ code: zod_1.ZodIssueCode.custom,
146
+ message: `Private-use URI Scheme redirect URI must not be a local hostname`,
147
+ });
148
+ }
149
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
150
+ //
151
+ // > Following the requirements of Section 3.2 of [RFC3986], as there is no
152
+ // > naming authority for private-use URI scheme redirects, only a single
153
+ // > slash ("/") appears after the scheme component.
154
+ if (url.href.startsWith(`${url.protocol}//`) ||
155
+ url.username ||
156
+ url.password ||
157
+ url.hostname ||
158
+ url.port) {
132
159
  ctx.addIssue({
133
160
  code: zod_1.ZodIssueCode.custom,
134
- message: 'Private-use URI schemes must not include a hostname (only one "/" is allowed after the protocol, as per RFC 8252)',
161
+ message: `Private-Use URI Scheme must be in the form ${url.protocol}/<path> (as per RFC 8252)`,
135
162
  });
136
163
  return false;
137
164
  }
package/dist/uri.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"uri.js","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"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","sourcesContent":["import { TypeOf, ZodIssueCode, z } from 'zod'\nimport { isHostnameIP, isLoopbackHost } from './util.js'\n\nconst canParseUrl =\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n URL.canParse?.bind(URL) ??\n // URL.canParse is not available in Node.js < 18.7.0\n ((urlStr: string): boolean => {\n try {\n new URL(urlStr)\n return true\n } catch {\n return false\n }\n })\n\n/**\n * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).\n *\n * Any value that matches this schema is safe to parse using `new URL()`.\n */\nexport const dangerousUriSchema = z\n .string()\n .refine(\n (data): data is `${string}:${string}` =>\n data.includes(':') && canParseUrl(data),\n {\n message: 'Invalid URL',\n },\n )\n\n/**\n * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).\n */\nexport type DangerousUrl = TypeOf<typeof dangerousUriSchema>\n\nexport const loopbackUriSchema = dangerousUriSchema.superRefine(\n (\n value,\n ctx,\n ): value is\n | `http://[::1]${string}`\n | `http://localhost${'' | `${':' | '/' | '?' | '#'}${string}`}`\n | `http://127.0.0.1${'' | `${':' | '/' | '?' | '#'}${string}`}` => {\n // Loopback url must use the \"http:\" protocol\n if (!value.startsWith('http://')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use the \"http:\" protocol',\n })\n return false\n }\n\n const url = new URL(value)\n\n if (!isLoopbackHost(url.hostname)) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname',\n })\n return false\n }\n\n return true\n },\n)\n\nexport type LoopbackUri = TypeOf<typeof loopbackUriSchema>\n\nexport const httpsUriSchema = dangerousUriSchema.superRefine(\n (value, ctx): value is `https://${string}` => {\n if (!value.startsWith('https://')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use the \"https:\" protocol',\n })\n return false\n }\n\n const url = new URL(value)\n\n // Disallow loopback URLs with the `https:` protocol\n if (isLoopbackHost(url.hostname)) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'https: URL must not use a loopback host',\n })\n return false\n }\n\n if (isHostnameIP(url.hostname)) {\n // Hostname is an IP address\n } else {\n // Hostname is a domain name\n if (!url.hostname.includes('.')) {\n // we don't depend on PSL here, so we only check for a dot\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'Domain name must contain at least two segments',\n })\n return false\n }\n\n if (url.hostname.endsWith('.local')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'Domain name must not end with \".local\"',\n })\n return false\n }\n }\n\n return true\n },\n)\n\nexport type HttpsUri = TypeOf<typeof httpsUriSchema>\n\nexport const webUriSchema = z\n .string()\n .superRefine((value, ctx): value is LoopbackUri | HttpsUri => {\n // discriminated union of `loopbackUriSchema` and `httpsUriSchema`\n if (value.startsWith('http://')) {\n const result = loopbackUriSchema.safeParse(value)\n if (!result.success) result.error.issues.forEach(ctx.addIssue, ctx)\n return result.success\n }\n\n if (value.startsWith('https://')) {\n const result = httpsUriSchema.safeParse(value)\n if (!result.success) result.error.issues.forEach(ctx.addIssue, ctx)\n return result.success\n }\n\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use the \"http:\" or \"https:\" protocol',\n })\n return false\n })\n\nexport type WebUri = TypeOf<typeof webUriSchema>\n\nexport const privateUseUriSchema = dangerousUriSchema.superRefine(\n (value, ctx): value is `${string}.${string}:/${string}` => {\n const dotIdx = value.indexOf('.')\n const colonIdx = value.indexOf(':')\n\n // Optimization: avoid parsing the URL if the protocol does not contain a \".\"\n if (dotIdx === -1 || colonIdx === -1 || dotIdx > colonIdx) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message:\n 'Private-use URI scheme requires a \".\" as part of the protocol',\n })\n return false\n }\n\n const url = new URL(value)\n\n // Should be covered by the check before, but let's be extra sure\n if (!url.protocol.includes('.')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'Invalid private-use URI scheme',\n })\n return false\n }\n\n if (url.hostname) {\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message:\n 'Private-use URI schemes must not include a hostname (only one \"/\" is allowed after the protocol, as per RFC 8252)',\n })\n return false\n }\n\n return true\n },\n)\n\nexport type PrivateUseUri = TypeOf<typeof privateUseUriSchema>\n"]}
1
+ {"version":3,"file":"uri.js","sourceRoot":"","sources":["../src/uri.ts"],"names":[],"mappings":"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","sourcesContent":["import { TypeOf, ZodIssueCode, z } from 'zod'\nimport {\n canParseUrl,\n isHostnameIP,\n isLocalHostname,\n isLoopbackHost,\n} from './util.js'\n\n/**\n * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).\n *\n * Any value that matches this schema is safe to parse using `new URL()`.\n */\nexport const dangerousUriSchema = z\n .string()\n .refine(\n (data): data is `${string}:${string}` =>\n data.includes(':') && canParseUrl(data),\n {\n message: 'Invalid URL',\n },\n )\n\n/**\n * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).\n */\nexport type DangerousUrl = TypeOf<typeof dangerousUriSchema>\n\nexport const loopbackUriSchema = dangerousUriSchema.superRefine(\n (\n value,\n ctx,\n ): value is\n | `http://[::1]${string}`\n | `http://localhost${'' | `${':' | '/' | '?' | '#'}${string}`}`\n | `http://127.0.0.1${'' | `${':' | '/' | '?' | '#'}${string}`}` => {\n // Loopback url must use the \"http:\" protocol\n if (!value.startsWith('http://')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use the \"http:\" protocol',\n })\n return false\n }\n\n const url = new URL(value)\n\n if (!isLoopbackHost(url.hostname)) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use \"localhost\", \"127.0.0.1\" or \"[::1]\" as hostname',\n })\n return false\n }\n\n return true\n },\n)\n\nexport type LoopbackUri = TypeOf<typeof loopbackUriSchema>\n\nexport const httpsUriSchema = dangerousUriSchema.superRefine(\n (value, ctx): value is `https://${string}` => {\n if (!value.startsWith('https://')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use the \"https:\" protocol',\n })\n return false\n }\n\n const url = new URL(value)\n\n // Disallow loopback URLs with the `https:` protocol\n if (isLoopbackHost(url.hostname)) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'https: URL must not use a loopback host',\n })\n return false\n }\n\n if (isHostnameIP(url.hostname)) {\n // Hostname is an IP address\n } else {\n // Hostname is a domain name\n if (!url.hostname.includes('.')) {\n // we don't depend on PSL here, so we only check for a dot\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'Domain name must contain at least two segments',\n })\n return false\n }\n\n if (url.hostname.endsWith('.local')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'Domain name must not end with \".local\"',\n })\n return false\n }\n }\n\n return true\n },\n)\n\nexport type HttpsUri = TypeOf<typeof httpsUriSchema>\n\nexport const webUriSchema = z\n .string()\n .superRefine((value, ctx): value is LoopbackUri | HttpsUri => {\n // discriminated union of `loopbackUriSchema` and `httpsUriSchema`\n if (value.startsWith('http://')) {\n const result = loopbackUriSchema.safeParse(value)\n if (!result.success) result.error.issues.forEach(ctx.addIssue, ctx)\n return result.success\n }\n\n if (value.startsWith('https://')) {\n const result = httpsUriSchema.safeParse(value)\n if (!result.success) result.error.issues.forEach(ctx.addIssue, ctx)\n return result.success\n }\n\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'URL must use the \"http:\" or \"https:\" protocol',\n })\n return false\n })\n\nexport type WebUri = TypeOf<typeof webUriSchema>\n\nexport const privateUseUriSchema = dangerousUriSchema.superRefine(\n (value, ctx): value is `${string}.${string}:/${string}` => {\n const dotIdx = value.indexOf('.')\n const colonIdx = value.indexOf(':')\n\n // Optimization: avoid parsing the URL if the protocol does not contain a \".\"\n if (dotIdx === -1 || colonIdx === -1 || dotIdx > colonIdx) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message:\n 'Private-use URI scheme requires a \".\" as part of the protocol',\n })\n return false\n }\n\n const url = new URL(value)\n\n // Should be covered by the check before, but let's be extra sure\n if (!url.protocol.includes('.')) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: 'Invalid private-use URI scheme',\n })\n return false\n }\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1\n //\n // > When choosing a URI scheme to associate with the app, apps MUST use a\n // > URI scheme based on a domain name under their control, expressed in\n // > reverse order\n //\n // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4\n //\n // > In addition to the collision-resistant properties, requiring a URI\n // > scheme based on a domain name that is under the control of the app can\n // > help to prove ownership in the event of a dispute where two apps claim\n // > the same private-use URI scheme (where one app is acting maliciously).\n //\n // We can't check for ownership here (as there is no concept of proven\n // ownership in a generic validation logic), besides excluding local domains\n // as they can't be controlled/owned by the app.\n //\n // https://atproto.com/specs/oauth\n //\n // > Any custom scheme must match the `client_id` hostname in reverse-domain\n // > order.\n //\n // This ATPROTO specific requirement cannot be enforced here, (as there is\n // no concept of `client_id` in this context).\n\n const uriScheme = url.protocol.slice(0, -1) // remove trailing \":\"\n const urlDomain = uriScheme.split('.').reverse().join('.')\n\n if (isLocalHostname(urlDomain)) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `Private-use URI Scheme redirect URI must not be a local hostname`,\n })\n }\n\n // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1\n //\n // > Following the requirements of Section 3.2 of [RFC3986], as there is no\n // > naming authority for private-use URI scheme redirects, only a single\n // > slash (\"/\") appears after the scheme component.\n if (\n url.href.startsWith(`${url.protocol}//`) ||\n url.username ||\n url.password ||\n url.hostname ||\n url.port\n ) {\n ctx.addIssue({\n code: ZodIssueCode.custom,\n message: `Private-Use URI Scheme must be in the form ${url.protocol}/<path> (as per RFC 8252)`,\n })\n return false\n }\n\n return true\n },\n)\n\nexport type PrivateUseUri = TypeOf<typeof privateUseUriSchema>\n"]}
package/dist/util.d.ts CHANGED
@@ -1,7 +1,8 @@
1
+ export declare const canParseUrl: (url: string | URL, base?: string | URL) => boolean;
1
2
  export declare function isHostnameIP(hostname: string): boolean;
2
3
  export type LoopbackHost = 'localhost' | '127.0.0.1' | '[::1]';
3
4
  export declare function isLoopbackHost(host: unknown): host is LoopbackHost;
4
- export declare function isLoopbackUrl(input: URL | string): boolean;
5
+ export declare function isLocalHostname(hostname: string): boolean;
5
6
  export declare function safeUrl(input: URL | string): URL | null;
6
7
  export declare function extractUrlPath(url: any): any;
7
8
  export declare const jsonObjectPreprocess: (val: unknown) => any;
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,WAQ5C;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,CAAA;AAE9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,YAAY,CAElE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,OAAO,CAG1D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,IAAI,CAMvD;AAED,wBAAgB,cAAc,CAAC,GAAG,KAAA,OAsCjC;AAED,eAAO,MAAM,oBAAoB,GAAI,KAAK,OAAO,QAUhD,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,KAAK,OAAO,KAAG,OAM/C,CAAA;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,WAGlE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,WAExD;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,GAC7B,SAAS,GAAG,SAAS,CAAC,EAAE,CAI1B;AAED,MAAM,MAAM,mBAAmB,CAAC,KAAK,SAAS,MAAM,IAClD,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,GAAG,KAAK,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE,EAAE,CAAA;AAEpD,eAAO,MAAM,qBAAqB,GAAI,KAAK,SAAS,MAAM,EACxD,OAAO,KAAK,EACZ,OAAO,MAAM,KACZ,KAAK,IAAI,mBAAmB,CAAC,KAAK,CA+BpC,CAAA"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW,qDAWpB,CAAA;AAEJ,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,WAQ5C;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,WAAW,GAAG,OAAO,CAAA;AAE9D,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,YAAY,CAElE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAYzD;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,GAAG,GAAG,GAAG,IAAI,CAMvD;AAED,wBAAgB,cAAc,CAAC,GAAG,KAAA,OAsCjC;AAED,eAAO,MAAM,oBAAoB,GAAI,KAAK,OAAO,QAUhD,CAAA;AAED,eAAO,MAAM,gBAAgB,GAAI,KAAK,OAAO,KAAG,OAM/C,CAAA;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,WAGlE;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,WAExD;AAED,wBAAgB,OAAO,CAAC,CAAC,EACvB,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,SAAS,GAC7B,SAAS,GAAG,SAAS,CAAC,EAAE,CAI1B;AAED,MAAM,MAAM,mBAAmB,CAAC,KAAK,SAAS,MAAM,IAClD,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,GAAG,KAAK,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE,EAAE,CAAA;AAEpD,eAAO,MAAM,qBAAqB,GAAI,KAAK,SAAS,MAAM,EACxD,OAAO,KAAK,EACZ,OAAO,MAAM,KACZ,KAAK,IAAI,mBAAmB,CAAC,KAAK,CA+BpC,CAAA"}
package/dist/util.js CHANGED
@@ -1,14 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isSpaceSeparatedValue = exports.numberPreprocess = exports.jsonObjectPreprocess = void 0;
3
+ exports.isSpaceSeparatedValue = exports.numberPreprocess = exports.jsonObjectPreprocess = exports.canParseUrl = void 0;
4
4
  exports.isHostnameIP = isHostnameIP;
5
5
  exports.isLoopbackHost = isLoopbackHost;
6
- exports.isLoopbackUrl = isLoopbackUrl;
6
+ exports.isLocalHostname = isLocalHostname;
7
7
  exports.safeUrl = safeUrl;
8
8
  exports.extractUrlPath = extractUrlPath;
9
9
  exports.arrayEquivalent = arrayEquivalent;
10
10
  exports.includedIn = includedIn;
11
11
  exports.asArray = asArray;
12
+ exports.canParseUrl =
13
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
14
+ URL.canParse?.bind(URL) ??
15
+ // URL.canParse is not available in Node.js < 18.7.0
16
+ ((urlStr) => {
17
+ try {
18
+ new URL(urlStr);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ });
12
25
  function isHostnameIP(hostname) {
13
26
  // IPv4
14
27
  if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/))
@@ -21,9 +34,16 @@ function isHostnameIP(hostname) {
21
34
  function isLoopbackHost(host) {
22
35
  return host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
23
36
  }
24
- function isLoopbackUrl(input) {
25
- const url = typeof input === 'string' ? new URL(input) : input;
26
- return isLoopbackHost(url.hostname);
37
+ function isLocalHostname(hostname) {
38
+ const parts = hostname.split('.');
39
+ if (parts.length < 2)
40
+ return true;
41
+ const tld = parts.at(-1).toLowerCase();
42
+ return (tld === 'test' ||
43
+ tld === 'local' ||
44
+ tld === 'localhost' ||
45
+ tld === 'invalid' ||
46
+ tld === 'example');
27
47
  }
28
48
  function safeUrl(input) {
29
49
  try {
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["export function isHostnameIP(hostname: string) {\n // IPv4\n if (hostname.match(/^\\d+\\.\\d+\\.\\d+\\.\\d+$/)) return true\n\n // IPv6\n if (hostname.startsWith('[') && hostname.endsWith(']')) return true\n\n return false\n}\n\nexport type LoopbackHost = 'localhost' | '127.0.0.1' | '[::1]'\n\nexport function isLoopbackHost(host: unknown): host is LoopbackHost {\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]'\n}\n\nexport function isLoopbackUrl(input: URL | string): boolean {\n const url = typeof input === 'string' ? new URL(input) : input\n return isLoopbackHost(url.hostname)\n}\n\nexport function safeUrl(input: URL | string): URL | null {\n try {\n return new URL(input)\n } catch {\n return null\n }\n}\n\nexport function extractUrlPath(url) {\n // Extracts the path from a URL, without relying on the URL constructor\n // (because it normalizes the URL)\n const endOfProtocol = url.startsWith('https://')\n ? 8\n : url.startsWith('http://')\n ? 7\n : -1\n if (endOfProtocol === -1) {\n throw new TypeError('URL must use the \"https:\" or \"http:\" protocol')\n }\n\n const hashIdx = url.indexOf('#', endOfProtocol)\n const questionIdx = url.indexOf('?', endOfProtocol)\n\n const queryStrIdx =\n questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx)\n ? questionIdx\n : -1\n\n const pathEnd =\n hashIdx === -1\n ? queryStrIdx === -1\n ? url.length\n : queryStrIdx\n : queryStrIdx === -1\n ? hashIdx\n : Math.min(hashIdx, queryStrIdx)\n\n const slashIdx = url.indexOf('/', endOfProtocol)\n\n const pathStart = slashIdx === -1 || slashIdx > pathEnd ? pathEnd : slashIdx\n\n if (endOfProtocol === pathStart) {\n throw new TypeError('URL must contain a host')\n }\n\n return url.substring(pathStart, pathEnd)\n}\n\nexport const jsonObjectPreprocess = (val: unknown) => {\n if (typeof val === 'string' && val.startsWith('{') && val.endsWith('}')) {\n try {\n return JSON.parse(val)\n } catch {\n return val\n }\n }\n\n return val\n}\n\nexport const numberPreprocess = (val: unknown): unknown => {\n if (typeof val === 'string') {\n const number = Number(val)\n if (!Number.isNaN(number)) return number\n }\n return val\n}\n\n/**\n * Returns true if the two arrays contain the same elements, regardless of order\n * or duplicates.\n */\nexport function arrayEquivalent<T>(a: readonly T[], b: readonly T[]) {\n if (a === b) return true\n return a.every(includedIn, b) && b.every(includedIn, a)\n}\n\nexport function includedIn<T>(this: readonly T[], item: T) {\n return this.includes(item)\n}\n\nexport function asArray<T>(\n value: Iterable<T> | undefined,\n): undefined | readonly T[] {\n if (value == null) return undefined\n if (Array.isArray(value)) return value // already a (possibly readonly) array\n return Array.from(value)\n}\n\nexport type SpaceSeparatedValue<Value extends string> =\n `${'' | `${string} `}${Value}${'' | ` ${string}`}`\n\nexport const isSpaceSeparatedValue = <Value extends string>(\n value: Value,\n input: string,\n): input is SpaceSeparatedValue<Value> => {\n if (value.length === 0) throw new TypeError('Value cannot be empty')\n if (value.includes(' ')) throw new TypeError('Value cannot contain spaces')\n\n // Optimized version of:\n // return input.split(' ').includes(value)\n\n const inputLength = input.length\n const valueLength = value.length\n\n if (inputLength < valueLength) return false\n\n let idx = input.indexOf(value)\n let idxEnd: number\n\n while (idx !== -1) {\n idxEnd = idx + valueLength\n\n if (\n // at beginning or preceded by space\n (idx === 0 || input.charCodeAt(idx - 1) === 32) &&\n // at end or followed by space\n (idxEnd === inputLength || input.charCodeAt(idxEnd) === 32)\n ) {\n return true\n }\n\n idx = input.indexOf(value, idxEnd + 1)\n }\n\n return false\n}\n"]}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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","sourcesContent":["export const canParseUrl =\n // eslint-disable-next-line n/no-unsupported-features/node-builtins\n URL.canParse?.bind(URL) ??\n // URL.canParse is not available in Node.js < 18.7.0\n ((urlStr: string): boolean => {\n try {\n new URL(urlStr)\n return true\n } catch {\n return false\n }\n })\n\nexport function isHostnameIP(hostname: string) {\n // IPv4\n if (hostname.match(/^\\d+\\.\\d+\\.\\d+\\.\\d+$/)) return true\n\n // IPv6\n if (hostname.startsWith('[') && hostname.endsWith(']')) return true\n\n return false\n}\n\nexport type LoopbackHost = 'localhost' | '127.0.0.1' | '[::1]'\n\nexport function isLoopbackHost(host: unknown): host is LoopbackHost {\n return host === 'localhost' || host === '127.0.0.1' || host === '[::1]'\n}\n\nexport function isLocalHostname(hostname: string): boolean {\n const parts = hostname.split('.')\n if (parts.length < 2) return true\n\n const tld = parts.at(-1)!.toLowerCase()\n return (\n tld === 'test' ||\n tld === 'local' ||\n tld === 'localhost' ||\n tld === 'invalid' ||\n tld === 'example'\n )\n}\n\nexport function safeUrl(input: URL | string): URL | null {\n try {\n return new URL(input)\n } catch {\n return null\n }\n}\n\nexport function extractUrlPath(url) {\n // Extracts the path from a URL, without relying on the URL constructor\n // (because it normalizes the URL)\n const endOfProtocol = url.startsWith('https://')\n ? 8\n : url.startsWith('http://')\n ? 7\n : -1\n if (endOfProtocol === -1) {\n throw new TypeError('URL must use the \"https:\" or \"http:\" protocol')\n }\n\n const hashIdx = url.indexOf('#', endOfProtocol)\n const questionIdx = url.indexOf('?', endOfProtocol)\n\n const queryStrIdx =\n questionIdx !== -1 && (hashIdx === -1 || questionIdx < hashIdx)\n ? questionIdx\n : -1\n\n const pathEnd =\n hashIdx === -1\n ? queryStrIdx === -1\n ? url.length\n : queryStrIdx\n : queryStrIdx === -1\n ? hashIdx\n : Math.min(hashIdx, queryStrIdx)\n\n const slashIdx = url.indexOf('/', endOfProtocol)\n\n const pathStart = slashIdx === -1 || slashIdx > pathEnd ? pathEnd : slashIdx\n\n if (endOfProtocol === pathStart) {\n throw new TypeError('URL must contain a host')\n }\n\n return url.substring(pathStart, pathEnd)\n}\n\nexport const jsonObjectPreprocess = (val: unknown) => {\n if (typeof val === 'string' && val.startsWith('{') && val.endsWith('}')) {\n try {\n return JSON.parse(val)\n } catch {\n return val\n }\n }\n\n return val\n}\n\nexport const numberPreprocess = (val: unknown): unknown => {\n if (typeof val === 'string') {\n const number = Number(val)\n if (!Number.isNaN(number)) return number\n }\n return val\n}\n\n/**\n * Returns true if the two arrays contain the same elements, regardless of order\n * or duplicates.\n */\nexport function arrayEquivalent<T>(a: readonly T[], b: readonly T[]) {\n if (a === b) return true\n return a.every(includedIn, b) && b.every(includedIn, a)\n}\n\nexport function includedIn<T>(this: readonly T[], item: T) {\n return this.includes(item)\n}\n\nexport function asArray<T>(\n value: Iterable<T> | undefined,\n): undefined | readonly T[] {\n if (value == null) return undefined\n if (Array.isArray(value)) return value // already a (possibly readonly) array\n return Array.from(value)\n}\n\nexport type SpaceSeparatedValue<Value extends string> =\n `${'' | `${string} `}${Value}${'' | ` ${string}`}`\n\nexport const isSpaceSeparatedValue = <Value extends string>(\n value: Value,\n input: string,\n): input is SpaceSeparatedValue<Value> => {\n if (value.length === 0) throw new TypeError('Value cannot be empty')\n if (value.includes(' ')) throw new TypeError('Value cannot contain spaces')\n\n // Optimized version of:\n // return input.split(' ').includes(value)\n\n const inputLength = input.length\n const valueLength = value.length\n\n if (inputLength < valueLength) return false\n\n let idx = input.indexOf(value)\n let idxEnd: number\n\n while (idx !== -1) {\n idxEnd = idx + valueLength\n\n if (\n // at beginning or preceded by space\n (idx === 0 || input.charCodeAt(idx - 1) === 32) &&\n // at end or followed by space\n (idxEnd === inputLength || input.charCodeAt(idxEnd) === 32)\n ) {\n return true\n }\n\n idx = input.indexOf(value, idxEnd + 1)\n }\n\n return false\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atproto/oauth-types",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "description": "OAuth typing & validation library",
6
6
  "keywords": [
@@ -12,7 +12,7 @@ import {
12
12
  } from './oauth-client-id-loopback.js'
13
13
  import {
14
14
  OAuthLoopbackRedirectURI,
15
- oauthLoopbackRedirectURISchema,
15
+ oauthLoopbackClientRedirectUriSchema,
16
16
  } from './oauth-redirect-uri.js'
17
17
  import { arrayEquivalent, asArray } from './util.js'
18
18
 
@@ -41,7 +41,10 @@ export function buildAtprotoLoopbackClientId(
41
41
  throw new TypeError(`Unexpected empty "redirect_uris" config`)
42
42
  }
43
43
  for (const uri of redirectUris) {
44
- params.append('redirect_uri', oauthLoopbackRedirectURISchema.parse(uri))
44
+ params.append(
45
+ 'redirect_uri',
46
+ oauthLoopbackClientRedirectUriSchema.parse(uri),
47
+ )
45
48
  }
46
49
  }
47
50
 
@@ -67,10 +67,10 @@ export const oauthAuthorizationServerMetadataSchema = z.object({
67
67
  // https://datatracker.ietf.org/doc/html/rfc9449#section-5.1
68
68
  dpop_signing_alg_values_supported: z.array(z.string()).optional(),
69
69
 
70
- // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#section-4
70
+ // https://www.rfc-editor.org/rfc/rfc9728.html#section-4
71
71
  protected_resources: z.array(webUriSchema).optional(),
72
72
 
73
- // https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html
73
+ // https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html
74
74
  client_id_metadata_document_supported: z.boolean().optional(),
75
75
  })
76
76
 
@@ -4,7 +4,7 @@ import { httpsUriSchema } from './uri.js'
4
4
  import { extractUrlPath, isHostnameIP } from './util.js'
5
5
 
6
6
  /**
7
- * @see {@link https://drafts.aaronpk.com/draft-parecki-oauth-client-id-metadata-document/draft-parecki-oauth-client-id-metadata-document.html}
7
+ * @see {@link https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-00.html}
8
8
  */
9
9
  export const oauthClientIdDiscoverableSchema = z
10
10
  .intersection(oauthClientIdSchema, httpsUriSchema)
@@ -1,7 +1,7 @@
1
1
  import { oauthClientIdSchema } from './oauth-client-id.js'
2
2
  import {
3
3
  OAuthLoopbackRedirectURI,
4
- oauthLoopbackRedirectURISchema,
4
+ oauthLoopbackClientRedirectUriSchema,
5
5
  } from './oauth-redirect-uri.js'
6
6
  import { OAuthScope, oauthScopeSchema } from './oauth-scope.js'
7
7
 
@@ -138,7 +138,7 @@ export function safeParseOAuthLoopbackClientIdQueryString(
138
138
 
139
139
  params.scope = res.data
140
140
  } else if (key === 'redirect_uri') {
141
- const res = oauthLoopbackRedirectURISchema.safeParse(value)
141
+ const res = oauthLoopbackClientRedirectUriSchema.safeParse(value)
142
142
  if (!res.success) {
143
143
  const reason = res.error.issues.map((i) => i.message).join(', ')
144
144
  return {
@@ -3,7 +3,7 @@ import { oauthIssuerIdentifierSchema } from './oauth-issuer-identifier.js'
3
3
  import { webUriSchema } from './uri.js'
4
4
 
5
5
  /**
6
- * @see {@link https://datatracker.ietf.org/doc/html/draft-ietf-oauth-resource-metadata-05#name-protected-resource-metadata-r}
6
+ * @see {@link https://www.rfc-editor.org/rfc/rfc9728.html#section-3.2}
7
7
  */
8
8
  export const oauthProtectedResourceMetadataSchema = z.object({
9
9
  /**
@@ -6,19 +6,23 @@ import {
6
6
  privateUseUriSchema,
7
7
  } from './uri.js'
8
8
 
9
- export const oauthLoopbackRedirectURISchema = loopbackUriSchema.superRefine(
9
+ /**
10
+ * This is a {@link loopbackUriSchema} with the additional restriction that
11
+ * the hostname `localhost` is not allowed.
12
+ *
13
+ * @see {@link https://datatracker.ietf.org/doc/html/rfc8252#section-8.3 Loopback Redirect Considerations} RFC8252
14
+ *
15
+ * > While redirect URIs using localhost (i.e.,
16
+ * > "http://localhost:{port}/{path}") function similarly to loopback IP
17
+ * > redirects described in Section 7.3, the use of localhost is NOT
18
+ * > RECOMMENDED. Specifying a redirect URI with the loopback IP literal rather
19
+ * > than localhost avoids inadvertently listening on network interfaces other
20
+ * > than the loopback interface. It is also less susceptible to client-side
21
+ * > firewalls and misconfigured host name resolution on the user's device.
22
+ */
23
+ export const loopbackRedirectURISchema = loopbackUriSchema.superRefine(
10
24
  (value, ctx): value is Exclude<LoopbackUri, `http://localhost${string}`> => {
11
25
  if (value.startsWith('http://localhost')) {
12
- // https://datatracker.ietf.org/doc/html/rfc8252#section-8.3
13
- //
14
- // > While redirect URIs using localhost (i.e.,
15
- // > "http://localhost:{port}/{path}") function similarly to loopback IP
16
- // > redirects described in Section 7.3, the use of localhost is NOT
17
- // > RECOMMENDED. Specifying a redirect URI with the loopback IP literal
18
- // > rather than localhost avoids inadvertently listening on network
19
- // > interfaces other than the loopback interface. It is also less
20
- // > susceptible to client-side firewalls and misconfigured host name
21
- // > resolution on the user's device.
22
26
  ctx.addIssue({
23
27
  code: ZodIssueCode.custom,
24
28
  message:
@@ -30,27 +34,17 @@ export const oauthLoopbackRedirectURISchema = loopbackUriSchema.superRefine(
30
34
  return true
31
35
  },
32
36
  )
33
- export type OAuthLoopbackRedirectURI = TypeOf<
34
- typeof oauthLoopbackRedirectURISchema
35
- >
36
-
37
- export const oauthHttpsRedirectURISchema = httpsUriSchema
38
- export type OAuthHttpsRedirectURI = TypeOf<typeof oauthHttpsRedirectURISchema>
37
+ export type LoopbackRedirectURI = TypeOf<typeof loopbackRedirectURISchema>
39
38
 
40
- export const oauthPrivateUseRedirectURISchema = privateUseUriSchema
41
- export type OAuthPrivateUseRedirectURI = TypeOf<
42
- typeof oauthPrivateUseRedirectURISchema
39
+ export const oauthLoopbackClientRedirectUriSchema = loopbackRedirectURISchema
40
+ export type OAuthLoopbackRedirectURI = TypeOf<
41
+ typeof oauthLoopbackClientRedirectUriSchema
43
42
  >
44
43
 
45
44
  export const oauthRedirectUriSchema = z.union(
46
- [
47
- oauthLoopbackRedirectURISchema,
48
- oauthHttpsRedirectURISchema,
49
- oauthPrivateUseRedirectURISchema,
50
- ],
45
+ [loopbackRedirectURISchema, httpsUriSchema, privateUseUriSchema],
51
46
  {
52
47
  message: `URL must use the "https:" or "http:" protocol, or a private-use URI scheme (RFC 8252)`,
53
48
  },
54
49
  )
55
-
56
50
  export type OAuthRedirectUri = TypeOf<typeof oauthRedirectUriSchema>
package/src/uri.ts CHANGED
@@ -1,18 +1,10 @@
1
1
  import { TypeOf, ZodIssueCode, z } from 'zod'
2
- import { isHostnameIP, isLoopbackHost } from './util.js'
3
-
4
- const canParseUrl =
5
- // eslint-disable-next-line n/no-unsupported-features/node-builtins
6
- URL.canParse?.bind(URL) ??
7
- // URL.canParse is not available in Node.js < 18.7.0
8
- ((urlStr: string): boolean => {
9
- try {
10
- new URL(urlStr)
11
- return true
12
- } catch {
13
- return false
14
- }
15
- })
2
+ import {
3
+ canParseUrl,
4
+ isHostnameIP,
5
+ isLocalHostname,
6
+ isLoopbackHost,
7
+ } from './util.js'
16
8
 
17
9
  /**
18
10
  * Valid, but potentially dangerous URL (`data:`, `file:`, `javascript:`, etc.).
@@ -167,12 +159,56 @@ export const privateUseUriSchema = dangerousUriSchema.superRefine(
167
159
  return false
168
160
  }
169
161
 
170
- if (url.hostname) {
171
- // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
162
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
163
+ //
164
+ // > When choosing a URI scheme to associate with the app, apps MUST use a
165
+ // > URI scheme based on a domain name under their control, expressed in
166
+ // > reverse order
167
+ //
168
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-8.4
169
+ //
170
+ // > In addition to the collision-resistant properties, requiring a URI
171
+ // > scheme based on a domain name that is under the control of the app can
172
+ // > help to prove ownership in the event of a dispute where two apps claim
173
+ // > the same private-use URI scheme (where one app is acting maliciously).
174
+ //
175
+ // We can't check for ownership here (as there is no concept of proven
176
+ // ownership in a generic validation logic), besides excluding local domains
177
+ // as they can't be controlled/owned by the app.
178
+ //
179
+ // https://atproto.com/specs/oauth
180
+ //
181
+ // > Any custom scheme must match the `client_id` hostname in reverse-domain
182
+ // > order.
183
+ //
184
+ // This ATPROTO specific requirement cannot be enforced here, (as there is
185
+ // no concept of `client_id` in this context).
186
+
187
+ const uriScheme = url.protocol.slice(0, -1) // remove trailing ":"
188
+ const urlDomain = uriScheme.split('.').reverse().join('.')
189
+
190
+ if (isLocalHostname(urlDomain)) {
172
191
  ctx.addIssue({
173
192
  code: ZodIssueCode.custom,
174
- message:
175
- 'Private-use URI schemes must not include a hostname (only one "/" is allowed after the protocol, as per RFC 8252)',
193
+ message: `Private-use URI Scheme redirect URI must not be a local hostname`,
194
+ })
195
+ }
196
+
197
+ // https://datatracker.ietf.org/doc/html/rfc8252#section-7.1
198
+ //
199
+ // > Following the requirements of Section 3.2 of [RFC3986], as there is no
200
+ // > naming authority for private-use URI scheme redirects, only a single
201
+ // > slash ("/") appears after the scheme component.
202
+ if (
203
+ url.href.startsWith(`${url.protocol}//`) ||
204
+ url.username ||
205
+ url.password ||
206
+ url.hostname ||
207
+ url.port
208
+ ) {
209
+ ctx.addIssue({
210
+ code: ZodIssueCode.custom,
211
+ message: `Private-Use URI Scheme must be in the form ${url.protocol}/<path> (as per RFC 8252)`,
176
212
  })
177
213
  return false
178
214
  }
package/src/util.ts CHANGED
@@ -1,3 +1,16 @@
1
+ export const canParseUrl =
2
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
3
+ URL.canParse?.bind(URL) ??
4
+ // URL.canParse is not available in Node.js < 18.7.0
5
+ ((urlStr: string): boolean => {
6
+ try {
7
+ new URL(urlStr)
8
+ return true
9
+ } catch {
10
+ return false
11
+ }
12
+ })
13
+
1
14
  export function isHostnameIP(hostname: string) {
2
15
  // IPv4
3
16
  if (hostname.match(/^\d+\.\d+\.\d+\.\d+$/)) return true
@@ -14,9 +27,18 @@ export function isLoopbackHost(host: unknown): host is LoopbackHost {
14
27
  return host === 'localhost' || host === '127.0.0.1' || host === '[::1]'
15
28
  }
16
29
 
17
- export function isLoopbackUrl(input: URL | string): boolean {
18
- const url = typeof input === 'string' ? new URL(input) : input
19
- return isLoopbackHost(url.hostname)
30
+ export function isLocalHostname(hostname: string): boolean {
31
+ const parts = hostname.split('.')
32
+ if (parts.length < 2) return true
33
+
34
+ const tld = parts.at(-1)!.toLowerCase()
35
+ return (
36
+ tld === 'test' ||
37
+ tld === 'local' ||
38
+ tld === 'localhost' ||
39
+ tld === 'invalid' ||
40
+ tld === 'example'
41
+ )
20
42
  }
21
43
 
22
44
  export function safeUrl(input: URL | string): URL | null {