@cloudflare/workers-oauth-provider 0.10.0 → 0.10.1
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.
- package/README.md +8 -9
- package/dist/oauth-provider.d.ts +14 -11
- package/dist/oauth-provider.js +88 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -321,6 +321,10 @@ MCP 2026-07-28 deprecates DCR for new implementations in favor of CIMD. The endp
|
|
|
321
321
|
|
|
322
322
|
Registration accepts only authentication methods, grants, and response types implemented by the configured provider, and rejects inconsistent grant/response combinations before storage. Omitted metadata uses the RFC 7591 defaults: `client_secret_basic`, `grant_types: ["authorization_code"]`, and `response_types: ["code"]`.
|
|
323
323
|
|
|
324
|
+
An explicitly supplied `token_endpoint_auth_method` is enforced exactly. When it is omitted, no explicit-method marker is stored and the client may use either `client_secret_basic` or `client_secret_post`, provided the same stored secret validates. Client records written by earlier releases have no marker and receive the same compatibility. This never crosses between `none` and a secret method and does not apply to CIMD clients.
|
|
325
|
+
|
|
326
|
+
Calling `OAuthHelpers.updateClient()` with `tokenEndpointAuthMethod` adds the marker; unrelated updates leave it unchanged.
|
|
327
|
+
|
|
324
328
|
Related options:
|
|
325
329
|
|
|
326
330
|
- `clientRegistrationTTL` controls the lifetime of dynamically registered clients. The default is 90 days.
|
|
@@ -345,16 +349,11 @@ The provider owns `tokenEndpoint`. It exchanges authorization codes for tokens,
|
|
|
345
349
|
|
|
346
350
|
## Resources and token audiences
|
|
347
351
|
|
|
348
|
-
MCP clients
|
|
349
|
-
|
|
350
|
-
Resource policy follows `resourceMetadata.resource`:
|
|
351
|
-
|
|
352
|
-
- When configured, authorization requests, token requests, and externally resolved tokens must use that one exact resource. `resourceMatchOriginOnly` cannot weaken this policy.
|
|
353
|
-
- When omitted, valid resources are accepted. Token requests may inherit the authorization resource. If the authorization request also omits it, the provider uses the request origin as the default and issues an origin-bound token.
|
|
352
|
+
MCP clients are required to send the canonical MCP server URI as `resource` in authorization and token requests. The provider tolerates omission for compatibility: when `resourceMetadata.resource` is configured, it is used as the canonical default and inherited by later token requests; otherwise a token request inherits any resource already stored on the grant. An explicit resource that does not match a bound grant is rejected with `invalid_target`.
|
|
354
353
|
|
|
355
|
-
Path-aware audiences use path-boundary prefix matching
|
|
354
|
+
Legacy grants may have no stored resource. With no configured canonical resource, omitting `resource` preserves that unbound state. If a client supplies a resource during code exchange or refresh, it applies to that issued token but is not persisted as a new grant binding. Path-aware audiences use path-boundary prefix matching, so a token for `https://example.com/mcp` can be used at `/mcp/tools`, but not at `/mcp-other`.
|
|
356
355
|
|
|
357
|
-
`resourceMatchOriginOnly`
|
|
356
|
+
`resourceMatchOriginOnly` is deprecated; its existing behavior is unchanged. Prefer `resourceMetadata.resource` for new deployments.
|
|
358
357
|
|
|
359
358
|
## Scopes and step-up authorization
|
|
360
359
|
|
|
@@ -431,7 +430,7 @@ Deleting a client through `OAuthHelpers.deleteClient()` also revokes its grants
|
|
|
431
430
|
| `allowTokenExchangeGrant` | Enable RFC 8693 | `false` |
|
|
432
431
|
| `tokenExchangeCallback` | Update props, scopes, or lifetimes during token exchange | None |
|
|
433
432
|
| `resolveExternalToken` | Validate external bearer credentials (advanced) | None |
|
|
434
|
-
| `resourceMatchOriginOnly` |
|
|
433
|
+
| `resourceMatchOriginOnly` | Deprecated origin-only resource comparison | `false` |
|
|
435
434
|
| `enterpriseManagedAuthorization` | Enable experimental ID-JAG grant support | Disabled |
|
|
436
435
|
| `onError` | Observe or replace OAuth error responses | Logs a warning |
|
|
437
436
|
|
package/dist/oauth-provider.d.ts
CHANGED
|
@@ -299,7 +299,7 @@ interface EmaOptions<Env = Cloudflare.Env> {
|
|
|
299
299
|
}
|
|
300
300
|
//#endregion
|
|
301
301
|
//#region src/oauth-capabilities.d.ts
|
|
302
|
-
type AuthorizationErrorCode = 'invalid_request' | 'unauthorized_client' | 'access_denied' | 'unsupported_response_type' | 'invalid_scope' | 'server_error' | 'temporarily_unavailable';
|
|
302
|
+
type AuthorizationErrorCode = 'invalid_request' | 'invalid_target' | 'unauthorized_client' | 'access_denied' | 'unsupported_response_type' | 'invalid_scope' | 'server_error' | 'temporarily_unavailable';
|
|
303
303
|
interface AuthorizationErrorOptions {
|
|
304
304
|
/** Wire-safe OAuth authorization error description. */
|
|
305
305
|
description: string;
|
|
@@ -685,14 +685,16 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
|
|
|
685
685
|
*/
|
|
686
686
|
clientIdMetadataDocumentEnabled?: boolean;
|
|
687
687
|
/**
|
|
688
|
-
* When true, resource validation
|
|
689
|
-
* (scheme + host + port) instead of exact
|
|
690
|
-
*
|
|
691
|
-
* path
|
|
692
|
-
*
|
|
693
|
-
* Explicit `resourceMetadata.resource` configuration always uses exact matching.
|
|
688
|
+
* When true, requested-vs-granted resource validation compares origins only
|
|
689
|
+
* (scheme + host + port) instead of exact URIs. This allows an origin-only
|
|
690
|
+
* grant such as `https://server.com` to accept `https://server.com/mcp`, but
|
|
691
|
+
* also ignores path and query differences. Configured canonical resources
|
|
692
|
+
* always use exact matching.
|
|
694
693
|
*
|
|
695
694
|
* Defaults to false (strict exact matching per RFC 8707).
|
|
695
|
+
*
|
|
696
|
+
* @deprecated This comparison is unsafe for shared-origin multi-path or
|
|
697
|
+
* multi-tenant deployments. Prefer configuring `resourceMetadata.resource`.
|
|
696
698
|
*/
|
|
697
699
|
resourceMatchOriginOnly?: boolean;
|
|
698
700
|
/**
|
|
@@ -706,10 +708,11 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
|
|
|
706
708
|
/**
|
|
707
709
|
* The protected resource identifier URL (RFC 9728 `resource` field).
|
|
708
710
|
*
|
|
709
|
-
* Configuring this value pins
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
711
|
+
* Configuring this value pins grants and access-token audiences to this
|
|
712
|
+
* exact resource. An omitted authorization resource defaults to this value,
|
|
713
|
+
* and an omitted token-request resource inherits it from the grant. Without
|
|
714
|
+
* configuration, explicit RFC 8707 resource indicators are accepted and
|
|
715
|
+
* omission remains unbound for backwards compatibility.
|
|
713
716
|
*/
|
|
714
717
|
resource?: string;
|
|
715
718
|
/**
|
package/dist/oauth-provider.js
CHANGED
|
@@ -53,6 +53,25 @@ function validateClientCapabilities(server, client) {
|
|
|
53
53
|
if (client.grantTypes.includes("authorization_code") !== client.responseTypes.includes("code")) throw new Error("grant_types authorization_code and response_types code must be registered together");
|
|
54
54
|
if (client.grantTypes.includes("implicit") !== client.responseTypes.includes("token")) throw new Error("grant_types implicit and response_types token must be registered together");
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Selects the capabilities from a Client ID Metadata Document that this
|
|
58
|
+
* authorization server supports. CIMD documents may advertise extension
|
|
59
|
+
* capabilities alongside the flow used with this server, so unsupported grant
|
|
60
|
+
* and response types are omitted from the effective client metadata instead of
|
|
61
|
+
* invalidating an otherwise usable client.
|
|
62
|
+
*
|
|
63
|
+
* The effective subset is still checked for grant/response consistency, and
|
|
64
|
+
* token endpoint authentication must have a mutually supported method.
|
|
65
|
+
*/
|
|
66
|
+
function negotiateCimdClientCapabilities(server, client) {
|
|
67
|
+
const effective = {
|
|
68
|
+
grantTypes: client.grantTypes.filter((grantType) => server.grantTypes.includes(grantType)),
|
|
69
|
+
responseTypes: client.responseTypes.filter((responseType) => server.responseTypes.includes(responseType)),
|
|
70
|
+
tokenEndpointAuthMethod: client.tokenEndpointAuthMethod
|
|
71
|
+
};
|
|
72
|
+
validateClientCapabilities(server, effective);
|
|
73
|
+
return effective;
|
|
74
|
+
}
|
|
56
75
|
function validateAuthorizationResponseType(server, responseType, clientResponseTypes) {
|
|
57
76
|
if (!responseType) throw new AuthorizationError("invalid_request", { description: "response_type is required" });
|
|
58
77
|
if (!server.responseTypes.includes(responseType)) throw new AuthorizationError("unsupported_response_type", { description: `The authorization server does not support response_type ${responseType}` });
|
|
@@ -794,6 +813,15 @@ let GrantType = /* @__PURE__ */ function(GrantType$1) {
|
|
|
794
813
|
GrantType$1["JWT_BEARER"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
|
|
795
814
|
return GrantType$1;
|
|
796
815
|
}({});
|
|
816
|
+
function toPublicClientInfo(client) {
|
|
817
|
+
const { authMethodExplicit: _explicit, ...publicClient } = client;
|
|
818
|
+
return publicClient;
|
|
819
|
+
}
|
|
820
|
+
function isClientAuthMethodAllowed(client, presentedMethod, isClientMetadataDocument) {
|
|
821
|
+
if (presentedMethod === client.tokenEndpointAuthMethod) return true;
|
|
822
|
+
const isSecretMethod = (method) => method === "client_secret_basic" || method === "client_secret_post";
|
|
823
|
+
return !isClientMetadataDocument && client.authMethodExplicit === void 0 && isSecretMethod(client.tokenEndpointAuthMethod) && isSecretMethod(presentedMethod);
|
|
824
|
+
}
|
|
797
825
|
/**
|
|
798
826
|
* OAuth 2.0 Provider implementation for Cloudflare Workers
|
|
799
827
|
* Implements authorization code flow with support for refresh tokens
|
|
@@ -935,7 +963,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
935
963
|
/** Validate configured RFC 9728 protected resource metadata. */
|
|
936
964
|
validateResourceMetadataOptions(options) {
|
|
937
965
|
if (!options) return;
|
|
938
|
-
if (options.resource && !validateResourceUri(options.resource)) throw new TypeError("resourceMetadata.resource must be an absolute HTTP(S) URI without a fragment");
|
|
966
|
+
if (options.resource !== void 0 && !validateResourceUri(options.resource)) throw new TypeError("resourceMetadata.resource must be an absolute HTTP(S) URI without a fragment");
|
|
939
967
|
if (options.authorization_servers !== void 0) {
|
|
940
968
|
if (options.authorization_servers.length === 0) throw new TypeError("resourceMetadata.authorization_servers must contain at least one issuer");
|
|
941
969
|
for (const issuer of options.authorization_servers) {
|
|
@@ -1179,7 +1207,16 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
1179
1207
|
}
|
|
1180
1208
|
if (!clientInfo) return this.createInvalidClientResponse("Client not found", basicAuthenticationAttempted);
|
|
1181
1209
|
const presentedAuthMethod = basicAuthenticationAttempted ? "client_secret_basic" : formData.has("client_secret") ? "client_secret_post" : "none";
|
|
1182
|
-
|
|
1210
|
+
const registeredAuthMethod = clientInfo.tokenEndpointAuthMethod;
|
|
1211
|
+
if (!isClientAuthMethodAllowed(clientInfo, presentedAuthMethod, !!this.options.clientIdMetadataDocumentEnabled && this.isClientMetadataUrl(clientInfo.clientId))) return this.createInvalidClientResponse("Client authentication failed", basicAuthenticationAttempted, {
|
|
1212
|
+
category: "client-authentication",
|
|
1213
|
+
reason: "token_endpoint_auth_method_mismatch",
|
|
1214
|
+
detail: {
|
|
1215
|
+
clientId: clientInfo.clientId,
|
|
1216
|
+
registeredMethod: registeredAuthMethod,
|
|
1217
|
+
presentedMethod: presentedAuthMethod
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1183
1220
|
if (presentedAuthMethod !== "none") {
|
|
1184
1221
|
if (!clientSecret) return this.createInvalidClientResponse("Client authentication failed: missing client_secret", basicAuthenticationAttempted);
|
|
1185
1222
|
if (!clientInfo.clientSecret) return this.createInvalidClientResponse("Client authentication failed: client has no registered secret", basicAuthenticationAttempted);
|
|
@@ -1671,7 +1708,7 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
1671
1708
|
let tokenScopes = this.downscope(requestedScopes, tokenSummary.scope);
|
|
1672
1709
|
const configuredResource = this.options.resourceMetadata?.resource;
|
|
1673
1710
|
if (configuredResource && !isExactResource(tokenSummary.audience, configuredResource)) throw new OAuthError("invalid_target", { description: "Subject token is not bound to the configured resource" });
|
|
1674
|
-
const newAudience = this.resolveTokenResource(requestedResource, grantData.resource);
|
|
1711
|
+
const newAudience = requestedResource === void 0 ? tokenSummary.audience : this.resolveTokenResource(requestedResource, grantData.resource);
|
|
1675
1712
|
const now = Math.floor(Date.now() / 1e3);
|
|
1676
1713
|
const subjectTokenRemainingLifetime = tokenSummary.expiresAt - now;
|
|
1677
1714
|
if (subjectTokenRemainingLifetime < KV_MIN_EXPIRATION_TTL_SECONDS) throw new OAuthError("invalid_grant", { description: "Subject token is too close to expiry to exchange" });
|
|
@@ -2078,10 +2115,12 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
2078
2115
|
statusCode: 400
|
|
2079
2116
|
});
|
|
2080
2117
|
}
|
|
2118
|
+
let authMethodWasExplicit;
|
|
2081
2119
|
let authMethod;
|
|
2082
2120
|
let grantTypes;
|
|
2083
2121
|
let responseTypes;
|
|
2084
2122
|
try {
|
|
2123
|
+
authMethodWasExplicit = clientMetadata.token_endpoint_auth_method !== void 0;
|
|
2085
2124
|
authMethod = OAuthProviderImpl.validateStringField(clientMetadata.token_endpoint_auth_method) || "client_secret_basic";
|
|
2086
2125
|
grantTypes = OAuthProviderImpl.validateStringArray(clientMetadata.grant_types, "grant_types") || [GrantType.AUTHORIZATION_CODE];
|
|
2087
2126
|
responseTypes = OAuthProviderImpl.validateStringArray(clientMetadata.response_types, "response_types") || ["code"];
|
|
@@ -2121,7 +2160,8 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
2121
2160
|
grantTypes,
|
|
2122
2161
|
responseTypes,
|
|
2123
2162
|
registrationDate: Math.floor(Date.now() / 1e3),
|
|
2124
|
-
tokenEndpointAuthMethod: authMethod
|
|
2163
|
+
tokenEndpointAuthMethod: authMethod,
|
|
2164
|
+
...authMethodWasExplicit ? { authMethodExplicit: true } : {}
|
|
2125
2165
|
};
|
|
2126
2166
|
if (!isPublicClient && hashedSecret) clientInfo.clientSecret = hashedSecret;
|
|
2127
2167
|
} catch (error) {
|
|
@@ -2353,19 +2393,26 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
2353
2393
|
}
|
|
2354
2394
|
/**
|
|
2355
2395
|
* Resolves an access-token audience from a token request and its authorization grant.
|
|
2356
|
-
*
|
|
2357
|
-
* configuration, RFC 8707 downscoping is allowed
|
|
2396
|
+
* A configured canonical resource is inherited when omitted but cannot be overridden.
|
|
2397
|
+
* Without configuration, RFC 8707 downscoping is allowed, omission inherits a
|
|
2398
|
+
* bound grant, and a legacy unbound grant retains the v0.8.2 behavior.
|
|
2358
2399
|
*/
|
|
2359
2400
|
resolveTokenResource(requestedResource, grantedResource) {
|
|
2401
|
+
const resourceWasProvided = requestedResource !== void 0;
|
|
2360
2402
|
const requestedAudience = parseResourceParameter(requestedResource);
|
|
2361
|
-
if (
|
|
2403
|
+
if (resourceWasProvided && !requestedAudience) throw new OAuthError("invalid_target", { description: "The resource parameter must be a valid absolute URI without a fragment" });
|
|
2404
|
+
const grantResourceWasStored = grantedResource !== void 0;
|
|
2362
2405
|
const grantedAudience = parseResourceParameter(grantedResource);
|
|
2363
|
-
if (
|
|
2406
|
+
if (grantResourceWasStored && !grantedAudience) throw new OAuthError("invalid_target", { description: "The authorization grant contains an invalid resource" });
|
|
2364
2407
|
const configuredResource = this.options.resourceMetadata?.resource;
|
|
2365
|
-
if (configuredResource
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2408
|
+
if (configuredResource) {
|
|
2409
|
+
if (resourceWasProvided && !isExactResource(requestedResource, configuredResource)) throw new OAuthError("invalid_target", { description: `The resource parameter must exactly match ${configuredResource}` });
|
|
2410
|
+
if (isExactResource(grantedResource, configuredResource)) return configuredResource;
|
|
2411
|
+
if (!grantResourceWasStored) return configuredResource;
|
|
2412
|
+
throw new OAuthError("invalid_target", { description: "The authorization grant is not bound to the configured resource" });
|
|
2413
|
+
}
|
|
2414
|
+
const originOnly = !!this.options.resourceMatchOriginOnly;
|
|
2415
|
+
if (resourceWasProvided && grantResourceWasStored) {
|
|
2369
2416
|
const requestedResources = Array.isArray(requestedResource) ? requestedResource : [requestedResource];
|
|
2370
2417
|
const grantedResources = Array.isArray(grantedResource) ? grantedResource : [grantedResource];
|
|
2371
2418
|
for (const requested of requestedResources) if (!grantedResources.some((granted) => resourceMatches(requested, granted, originOnly))) throw new OAuthError("invalid_target", { description: "Requested resource was not included in the authorization request" });
|
|
@@ -2570,13 +2617,13 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
2570
2617
|
if (!clientName?.trim()) throw new Error("client_name is required and must not be empty");
|
|
2571
2618
|
if (!redirectUris || redirectUris.length === 0) throw new Error("redirect_uris is required and must not be empty");
|
|
2572
2619
|
if (declaredAuthMethod && !OAuthProviderImpl.CIMD_ALLOWED_AUTH_METHODS.includes(declaredAuthMethod) || authMethodChoices && !authMethodChoices.some((method) => OAuthProviderImpl.CIMD_ALLOWED_AUTH_METHODS.includes(method))) throw new Error(`CIMD client does not support an accepted token endpoint authentication method. Supported methods: ${OAuthProviderImpl.CIMD_ALLOWED_AUTH_METHODS.join(", ")}`);
|
|
2573
|
-
const
|
|
2574
|
-
const
|
|
2620
|
+
const advertisedGrantTypes = OAuthProviderImpl.validateStringArray(rawMetadata.grant_types, "grant_types") || [GrantType.AUTHORIZATION_CODE];
|
|
2621
|
+
const advertisedResponseTypes = OAuthProviderImpl.validateStringArray(rawMetadata.response_types, "response_types") || ["code"];
|
|
2575
2622
|
const effectiveAuthMethod = tokenEndpointAuthMethod || "none";
|
|
2576
|
-
|
|
2623
|
+
const { grantTypes, responseTypes } = negotiateCimdClientCapabilities(this.serverCapabilities, {
|
|
2577
2624
|
tokenEndpointAuthMethod: effectiveAuthMethod,
|
|
2578
|
-
grantTypes,
|
|
2579
|
-
responseTypes
|
|
2625
|
+
grantTypes: advertisedGrantTypes,
|
|
2626
|
+
responseTypes: advertisedResponseTypes
|
|
2580
2627
|
});
|
|
2581
2628
|
return {
|
|
2582
2629
|
clientId,
|
|
@@ -2885,6 +2932,7 @@ function audienceMatches(resourceServerUrl, audienceValue) {
|
|
|
2885
2932
|
function parseResourceParameter(value) {
|
|
2886
2933
|
if (!value) return;
|
|
2887
2934
|
const uris = Array.isArray(value) ? value : [value];
|
|
2935
|
+
if (uris.length === 0) return;
|
|
2888
2936
|
for (const uri of uris) if (typeof uri !== "string" || !validateResourceUri(uri)) return;
|
|
2889
2937
|
return value;
|
|
2890
2938
|
}
|
|
@@ -3258,11 +3306,14 @@ var OAuthHelpersImpl = class {
|
|
|
3258
3306
|
const withRedirect = (error) => {
|
|
3259
3307
|
throw withAuthorizationRedirect(error, redirectUri, state || void 0, issuer);
|
|
3260
3308
|
};
|
|
3309
|
+
const resourceWasProvided = resourceParam !== void 0;
|
|
3261
3310
|
let resource = parseResourceParameter(resourceParam);
|
|
3262
|
-
if (
|
|
3311
|
+
if (resourceWasProvided && !resource) withRedirect(new AuthorizationError("invalid_target", { description: "The resource parameter must be a valid absolute URI without a fragment" }));
|
|
3263
3312
|
const configuredResource = this.provider.options.resourceMetadata?.resource;
|
|
3264
|
-
if (configuredResource
|
|
3265
|
-
|
|
3313
|
+
if (configuredResource) {
|
|
3314
|
+
if (resourceWasProvided && !isExactResource(resource, configuredResource)) withRedirect(new AuthorizationError("invalid_target", { description: `The resource parameter must exactly match ${configuredResource}` }));
|
|
3315
|
+
resource = configuredResource;
|
|
3316
|
+
}
|
|
3266
3317
|
try {
|
|
3267
3318
|
validateAuthorizationResponseType(this.provider.serverCapabilities, responseType, clientInfo.responseTypes);
|
|
3268
3319
|
validateAuthorizationPkce(this.provider.serverCapabilities, {
|
|
@@ -3298,7 +3349,8 @@ var OAuthHelpersImpl = class {
|
|
|
3298
3349
|
* validating the metadata document fails.
|
|
3299
3350
|
*/
|
|
3300
3351
|
async lookupClient(clientId) {
|
|
3301
|
-
|
|
3352
|
+
const client = await this.provider.getClient(this.env, clientId);
|
|
3353
|
+
return client ? toPublicClientInfo(client) : null;
|
|
3302
3354
|
}
|
|
3303
3355
|
/**
|
|
3304
3356
|
* Completes an authorization request by creating a grant and either:
|
|
@@ -3316,8 +3368,11 @@ var OAuthHelpersImpl = class {
|
|
|
3316
3368
|
if (!clientInfo || !isValidRedirectUri(redirectUri, clientInfo.redirectUris)) throw new Error("Invalid redirect URI. The redirect URI provided does not match any registered URI for this client.");
|
|
3317
3369
|
validateAuthorizationResponseType(this.provider.serverCapabilities, options.request.responseType, clientInfo.responseTypes);
|
|
3318
3370
|
const configuredResource = this.provider.options.resourceMetadata?.resource;
|
|
3319
|
-
|
|
3320
|
-
const
|
|
3371
|
+
const resourceWasProvided = options.request.resource !== void 0;
|
|
3372
|
+
const parsedResource = parseResourceParameter(options.request.resource);
|
|
3373
|
+
if (resourceWasProvided && !parsedResource) throw new AuthorizationError("invalid_target", { description: "The resource parameter must be a valid absolute URI without a fragment" });
|
|
3374
|
+
if (configuredResource && resourceWasProvided && !isExactResource(parsedResource, configuredResource)) throw new AuthorizationError("invalid_target", { description: `The resource parameter must exactly match ${configuredResource}` });
|
|
3375
|
+
const effectiveResource = configuredResource ?? parsedResource;
|
|
3321
3376
|
validateAuthorizationPkce(this.provider.serverCapabilities, options.request, clientInfo);
|
|
3322
3377
|
let grantsToRevoke = [];
|
|
3323
3378
|
if (options.revokeExistingGrants !== false) {
|
|
@@ -3423,6 +3478,7 @@ var OAuthHelpersImpl = class {
|
|
|
3423
3478
|
*/
|
|
3424
3479
|
async createClient(clientInfo) {
|
|
3425
3480
|
const clientId = generateRandomString(16);
|
|
3481
|
+
const authMethodWasExplicit = clientInfo.tokenEndpointAuthMethod !== void 0;
|
|
3426
3482
|
const tokenEndpointAuthMethod = clientInfo.tokenEndpointAuthMethod || "client_secret_basic";
|
|
3427
3483
|
const isPublicClient = tokenEndpointAuthMethod === "none";
|
|
3428
3484
|
const newClient = {
|
|
@@ -3443,7 +3499,8 @@ var OAuthHelpersImpl = class {
|
|
|
3443
3499
|
],
|
|
3444
3500
|
responseTypes: clientInfo.responseTypes || ["code"],
|
|
3445
3501
|
registrationDate: Math.floor(Date.now() / 1e3),
|
|
3446
|
-
tokenEndpointAuthMethod
|
|
3502
|
+
tokenEndpointAuthMethod,
|
|
3503
|
+
...authMethodWasExplicit ? { authMethodExplicit: true } : {}
|
|
3447
3504
|
};
|
|
3448
3505
|
for (const uri of newClient.redirectUris) validateRedirectUriScheme(uri);
|
|
3449
3506
|
let clientSecret;
|
|
@@ -3452,7 +3509,7 @@ var OAuthHelpersImpl = class {
|
|
|
3452
3509
|
newClient.clientSecret = await hashSecret(clientSecret);
|
|
3453
3510
|
}
|
|
3454
3511
|
await this.env.OAUTH_KV.put(`client:${clientId}`, JSON.stringify(newClient));
|
|
3455
|
-
const clientResponse =
|
|
3512
|
+
const clientResponse = toPublicClientInfo(newClient);
|
|
3456
3513
|
if (!isPublicClient && clientSecret) clientResponse.clientSecret = clientSecret;
|
|
3457
3514
|
return clientResponse;
|
|
3458
3515
|
}
|
|
@@ -3470,7 +3527,7 @@ var OAuthHelpersImpl = class {
|
|
|
3470
3527
|
const promises = response.keys.map(async (key) => {
|
|
3471
3528
|
const clientId = key.name.substring(7);
|
|
3472
3529
|
const client = await this.provider.getClient(this.env, clientId);
|
|
3473
|
-
if (client) clients.push(client);
|
|
3530
|
+
if (client) clients.push(toPublicClientInfo(client));
|
|
3474
3531
|
});
|
|
3475
3532
|
await Promise.all(promises);
|
|
3476
3533
|
return {
|
|
@@ -3487,7 +3544,8 @@ var OAuthHelpersImpl = class {
|
|
|
3487
3544
|
async updateClient(clientId, updates) {
|
|
3488
3545
|
const client = await this.provider.getClient(this.env, clientId);
|
|
3489
3546
|
if (!client) return null;
|
|
3490
|
-
|
|
3547
|
+
const authMethodWasExplicit = updates.tokenEndpointAuthMethod !== void 0;
|
|
3548
|
+
const authMethod = updates.tokenEndpointAuthMethod || client.tokenEndpointAuthMethod || "client_secret_basic";
|
|
3491
3549
|
const isPublicClient = authMethod === "none";
|
|
3492
3550
|
let secretToStore = client.clientSecret;
|
|
3493
3551
|
let originalSecret = void 0;
|
|
@@ -3500,14 +3558,15 @@ var OAuthHelpersImpl = class {
|
|
|
3500
3558
|
...client,
|
|
3501
3559
|
...updates,
|
|
3502
3560
|
clientId: client.clientId,
|
|
3503
|
-
tokenEndpointAuthMethod: authMethod
|
|
3561
|
+
tokenEndpointAuthMethod: authMethod,
|
|
3562
|
+
authMethodExplicit: authMethodWasExplicit ? true : client.authMethodExplicit
|
|
3504
3563
|
};
|
|
3505
3564
|
if (!isPublicClient && secretToStore) updatedClient.clientSecret = secretToStore;
|
|
3506
3565
|
else delete updatedClient.clientSecret;
|
|
3507
3566
|
const clientKvOptions = {};
|
|
3508
3567
|
if (this.provider.options.clientRegistrationTTL !== void 0) clientKvOptions.expirationTtl = this.provider.options.clientRegistrationTTL;
|
|
3509
3568
|
await this.env.OAUTH_KV.put(`client:${clientId}`, JSON.stringify(updatedClient), clientKvOptions);
|
|
3510
|
-
const response =
|
|
3569
|
+
const response = toPublicClientInfo(updatedClient);
|
|
3511
3570
|
if (!isPublicClient && originalSecret) response.clientSecret = originalSecret;
|
|
3512
3571
|
return response;
|
|
3513
3572
|
}
|