@noodleseed/one 0.128.0 → 0.129.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 (41) hide show
  1. package/node_modules/@noodle-borg/agent-kit/package.json +1 -1
  2. package/node_modules/@noodle-borg/assistant-gateway/package.json +1 -1
  3. package/node_modules/@noodle-borg/auth/dist/index.d.ts +1 -0
  4. package/node_modules/@noodle-borg/auth/dist/index.d.ts.map +1 -1
  5. package/node_modules/@noodle-borg/auth/dist/index.js +1 -0
  6. package/node_modules/@noodle-borg/auth/dist/index.js.map +1 -1
  7. package/node_modules/@noodle-borg/auth/dist/oauth-client-redirect-policy.d.ts +34 -0
  8. package/node_modules/@noodle-borg/auth/dist/oauth-client-redirect-policy.d.ts.map +1 -0
  9. package/node_modules/@noodle-borg/auth/dist/oauth-client-redirect-policy.js +230 -0
  10. package/node_modules/@noodle-borg/auth/dist/oauth-client-redirect-policy.js.map +1 -0
  11. package/node_modules/@noodle-borg/oauth-client-registry/dist/authorization-handler.d.ts +17 -0
  12. package/node_modules/@noodle-borg/oauth-client-registry/dist/authorization-handler.d.ts.map +1 -0
  13. package/node_modules/@noodle-borg/oauth-client-registry/dist/authorization-handler.js +118 -0
  14. package/node_modules/@noodle-borg/oauth-client-registry/dist/authorization-handler.js.map +1 -0
  15. package/node_modules/@noodle-borg/oauth-client-registry/dist/client-registration-handler.d.ts +18 -0
  16. package/node_modules/@noodle-borg/oauth-client-registry/dist/client-registration-handler.d.ts.map +1 -0
  17. package/node_modules/@noodle-borg/oauth-client-registry/dist/client-registration-handler.js +130 -0
  18. package/node_modules/@noodle-borg/oauth-client-registry/dist/client-registration-handler.js.map +1 -0
  19. package/node_modules/@noodle-borg/oauth-client-registry/dist/index.d.ts +5 -0
  20. package/node_modules/@noodle-borg/oauth-client-registry/dist/index.d.ts.map +1 -0
  21. package/node_modules/@noodle-borg/oauth-client-registry/dist/index.js +5 -0
  22. package/node_modules/@noodle-borg/oauth-client-registry/dist/index.js.map +1 -0
  23. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-inventory.d.ts +54 -0
  24. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-inventory.d.ts.map +1 -0
  25. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-inventory.js +256 -0
  26. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-inventory.js.map +1 -0
  27. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-redirect-rollout.d.ts +7 -0
  28. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-redirect-rollout.d.ts.map +1 -0
  29. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-redirect-rollout.js +6 -0
  30. package/node_modules/@noodle-borg/oauth-client-registry/dist/legacy-redirect-rollout.js.map +1 -0
  31. package/node_modules/@noodle-borg/oauth-client-registry/package.json +36 -0
  32. package/node_modules/@noodle-borg/service/dist/oauth/app.d.ts +2 -1
  33. package/node_modules/@noodle-borg/service/dist/oauth/app.d.ts.map +1 -1
  34. package/node_modules/@noodle-borg/service/dist/oauth/app.js +3 -21
  35. package/node_modules/@noodle-borg/service/dist/oauth/app.js.map +1 -1
  36. package/node_modules/@noodle-borg/service/dist/oauth/service-bootstrap.d.ts.map +1 -1
  37. package/node_modules/@noodle-borg/service/dist/oauth/service-bootstrap.js +1 -0
  38. package/node_modules/@noodle-borg/service/dist/oauth/service-bootstrap.js.map +1 -1
  39. package/node_modules/@noodle-borg/service/package.json +2 -1
  40. package/node_modules/@noodleseed/assistant/package.json +1 -1
  41. package/package.json +3 -2
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodle-borg/agent-kit",
3
- "version": "0.75.0",
3
+ "version": "0.76.0",
4
4
  "private": true,
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -28,7 +28,7 @@
28
28
  "@noodle-borg/module": "0.0.0",
29
29
  "@noodle-borg/protocol": "0.0.0",
30
30
  "@noodle-borg/runtime": "0.0.0",
