@cloudflare/workers-oauth-provider 0.3.2 → 0.4.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.
- package/dist/oauth-provider.d.ts +10 -0
- package/dist/oauth-provider.js +19 -3
- package/package.json +1 -1
package/dist/oauth-provider.d.ts
CHANGED
|
@@ -253,6 +253,16 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
|
|
|
253
253
|
* Defaults to false.
|
|
254
254
|
*/
|
|
255
255
|
clientIdMetadataDocumentEnabled?: boolean;
|
|
256
|
+
/**
|
|
257
|
+
* When true, resource validation during token exchange compares origins only
|
|
258
|
+
* (scheme + host + port) instead of exact URI matching. This allows grants issued
|
|
259
|
+
* with an origin-only resource (e.g. `https://server.com`) to be used with
|
|
260
|
+
* path-aware resource requests (e.g. `https://server.com/mcp`), enabling seamless
|
|
261
|
+
* migration from pre-0.4.0 versions that stored origin-only resource URIs.
|
|
262
|
+
*
|
|
263
|
+
* Defaults to false (strict exact matching per RFC 8707).
|
|
264
|
+
*/
|
|
265
|
+
resourceMatchOriginOnly?: boolean;
|
|
256
266
|
/**
|
|
257
267
|
* Optional metadata for RFC 9728 OAuth 2.0 Protected Resource Metadata.
|
|
258
268
|
* Controls the response served at /.well-known/oauth-protected-resource.
|
package/dist/oauth-provider.js
CHANGED
|
@@ -537,10 +537,11 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
537
537
|
grantData.expiresAt = expiresAt;
|
|
538
538
|
}
|
|
539
539
|
await this.saveGrantWithTTL(env, grantKey, grantData, now);
|
|
540
|
+
const originOnly = !!this.options.resourceMatchOriginOnly;
|
|
540
541
|
if (body.resource && grantData.resource) {
|
|
541
542
|
const requestedResources = Array.isArray(body.resource) ? body.resource : [body.resource];
|
|
542
543
|
const grantedResources = Array.isArray(grantData.resource) ? grantData.resource : [grantData.resource];
|
|
543
|
-
for (const requested of requestedResources) if (!grantedResources.
|
|
544
|
+
for (const requested of requestedResources) if (!grantedResources.some((granted) => resourceMatches(requested, granted, originOnly))) return this.createErrorResponse("invalid_target", "Requested resource was not included in the authorization request");
|
|
544
545
|
}
|
|
545
546
|
const audience = parseResourceParameter(body.resource || grantData.resource);
|
|
546
547
|
if ((body.resource || grantData.resource) && !audience) return this.createErrorResponse("invalid_target", "The resource parameter must be a valid absolute URI without a fragment");
|
|
@@ -657,10 +658,11 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
657
658
|
grantData.refreshTokenId = newRefreshTokenId;
|
|
658
659
|
grantData.refreshTokenWrappedKey = newRefreshTokenWrappedKey;
|
|
659
660
|
await this.saveGrantWithTTL(env, grantKey, grantData, now);
|
|
661
|
+
const originOnly = !!this.options.resourceMatchOriginOnly;
|
|
660
662
|
if (body.resource && grantData.resource) {
|
|
661
663
|
const requestedResources = Array.isArray(body.resource) ? body.resource : [body.resource];
|
|
662
664
|
const grantedResources = Array.isArray(grantData.resource) ? grantData.resource : [grantData.resource];
|
|
663
|
-
for (const requested of requestedResources) if (!grantedResources.
|
|
665
|
+
for (const requested of requestedResources) if (!grantedResources.some((granted) => resourceMatches(requested, granted, originOnly))) return this.createErrorResponse("invalid_target", "Requested resource was not included in the authorization request");
|
|
664
666
|
}
|
|
665
667
|
const audience = parseResourceParameter(body.resource || grantData.resource);
|
|
666
668
|
if ((body.resource || grantData.resource) && !audience) return this.createErrorResponse("invalid_target", "The resource parameter must be a valid absolute URI without a fragment");
|
|
@@ -712,12 +714,13 @@ var OAuthProviderImpl = class OAuthProviderImpl {
|
|
|
712
714
|
const grantData = await env.OAUTH_KV.get(grantKey, { type: "json" });
|
|
713
715
|
if (!grantData) throw new OAuthError("invalid_grant", "Grant not found");
|
|
714
716
|
let tokenScopes = this.downscope(requestedScopes, grantData.scope);
|
|
717
|
+
const originOnly = !!this.options.resourceMatchOriginOnly;
|
|
715
718
|
let newAudience = tokenSummary.audience;
|
|
716
719
|
if (requestedResource) {
|
|
717
720
|
if (grantData.resource) {
|
|
718
721
|
const requestedResources = Array.isArray(requestedResource) ? requestedResource : [requestedResource];
|
|
719
722
|
const grantedResources = Array.isArray(grantData.resource) ? grantData.resource : [grantData.resource];
|
|
720
|
-
for (const requested of requestedResources) if (!grantedResources.
|
|
723
|
+
for (const requested of requestedResources) if (!grantedResources.some((granted) => resourceMatches(requested, granted, originOnly))) throw new OAuthError("invalid_target", "Requested resource was not included in the authorization request");
|
|
721
724
|
}
|
|
722
725
|
const parsedResource = parseResourceParameter(requestedResource);
|
|
723
726
|
if (!parsedResource) throw new OAuthError("invalid_target", "The resource parameter must be a valid absolute URI without a fragment");
|
|
@@ -1353,6 +1356,19 @@ function parseResourceParameter(value) {
|
|
|
1353
1356
|
return value;
|
|
1354
1357
|
}
|
|
1355
1358
|
/**
|
|
1359
|
+
* Checks if a requested resource matches a granted resource.
|
|
1360
|
+
* When originOnly is true, compares only the origin (scheme + host + port),
|
|
1361
|
+
* allowing path-aware resources to match origin-only grants.
|
|
1362
|
+
*/
|
|
1363
|
+
function resourceMatches(requested, granted, originOnly) {
|
|
1364
|
+
if (!originOnly) return requested === granted;
|
|
1365
|
+
try {
|
|
1366
|
+
return new URL(requested).origin === new URL(granted).origin;
|
|
1367
|
+
} catch {
|
|
1368
|
+
return requested === granted;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1356
1372
|
* Hashes a secret value using SHA-256
|
|
1357
1373
|
* @param secret - The secret value to hash
|
|
1358
1374
|
* @returns A hex string representation of the hash
|