31
- "@noodleseed/assistant": "1.19.0"
31
+ "@noodleseed/assistant": "1.20.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/node": "^24.13.1",
@@ -12,6 +12,7 @@ export { type DiscoveredIssuerMetadata, discoverIssuerMetadata, type IssuerMetad
12
12
  export { type AccessTokenClaims, mintAccessToken } from './jwt-issuer.js';
13
13
  export { type BearerChallenge, canonicalScopes, McpOAuthClient, type McpOAuthClientOptions, type McpOAuthClientRegistration, type McpOAuthDiscovery, type McpOAuthPendingAuthorization, type McpOAuthTokens, parseBearerChallenge, } from './mcp-oauth-client.js';
14
14
  export { type ProtectedResourceMetadata, protectedResourceMetadata, protectedResourceMetadataUrl, } from './metadata.js';
15
+ export { classifyStoredOAuthRedirectPolicy, matchOAuthAuthorizationRedirect, type NormalizedOAuthRedirectClientMetadata, normalizeOAuthClientMetadata, type OAuthApplicationType, type OAuthRedirectClientMetadata, type OAuthRedirectMatchResult, OAuthRedirectPolicyError, type OAuthRedirectPolicyErrorReason, type OAuthTokenEndpointAuthMethod, type StoredRedirectPolicyClass, } from './oauth-client-redirect-policy.js';
15
16
  export { createStaticSigningKeyProvider, type SigningKey, type SigningKeyProvider, type StaticSigningKeyOptions, } from './signer.js';
16
17
  export { createJwtVerifier, type JwtVerifierConfig, type TokenVerifier, type VerifiedIdentity, type VerifiedServicePrincipalBinding, type VerifiedTokenEnvelope, } from './verify.js';
17
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,KAAK,qBAAqB,EAC1B,oCAAoC,EACpC,8BAA8B,EAC9B,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,wBAAwB,EAC7B,sBAAsB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EACL,KAAK,eAAe,EACpB,eAAe,EACf,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACnB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,yBAAyB,EAC9B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,8BAA8B,EAC9B,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,KAAK,qBAAqB,EAC1B,oCAAoC,EACpC,8BAA8B,EAC9B,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,wBAAwB,EAC7B,sBAAsB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,KAAK,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EACL,KAAK,eAAe,EACpB,eAAe,EACf,cAAc,EACd,KAAK,qBAAqB,EAC1B,KAAK,0BAA0B,EAC/B,KAAK,iBAAiB,EACtB,KAAK,4BAA4B,EACjC,KAAK,cAAc,EACnB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,KAAK,yBAAyB,EAC9B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAC/B,KAAK,qCAAqC,EAC1C,4BAA4B,EAC5B,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,wBAAwB,EACxB,KAAK,8BAA8B,EACnC,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,GAC/B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,8BAA8B,EAC9B,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,+BAA+B,EACpC,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAC"}
@@ -12,6 +12,7 @@ export { discoverIssuerMetadata, issuerMetadataCandidates, } from './discovery.j
12
12
  export { mintAccessToken } from './jwt-issuer.js';
13
13
  export { canonicalScopes, McpOAuthClient, parseBearerChallenge, } from './mcp-oauth-client.js';
14
14
  export { protectedResourceMetadata, protectedResourceMetadataUrl, } from './metadata.js';
15
+ export { classifyStoredOAuthRedirectPolicy, matchOAuthAuthorizationRedirect, normalizeOAuthClientMetadata, OAuthRedirectPolicyError, } from './oauth-client-redirect-policy.js';
15
16
  export { createStaticSigningKeyProvider, } from './signer.js';
16
17
  export { createJwtVerifier, } from './verify.js';
17
18
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAEL,oCAAoC,EACpC,8BAA8B,EAC9B,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,sBAAsB,EAGtB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAA0B,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAEL,eAAe,EACf,cAAc,EAMd,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,8BAA8B,GAI/B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,GAMlB,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAEL,oCAAoC,EACpC,8BAA8B,EAC9B,eAAe,EACf,gBAAgB,EAChB,4BAA4B,EAC5B,+BAA+B,EAC/B,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,OAAO,EAEL,sBAAsB,EAGtB,wBAAwB,GACzB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAA0B,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAEL,eAAe,EACf,cAAc,EAMd,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAEL,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,iCAAiC,EACjC,+BAA+B,EAE/B,4BAA4B,EAI5B,wBAAwB,GAIzB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EACL,8BAA8B,GAI/B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,iBAAiB,GAMlB,MAAM,aAAa,CAAC"}
@@ -0,0 +1,34 @@
1
+ export type OAuthApplicationType = 'web' | 'native';
2
+ export type OAuthTokenEndpointAuthMethod = 'none' | 'client_secret_post';
3
+ export type StoredRedirectPolicyClass = 'normalized' | 'safe_https' | 'safe_loopback_legacy' | 'unsafe_legacy' | 'malformed_legacy';
4
+ export interface OAuthRedirectClientMetadata {
5
+ application_type?: unknown;
6
+ redirect_uris?: unknown;
7
+ token_endpoint_auth_method?: unknown;
8
+ noodle_redirect_policy_version?: unknown;
9
+ }
10
+ export interface NormalizedOAuthRedirectClientMetadata {
11
+ application_type: OAuthApplicationType;
12
+ redirect_uris: readonly string[];
13
+ token_endpoint_auth_method: OAuthTokenEndpointAuthMethod;
14
+ noodle_redirect_policy_version: 1;
15
+ }
16
+ export interface OAuthRedirectMatchResult {
17
+ allowed: boolean;
18
+ usedLegacyLoopbackPortSubstitution: boolean;
19
+ reason: 'exact_match' | 'native_loopback_port_match' | 'legacy_loopback_port_match' | 'unregistered_redirect' | 'unsafe_legacy_redirect' | 'malformed_client_metadata';
20
+ }
21
+ export type OAuthRedirectPolicyErrorReason = 'invalid_application_type' | 'invalid_redirect_uris' | 'unsafe_redirect_uri' | 'unsupported_token_endpoint_auth_method';
22
+ /** A closed registration-policy error suitable for mapping to OAuth invalid_client_metadata. */
23
+ export declare class OAuthRedirectPolicyError extends Error {
24
+ readonly reason: OAuthRedirectPolicyErrorReason;
25
+ constructor(reason: OAuthRedirectPolicyErrorReason);
26
+ }
27
+ export declare function normalizeOAuthClientMetadata(metadata: OAuthRedirectClientMetadata): NormalizedOAuthRedirectClientMetadata;
28
+ export declare function classifyStoredOAuthRedirectPolicy(metadata: OAuthRedirectClientMetadata): StoredRedirectPolicyClass;
29
+ export declare function matchOAuthAuthorizationRedirect(args: {
30
+ client: OAuthRedirectClientMetadata;
31
+ requestedRedirectUri?: string;
32
+ allowLegacyLoopbackPortSubstitution: boolean;
33
+ }): OAuthRedirectMatchResult;
34
+ //# sourceMappingURL=oauth-client-redirect-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client-redirect-policy.d.ts","sourceRoot":"","sources":["../src/oauth-client-redirect-policy.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,QAAQ,CAAC;AACpD,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,oBAAoB,CAAC;AAEzE,MAAM,MAAM,yBAAyB,GACjC,YAAY,GACZ,YAAY,GACZ,sBAAsB,GACtB,eAAe,GACf,kBAAkB,CAAC;AAEvB,MAAM,WAAW,2BAA2B;IAC1C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,8BAA8B,CAAC,EAAE,OAAO,CAAC;CAC1C;AAED,MAAM,WAAW,qCAAqC;IACpD,gBAAgB,EAAE,oBAAoB,CAAC;IACvC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,0BAA0B,EAAE,4BAA4B,CAAC;IACzD,8BAA8B,EAAE,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,kCAAkC,EAAE,OAAO,CAAC;IAC5C,MAAM,EACF,aAAa,GACb,4BAA4B,GAC5B,4BAA4B,GAC5B,uBAAuB,GACvB,wBAAwB,GACxB,2BAA2B,CAAC;CACjC;AAED,MAAM,MAAM,8BAA8B,GACtC,0BAA0B,GAC1B,uBAAuB,GACvB,qBAAqB,GACrB,wCAAwC,CAAC;AAE7C,gGAAgG;AAChG,qBAAa,wBAAyB,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,EAAE,8BAA8B;gBAAtC,MAAM,EAAE,8BAA8B;CAI5D;AAsBD,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,2BAA2B,GACpC,qCAAqC,CAwBvC;AAED,wBAAgB,iCAAiC,CAC/C,QAAQ,EAAE,2BAA2B,GACpC,yBAAyB,CAmB3B;AAED,wBAAgB,+BAA+B,CAAC,IAAI,EAAE;IACpD,MAAM,EAAE,2BAA2B,CAAC;IACpC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mCAAmC,EAAE,OAAO,CAAC;CAC9C,GAAG,wBAAwB,CAyC3B"}
@@ -0,0 +1,230 @@
1
+ /** A closed registration-policy error suitable for mapping to OAuth invalid_client_metadata. */
2
+ export class OAuthRedirectPolicyError extends Error {
3
+ reason;
4
+ constructor(reason) {
5
+ super(`OAuth client metadata rejected: ${reason}`);
6
+ this.reason = reason;
7
+ this.name = 'OAuthRedirectPolicyError';
8
+ }
9
+ }
10
+ const PERMITTED_LOOPBACK_HOSTS = new Set(['127.0.0.1', '[::1]', 'localhost']);
11
+ export function normalizeOAuthClientMetadata(metadata) {
12
+ const applicationType = applicationTypeForRegistration(metadata.application_type);
13
+ const tokenEndpointAuthMethod = tokenEndpointAuthMethodForRegistration(metadata.token_endpoint_auth_method);
14
+ const redirectUris = redirectUrisForRegistration(metadata.redirect_uris);
15
+ const parsedRedirects = redirectUris.map(parseRedirect);
16
+ if (parsedRedirects.some((redirect) => !isSafeRedirect(redirect))) {
17
+ throw new OAuthRedirectPolicyError('unsafe_redirect_uri');
18
+ }
19
+ if (metadata.application_type === 'web' &&
20
+ parsedRedirects.some((redirect) => redirect.kind !== 'parsed' || redirect.channel !== 'https')) {
21
+ throw new OAuthRedirectPolicyError('unsafe_redirect_uri');
22
+ }
23
+ return {
24
+ application_type: applicationType,
25
+ redirect_uris: redirectUris,
26
+ token_endpoint_auth_method: tokenEndpointAuthMethod,
27
+ noodle_redirect_policy_version: 1,
28
+ };
29
+ }
30
+ export function classifyStoredOAuthRedirectPolicy(metadata) {
31
+ if (hasExplicitApplicationType(metadata) && isNormalizedStoredRecord(metadata))
32
+ return 'normalized';
33
+ if (isMarkedOmittedTypeCompatibleRecord(metadata))
34
+ return 'normalized';
35
+ const redirects = redirectUrisForStoredRecord(metadata.redirect_uris);
36
+ if (redirects === undefined)
37
+ return 'malformed_legacy';
38
+ const parsedRedirects = redirects.map(parseRedirect);
39
+ if (parsedRedirects.some((redirect) => redirect.kind === 'malformed')) {
40
+ return 'malformed_legacy';
41
+ }
42
+ if (!hasExplicitApplicationType(metadata)) {
43
+ if (parsedRedirects.every((redirect) => isSafeHttpsRedirect(redirect)))
44
+ return 'safe_https';
45
+ if (parsedRedirects.every(isSafeRedirect) && parsedRedirects.some(isLoopbackHttpRedirect)) {
46
+ return 'safe_loopback_legacy';
47
+ }
48
+ }
49
+ return 'unsafe_legacy';
50
+ }
51
+ export function matchOAuthAuthorizationRedirect(args) {
52
+ const policyClass = classifyStoredOAuthRedirectPolicy(args.client);
53
+ if (policyClass === 'malformed_legacy')
54
+ return rejected('malformed_client_metadata');
55
+ const redirectUris = redirectUrisForStoredRecord(args.client.redirect_uris);
56
+ if (redirectUris === undefined)
57
+ return rejected('malformed_client_metadata');
58
+ if (args.requestedRedirectUri !== undefined && redirectUris.includes(args.requestedRedirectUri)) {
59
+ return allowed('exact_match');
60
+ }
61
+ if (policyClass === 'unsafe_legacy')
62
+ return rejected('unsafe_legacy_redirect');
63
+ if (args.requestedRedirectUri === undefined)
64
+ return rejected('unregistered_redirect');
65
+ const requested = parseRedirect(args.requestedRedirectUri);
66
+ const registered = redirectUris.map(parseRedirect);
67
+ if (policyClass === 'normalized') {
68
+ if (args.client.application_type === 'native' &&
69
+ registered.some((redirect) => loopbackRedirectsMatchIgnoringPort(redirect, requested))) {
70
+ return allowed('native_loopback_port_match');
71
+ }
72
+ return rejected('unregistered_redirect');
73
+ }
74
+ if (policyClass === 'safe_loopback_legacy' &&
75
+ args.allowLegacyLoopbackPortSubstitution &&
76
+ registered.some((redirect) => loopbackRedirectsMatchIgnoringPort(redirect, requested))) {
77
+ const matched = registered.find((redirect) => loopbackRedirectsMatchIgnoringPort(redirect, requested));
78
+ return {
79
+ allowed: true,
80
+ usedLegacyLoopbackPortSubstitution: matched?.kind === 'parsed' &&
81
+ requested.kind === 'parsed' &&
82
+ matched.port !== requested.port,
83
+ reason: 'legacy_loopback_port_match',
84
+ };
85
+ }
86
+ return rejected('unregistered_redirect');
87
+ }
88
+ function applicationTypeForRegistration(value) {
89
+ if (value === undefined)
90
+ return 'web';
91
+ if (value === 'web' || value === 'native')
92
+ return value;
93
+ throw new OAuthRedirectPolicyError('invalid_application_type');
94
+ }
95
+ function tokenEndpointAuthMethodForRegistration(value) {
96
+ if (value === undefined)
97
+ return 'client_secret_post';
98
+ if (value === 'none' || value === 'client_secret_post')
99
+ return value;
100
+ throw new OAuthRedirectPolicyError('unsupported_token_endpoint_auth_method');
101
+ }
102
+ function redirectUrisForRegistration(value) {
103
+ const redirects = redirectUrisForStoredRecord(value);
104
+ if (redirects === undefined)
105
+ throw new OAuthRedirectPolicyError('invalid_redirect_uris');
106
+ return redirects;
107
+ }
108
+ function redirectUrisForStoredRecord(value) {
109
+ if (!Array.isArray(value) ||
110
+ value.length === 0 ||
111
+ !value.every((redirect) => typeof redirect === 'string')) {
112
+ return undefined;
113
+ }
114
+ return value;
115
+ }
116
+ function parseRedirect(value) {
117
+ if (typeof value !== 'string')
118
+ return { kind: 'malformed' };
119
+ try {
120
+ const url = new URL(value);
121
+ const submittedHostname = submittedHostnameFromRedirect(value);
122
+ const effectiveHostname = url.hostname.toLowerCase();
123
+ const isLoopbackHttp = url.protocol === 'http:' &&
124
+ submittedHostname !== undefined &&
125
+ submittedHostname === effectiveHostname &&
126
+ PERMITTED_LOOPBACK_HOSTS.has(effectiveHostname);
127
+ return {
128
+ kind: 'parsed',
129
+ raw: value,
130
+ href: url.href,
131
+ protocol: url.protocol,
132
+ hostname: effectiveHostname,
133
+ port: url.port,
134
+ pathname: url.pathname,
135
+ search: url.search,
136
+ hasCredentials: url.username !== '' || url.password !== '',
137
+ hasFragment: value.includes('#'),
138
+ channel: url.protocol === 'https:' ? 'https' : isLoopbackHttp ? 'loopback_http' : 'unsupported',
139
+ };
140
+ }
141
+ catch {
142
+ return { kind: 'malformed' };
143
+ }
144
+ }
145
+ function hasExplicitApplicationType(metadata) {
146
+ return metadata.application_type !== undefined;
147
+ }
148
+ function isNormalizedStoredRecord(metadata) {
149
+ try {
150
+ const applicationType = applicationTypeForRegistration(metadata.application_type);
151
+ tokenEndpointAuthMethodForRegistration(metadata.token_endpoint_auth_method);
152
+ const redirects = redirectUrisForStoredRecord(metadata.redirect_uris);
153
+ if (redirects === undefined)
154
+ return false;
155
+ const parsedRedirects = redirects.map(parseRedirect);
156
+ return applicationType === 'web'
157
+ ? parsedRedirects.every(isSafeHttpsRedirect)
158
+ : parsedRedirects.every(isSafeRedirect);
159
+ }
160
+ catch {
161
+ return false;
162
+ }
163
+ }
164
+ function isMarkedOmittedTypeCompatibleRecord(metadata) {
165
+ if (metadata.noodle_redirect_policy_version !== 1 || metadata.application_type !== 'web') {
166
+ return false;
167
+ }
168
+ try {
169
+ tokenEndpointAuthMethodForRegistration(metadata.token_endpoint_auth_method);
170
+ const redirects = redirectUrisForStoredRecord(metadata.redirect_uris);
171
+ if (redirects === undefined)
172
+ return false;
173
+ const parsedRedirects = redirects.map(parseRedirect);
174
+ return parsedRedirects.every(isSafeRedirect) && parsedRedirects.some(isLoopbackHttpRedirect);
175
+ }
176
+ catch {
177
+ return false;
178
+ }
179
+ }
180
+ function submittedHostnameFromRedirect(value) {
181
+ const authorityStart = value.indexOf('://');
182
+ if (authorityStart === -1)
183
+ return undefined;
184
+ const afterScheme = value.slice(authorityStart + 3);
185
+ const authorityEnd = afterScheme.search(/[/?#]/);
186
+ const authority = authorityEnd === -1 ? afterScheme : afterScheme.slice(0, authorityEnd);
187
+ const hostAndPort = authority.slice(authority.lastIndexOf('@') + 1);
188
+ if (hostAndPort.startsWith('[')) {
189
+ const closingBracket = hostAndPort.indexOf(']');
190
+ return closingBracket === -1
191
+ ? undefined
192
+ : hostAndPort.slice(0, closingBracket + 1).toLowerCase();
193
+ }
194
+ return hostAndPort
195
+ .slice(0, hostAndPort.indexOf(':') === -1 ? undefined : hostAndPort.indexOf(':'))
196
+ .toLowerCase();
197
+ }
198
+ function isSafeRedirect(redirect) {
199
+ return (redirect.kind === 'parsed' &&
200
+ !redirect.hasCredentials &&
201
+ !redirect.hasFragment &&
202
+ (redirect.channel === 'https' || redirect.channel === 'loopback_http'));
203
+ }
204
+ function isSafeHttpsRedirect(redirect) {
205
+ return (redirect.kind === 'parsed' &&
206
+ !redirect.hasCredentials &&
207
+ !redirect.hasFragment &&
208
+ redirect.channel === 'https');
209
+ }
210
+ function isLoopbackHttpRedirect(redirect) {
211
+ return (redirect.kind === 'parsed' &&
212
+ !redirect.hasCredentials &&
213
+ !redirect.hasFragment &&
214
+ redirect.channel === 'loopback_http');
215
+ }
216
+ function loopbackRedirectsMatchIgnoringPort(left, right) {
217
+ return (isLoopbackHttpRedirect(left) &&
218
+ isLoopbackHttpRedirect(right) &&
219
+ left.protocol === right.protocol &&
220
+ left.hostname === right.hostname &&
221
+ left.pathname === right.pathname &&
222
+ left.search === right.search);
223
+ }
224
+ function allowed(reason) {
225
+ return { allowed: true, usedLegacyLoopbackPortSubstitution: false, reason };
226
+ }
227
+ function rejected(reason) {
228
+ return { allowed: false, usedLegacyLoopbackPortSubstitution: false, reason };
229
+ }
230
+ //# sourceMappingURL=oauth-client-redirect-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-client-redirect-policy.js","sourceRoot":"","sources":["../src/oauth-client-redirect-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { OAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/provider.js';
2
+ import { type RequestHandler } from 'express';
3
+ import { type Options as RateLimitOptions } from 'express-rate-limit';
4
+ import { type OAuthLegacyRedirectRollout } from './legacy-redirect-rollout.js';
5
+ export type AuthorizationRedirectScalarFields = Readonly<Record<string, string | number | boolean>>;
6
+ export interface AuthorizationRedirectLogger {
7
+ info(event: string, scalarFields: AuthorizationRedirectScalarFields): void;
8
+ }
9
+ export interface SafeAuthorizationHandlerOptions {
10
+ readonly provider: OAuthServerProvider;
11
+ readonly logger?: AuthorizationRedirectLogger | undefined;
12
+ readonly rollout?: OAuthLegacyRedirectRollout;
13
+ readonly rateLimit?: Partial<RateLimitOptions> | false;
14
+ }
15
+ /** Guard authorization redirects with Noodle policy before the SDK can redirect to a client callback. */
16
+ export declare function safeAuthorizationHandler({ provider, logger, rollout, rateLimit: rateLimitConfig, }: SafeAuthorizationHandlerOptions): RequestHandler;
17
+ //# sourceMappingURL=authorization-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization-handler.d.ts","sourceRoot":"","sources":["../src/authorization-handler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mDAAmD,CAAC;AAO7F,OAAgB,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACvD,OAAO,EAAE,KAAK,OAAO,IAAI,gBAAgB,EAAa,MAAM,oBAAoB,CAAC;AACjF,OAAO,EACL,KAAK,0BAA0B,EAEhC,MAAM,8BAA8B,CAAC;AAOtC,MAAM,MAAM,iCAAiC,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;AAEpG,MAAM,WAAW,2BAA2B;IAC1C,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,iCAAiC,GAAG,IAAI,CAAC;CAC5E;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,2BAA2B,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,OAAO,CAAC,EAAE,0BAA0B,CAAC;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;CACxD;AAED,yGAAyG;AACzG,wBAAgB,wBAAwB,CAAC,EACvC,QAAQ,EACR,MAAoB,EACpB,OAAqC,EACrC,SAAS,EAAE,eAAe,GAC3B,EAAE,+BAA+B,GAAG,cAAc,CAqBlD"}
@@ -0,0 +1,118 @@
1
+ import { InvalidRequestError, ServerError, TooManyRequestsError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
2
+ import { authorizationHandler } from '@modelcontextprotocol/sdk/server/auth/handlers/authorize.js';
3
+ import { allowedMethods } from '@modelcontextprotocol/sdk/server/auth/middleware/allowedMethods.js';
4
+ import { classifyStoredOAuthRedirectPolicy, matchOAuthAuthorizationRedirect, } from '@noodle-borg/auth';
5
+ import express, {} from 'express';
6
+ import { rateLimit } from 'express-rate-limit';
7
+ import { stageALegacyRedirectRollout, } from './legacy-redirect-rollout.js';
8
+ const POLICY_ERROR_DESCRIPTION = 'redirect_uri is not permitted for this client';
9
+ const LEGACY_OBSERVATION_EVENT = 'oauth.authorization_redirect.legacy_observed';
10
+ /** Guard authorization redirects with Noodle policy before the SDK can redirect to a client callback. */
11
+ export function safeAuthorizationHandler({ provider, logger = NOOP_LOGGER, rollout = stageALegacyRedirectRollout, rateLimit: rateLimitConfig, }) {
12
+ const router = express.Router();
13
+ router.use(allowedMethods(['GET', 'POST']));
14
+ router.use(express.urlencoded({ extended: false }));
15
+ if (rateLimitConfig !== false) {
16
+ router.use(rateLimit({
17
+ windowMs: 15 * 60 * 1000,
18
+ max: 100,
19
+ standardHeaders: true,
20
+ legacyHeaders: false,
21
+ message: new TooManyRequestsError('You have exceeded the rate limit for authorization requests').toResponseObject(),
22
+ ...rateLimitConfig,
23
+ }));
24
+ }
25
+ router.use(authorizationRedirectGuard(provider, logger, rollout));
26
+ router.use(authorizationHandler({ provider, rateLimit: false }));
27
+ return router;
28
+ }
29
+ function authorizationRedirectGuard(provider, logger, rollout) {
30
+ return async (req, res, next) => {
31
+ const phaseOne = phaseOneInput(req.method === 'POST' ? req.body : req.query);
32
+ if (phaseOne === undefined) {
33
+ next();
34
+ return;
35
+ }
36
+ let client;
37
+ try {
38
+ client = await provider.clientsStore.getClient(phaseOne.clientId);
39
+ }
40
+ catch {
41
+ rejectStoreFailure(res);
42
+ return;
43
+ }
44
+ if (client === undefined) {
45
+ next();
46
+ return;
47
+ }
48
+ const effectiveRedirectUri = phaseOne.redirectUri ?? soleRegisteredRedirectUri(client.redirect_uris);
49
+ if (effectiveRedirectUri === undefined) {
50
+ next();
51
+ return;
52
+ }
53
+ const policyClass = classifyStoredOAuthRedirectPolicy(client);
54
+ const match = matchOAuthAuthorizationRedirect({
55
+ client,
56
+ requestedRedirectUri: effectiveRedirectUri,
57
+ allowLegacyLoopbackPortSubstitution: rollout.loopbackPortMode === 'observe',
58
+ });
59
+ if (policyClass === 'unsafe_legacy' || policyClass === 'malformed_legacy') {
60
+ observeLegacy(logger, policyClass, match);
61
+ if (rollout.unsafeLegacyMode === 'observe') {
62
+ next();
63
+ return;
64
+ }
65
+ rejectRedirect(res);
66
+ return;
67
+ }
68
+ if (policyClass === 'safe_loopback_legacy' && match.usedLegacyLoopbackPortSubstitution) {
69
+ observeLegacy(logger, policyClass, match);
70
+ }
71
+ if (!match.allowed) {
72
+ rejectRedirect(res);
73
+ return;
74
+ }
75
+ next();
76
+ };
77
+ }
78
+ function soleRegisteredRedirectUri(value) {
79
+ if (!Array.isArray(value) || value.length !== 1)
80
+ return undefined;
81
+ const [redirectUri] = value;
82
+ return typeof redirectUri === 'string' && URL.canParse(redirectUri) ? redirectUri : undefined;
83
+ }
84
+ function phaseOneInput(value) {
85
+ if (typeof value !== 'object' || value === null)
86
+ return undefined;
87
+ const input = value;
88
+ if (typeof input.client_id !== 'string')
89
+ return undefined;
90
+ if (input.redirect_uri === undefined)
91
+ return { clientId: input.client_id };
92
+ if (typeof input.redirect_uri !== 'string' || !URL.canParse(input.redirect_uri))
93
+ return undefined;
94
+ return { clientId: input.client_id, redirectUri: input.redirect_uri };
95
+ }
96
+ function observeLegacy(logger, policyClass, match) {
97
+ try {
98
+ logger.info(LEGACY_OBSERVATION_EVENT, {
99
+ policyClass,
100
+ reason: match.reason,
101
+ allowed: match.allowed,
102
+ usedLegacyLoopbackPortSubstitution: match.usedLegacyLoopbackPortSubstitution,
103
+ });
104
+ }
105
+ catch {
106
+ // Observation telemetry is non-authoritative and must not change authorization behavior.
107
+ }
108
+ }
109
+ function rejectRedirect(res) {
110
+ res.setHeader('Cache-Control', 'no-store');
111
+ res.status(400).json(new InvalidRequestError(POLICY_ERROR_DESCRIPTION).toResponseObject());
112
+ }
113
+ function rejectStoreFailure(res) {
114
+ res.setHeader('Cache-Control', 'no-store');
115
+ res.status(500).json(new ServerError('Internal Server Error').toResponseObject());
116
+ }
117
+ const NOOP_LOGGER = { info: () => undefined };
118
+ //# sourceMappingURL=authorization-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"authorization-handler.js","sourceRoot":"","sources":["../src/authorization-handler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { OAuthRegisteredClientsStore } from '@modelcontextprotocol/sdk/server/auth/clients.js';
2
+ import { type OAuthClientInformationFull } from '@modelcontextprotocol/sdk/shared/auth.js';
3
+ import { type NormalizedOAuthRedirectClientMetadata } from '@noodle-borg/auth';
4
+ import { type RequestHandler } from 'express';
5
+ import { type Options as RateLimitOptions } from 'express-rate-limit';
6
+ export type RegisteredOAuthClient = OAuthClientInformationFull & Omit<NormalizedOAuthRedirectClientMetadata, 'redirect_uris'> & {
7
+ readonly redirect_uris: string[];
8
+ };
9
+ export interface SafeClientRegistrationHandlerOptions {
10
+ readonly clientsStore: OAuthRegisteredClientsStore;
11
+ readonly clientSecretExpirySeconds?: number;
12
+ readonly rateLimit?: Partial<RateLimitOptions> | false;
13
+ }
14
+ /**
15
+ * Preserve the MCP SDK's DCR transport while retaining Noodle's normalized redirect-policy metadata.
16
+ */
17
+ export declare function safeClientRegistrationHandler({ clientsStore, clientSecretExpirySeconds, rateLimit: rateLimitConfig, }: SafeClientRegistrationHandlerOptions): RequestHandler;
18
+ //# sourceMappingURL=client-registration-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-registration-handler.d.ts","sourceRoot":"","sources":["../src/client-registration-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kDAAkD,CAAC;AAQpG,OAAO,EACL,KAAK,0BAA0B,EAGhC,MAAM,0CAA0C,CAAC;AAClD,OAAO,EACL,KAAK,qCAAqC,EAI3C,MAAM,mBAAmB,CAAC;AAE3B,OAAgB,EAA4B,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC;AACjF,OAAO,EAAE,KAAK,OAAO,IAAI,gBAAgB,EAAa,MAAM,oBAAoB,CAAC;AAIjF,MAAM,MAAM,qBAAqB,GAAG,0BAA0B,GAC5D,IAAI,CAAC,qCAAqC,EAAE,eAAe,CAAC,GAAG;IAC7D,QAAQ,CAAC,aAAa,EAAE,MAAM,EAAE,CAAC;CAClC,CAAC;AAEJ,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,YAAY,EAAE,2BAA2B,CAAC;IACnD,QAAQ,CAAC,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAC5C,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,KAAK,CAAC;CACxD;AAED;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,EAC5C,YAAY,EACZ,yBAAgE,EAChE,SAAS,EAAE,eAAe,GAC3B,EAAE,oCAAoC,GAAG,cAAc,CAuEvD"}
@@ -0,0 +1,130 @@
1
+ import crypto from 'node:crypto';
2
+ import { InvalidClientMetadataError, OAuthError, ServerError, TooManyRequestsError, } from '@modelcontextprotocol/sdk/server/auth/errors.js';
3
+ import { allowedMethods } from '@modelcontextprotocol/sdk/server/auth/middleware/allowedMethods.js';
4
+ import { OAuthClientInformationFullSchema, OAuthClientMetadataSchema, } from '@modelcontextprotocol/sdk/shared/auth.js';
5
+ import { normalizeOAuthClientMetadata, OAuthRedirectPolicyError, } from '@noodle-borg/auth';
6
+ import cors from 'cors';
7
+ import express, {} from 'express';
8
+ import { rateLimit } from 'express-rate-limit';
9
+ const DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS = 30 * 24 * 60 * 60;
10
+ /**
11
+ * Preserve the MCP SDK's DCR transport while retaining Noodle's normalized redirect-policy metadata.
12
+ */
13
+ export function safeClientRegistrationHandler({ clientsStore, clientSecretExpirySeconds = DEFAULT_CLIENT_SECRET_EXPIRY_SECONDS, rateLimit: rateLimitConfig, }) {
14
+ if (!clientsStore.registerClient) {
15
+ throw new Error('Client registration store does not support registering clients');
16
+ }
17
+ const registerClient = clientsStore.registerClient.bind(clientsStore);
18
+ const router = express.Router();
19
+ router.use(cors());
20
+ router.use(allowedMethods(['POST']));
21
+ router.use(express.json());
22
+ router.use(invalidJsonHandler);
23
+ if (rateLimitConfig !== false) {
24
+ router.use(rateLimit({
25
+ windowMs: 60 * 60 * 1000,
26
+ max: 20,
27
+ standardHeaders: true,
28
+ legacyHeaders: false,
29
+ message: new TooManyRequestsError('You have exceeded the rate limit for client registration requests').toResponseObject(),
30
+ ...rateLimitConfig,
31
+ }));
32
+ }
33
+ router.post('/', async (req, res) => {
34
+ res.setHeader('Cache-Control', 'no-store');
35
+ try {
36
+ const parseResult = OAuthClientMetadataSchema.safeParse(req.body);
37
+ if (!parseResult.success) {
38
+ throw new InvalidClientMetadataError(parseResult.error.message);
39
+ }
40
+ const submitted = requestObject(req.body);
41
+ const normalized = normalizeOAuthClientMetadata({
42
+ application_type: submitted.application_type,
43
+ redirect_uris: parseResult.data.redirect_uris,
44
+ token_endpoint_auth_method: submitted.token_endpoint_auth_method,
45
+ noodle_redirect_policy_version: submitted.noodle_redirect_policy_version,
46
+ });
47
+ const persistedPolicy = { ...normalized, redirect_uris: [...normalized.redirect_uris] };
48
+ const issuedAt = Math.floor(Date.now() / 1000);
49
+ const hasSecret = normalized.token_endpoint_auth_method === 'client_secret_post';
50
+ const credentials = hasSecret
51
+ ? {
52
+ client_secret: crypto.randomBytes(32).toString('hex'),
53
+ client_secret_expires_at: clientSecretExpirySeconds > 0 ? issuedAt + clientSecretExpirySeconds : 0,
54
+ }
55
+ : {};
56
+ const client = {
57
+ ...parseResult.data,
58
+ ...persistedPolicy,
59
+ ...credentials,
60
+ client_id: crypto.randomUUID(),
61
+ client_id_issued_at: issuedAt,
62
+ };
63
+ const stored = await registerClient(client);
64
+ res.status(201).json(validatedStoredClient(stored, persistedPolicy));
65
+ }
66
+ catch (error) {
67
+ const oauthError = registrationError(error);
68
+ const status = oauthError instanceof ServerError ? 500 : 400;
69
+ res.status(status).json(oauthError.toResponseObject());
70
+ }
71
+ });
72
+ return router;
73
+ }
74
+ const invalidJsonHandler = (error, _req, res, next) => {
75
+ if (!isInvalidJson(error)) {
76
+ next(error);
77
+ return;
78
+ }
79
+ res.setHeader('Cache-Control', 'no-store');
80
+ res
81
+ .status(400)
82
+ .json(new InvalidClientMetadataError('client metadata must be valid JSON').toResponseObject());
83
+ };
84
+ function isInvalidJson(error) {
85
+ if (!(error instanceof SyntaxError))
86
+ return false;
87
+ const parseError = error;
88
+ return parseError.status === 400 && parseError.type === 'entity.parse.failed';
89
+ }
90
+ function requestObject(value) {
91
+ return typeof value === 'object' && value !== null ? value : {};
92
+ }
93
+ function validatedStoredClient(value, normalized) {
94
+ const sdkRecord = OAuthClientInformationFullSchema.safeParse(value);
95
+ const stored = requestObject(value);
96
+ if (!sdkRecord.success ||
97
+ stored.application_type !== normalized.application_type ||
98
+ stored.token_endpoint_auth_method !== normalized.token_endpoint_auth_method ||
99
+ stored.noodle_redirect_policy_version !== normalized.noodle_redirect_policy_version ||
100
+ !sameRedirectUris(stored.redirect_uris, normalized.redirect_uris)) {
101
+ throw new ServerError('Internal Server Error');
102
+ }
103
+ return value;
104
+ }
105
+ function sameRedirectUris(actual, expected) {
106
+ return (Array.isArray(actual) &&
107
+ actual.length === expected.length &&
108
+ actual.every((value, index) => value === expected[index]));
109
+ }
110
+ function registrationError(error) {
111
+ if (error instanceof OAuthRedirectPolicyError) {
112
+ return new InvalidClientMetadataError(policyErrorDescription(error.reason));
113
+ }
114
+ if (error instanceof OAuthError)
115
+ return error;
116
+ return new ServerError('Internal Server Error');
117
+ }
118
+ function policyErrorDescription(reason) {
119
+ switch (reason) {
120
+ case 'invalid_application_type':
121
+ return 'application_type must be web or native';
122
+ case 'invalid_redirect_uris':
123
+ return 'redirect_uris must be a non-empty list of URI strings';
124
+ case 'unsafe_redirect_uri':
125
+ return 'redirect_uris must use HTTPS or a permitted loopback HTTP redirect without credentials or fragments';
126
+ case 'unsupported_token_endpoint_auth_method':
127
+ return 'token_endpoint_auth_method must be none or client_secret_post';
128
+ }
129
+ }
130
+ //# sourceMappingURL=client-registration-handler.js.map