@cloudflare/workers-oauth-provider 1.2.0 → 1.2.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/dist/oauth-provider.d.ts
CHANGED
|
@@ -1098,9 +1098,11 @@ interface OAuthProviderOptions<Env = Cloudflare.Env> {
|
|
|
1098
1098
|
request?: Request;
|
|
1099
1099
|
}) => Response | void;
|
|
1100
1100
|
/**
|
|
1101
|
-
*
|
|
1102
|
-
* for native apps. By default redirect
|
|
1103
|
-
* MCP and OAuth 2.1 require; remote `http` is never
|
|
1101
|
+
* Let authorization requests use RFC 8252 private-use URI scheme redirect URIs (for example
|
|
1102
|
+
* `com.example.app:/oauth`), for native apps. By default a request's redirect URI must use
|
|
1103
|
+
* `https`, or `http` on a loopback host, as MCP and OAuth 2.1 require; remote `http` is never
|
|
1104
|
+
* accepted. Without this option a client can still register private-use URIs next to a compliant
|
|
1105
|
+
* one (Cursor does), but no request can use them.
|
|
1104
1106
|
*/
|
|
1105
1107
|
allowPrivateUseRedirectUris?: boolean;
|
|
1106
1108
|
/**
|
package/dist/oauth-provider.js
CHANGED
|
@@ -516,14 +516,12 @@ function validateRedirectUriSafety(redirectUri) {
|
|
|
516
516
|
if (dangerousSchemes.includes(scheme)) throw new Error("Invalid redirect URI");
|
|
517
517
|
}
|
|
518
518
|
/**
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* empty `#`)
|
|
522
|
-
*
|
|
523
|
-
* checks always apply.
|
|
524
|
-
* @throws Error describing why the redirect URI is not acceptable
|
|
519
|
+
* The checks every redirect URI a client registers passes, whether or not this server would send a
|
|
520
|
+
* code to it: the {@link validateRedirectUriSafety} checks, a parseable URI, and no fragment (not
|
|
521
|
+
* even an empty `#`) or userinfo.
|
|
522
|
+
* @throws Error when the redirect URI can't be registered at all
|
|
525
523
|
*/
|
|
526
|
-
function
|
|
524
|
+
function validateListedRedirectUri(redirectUri) {
|
|
527
525
|
validateRedirectUriSafety(redirectUri);
|
|
528
526
|
const normalized = redirectUri.trim();
|
|
529
527
|
let url;
|
|
@@ -533,6 +531,18 @@ function validateRedirectUri(redirectUri, server) {
|
|
|
533
531
|
throw new Error("Invalid redirect URI");
|
|
534
532
|
}
|
|
535
533
|
if (normalized.includes("#") || url.username || url.password) throw new Error("Invalid redirect URI");
|
|
534
|
+
return url;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Validates a redirect URI against the MCP / OAuth 2.1 redirect policy: `https`, or `http` on a
|
|
538
|
+
* loopback host (`localhost`, `127.0.0.0/8`, `::1`), with no userinfo and no fragment (not even an
|
|
539
|
+
* empty `#`). RFC 8252 private-use schemes are accepted only when the server enables
|
|
540
|
+
* `allowPrivateUseRedirectUris`; remote `http` never is. The {@link validateRedirectUriSafety}
|
|
541
|
+
* checks always apply.
|
|
542
|
+
* @throws Error describing why the redirect URI is not acceptable
|
|
543
|
+
*/
|
|
544
|
+
function validateRedirectUri(redirectUri, server) {
|
|
545
|
+
const url = validateListedRedirectUri(redirectUri);
|
|
536
546
|
if (url.protocol === "https:") return;
|
|
537
547
|
if (url.protocol === "http:") {
|
|
538
548
|
if (isLoopbackHostname(url.hostname)) return;
|
|
@@ -540,6 +550,27 @@ function validateRedirectUri(redirectUri, server) {
|
|
|
540
550
|
}
|
|
541
551
|
if (!server.allowPrivateUseRedirectUris) throw new Error("Redirect URI must use https, or http on a loopback host");
|
|
542
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Validates the redirect URIs a client registers (dynamic registration, `createClient()`,
|
|
555
|
+
* `updateClient()`). A client may list callbacks for surfaces this server won't send codes to:
|
|
556
|
+
* Cursor lists `cursor://anysphere.cursor-mcp/oauth/callback` next to its https and loopback
|
|
557
|
+
* callbacks. So each URI is held to {@link validateListedRedirectUri}, and at least one must satisfy
|
|
558
|
+
* {@link validateRedirectUri} so the client can sign in. The URI a request uses is held to the full
|
|
559
|
+
* policy by `parseAuthRequest()` and `completeAuthorization()`.
|
|
560
|
+
* @throws Error when a URI can't be registered, or none of them could ever receive a code
|
|
561
|
+
*/
|
|
562
|
+
function validateRegisteredRedirectUris(redirectUris, server) {
|
|
563
|
+
if (!redirectUris || redirectUris.length === 0) throw new Error("redirect_uris is required and must not be empty");
|
|
564
|
+
for (const redirectUri of redirectUris) validateListedRedirectUri(redirectUri);
|
|
565
|
+
let firstRefusal;
|
|
566
|
+
for (const redirectUri of redirectUris) try {
|
|
567
|
+
validateRedirectUri(redirectUri, server);
|
|
568
|
+
return redirectUris;
|
|
569
|
+
} catch (error) {
|
|
570
|
+
firstRefusal ??= error;
|
|
571
|
+
}
|
|
572
|
+
throw firstRefusal instanceof Error ? firstRefusal : /* @__PURE__ */ new Error("Invalid redirect URI");
|
|
573
|
+
}
|
|
543
574
|
function requireValidRedirectUris(redirectUris, validate) {
|
|
544
575
|
if (!redirectUris || redirectUris.length === 0) throw new Error("redirect_uris is required and must not be empty");
|
|
545
576
|
for (const redirectUri of redirectUris) validate(redirectUri);
|
|
@@ -560,7 +591,7 @@ function resolveDynamicClientRegistrationMetadata(raw, server) {
|
|
|
560
591
|
});
|
|
561
592
|
return {
|
|
562
593
|
...pickDisplayMetadata(metadata),
|
|
563
|
-
redirectUris:
|
|
594
|
+
redirectUris: validateRegisteredRedirectUris(metadata.redirectUris, server),
|
|
564
595
|
...capabilities,
|
|
565
596
|
authMethodExplicit: metadata.tokenEndpointAuthMethod !== void 0 || metadata.tokenEndpointAuthMethodsSupported !== void 0
|
|
566
597
|
};
|
|
@@ -5309,7 +5340,7 @@ var OAuthHelpersImpl = class {
|
|
|
5309
5340
|
tokenEndpointAuthMethod,
|
|
5310
5341
|
...authMethodWasExplicit ? { authMethodExplicit: true } : {}
|
|
5311
5342
|
};
|
|
5312
|
-
|
|
5343
|
+
validateRegisteredRedirectUris(newClient.redirectUris, this.provider.serverCapabilities);
|
|
5313
5344
|
validateClientCapabilities(this.provider.serverCapabilities, {
|
|
5314
5345
|
grantTypes: newClient.grantTypes,
|
|
5315
5346
|
responseTypes: newClient.responseTypes,
|
|
@@ -5359,7 +5390,7 @@ var OAuthHelpersImpl = class {
|
|
|
5359
5390
|
if (!client) return null;
|
|
5360
5391
|
if (updates.redirectUris !== void 0) {
|
|
5361
5392
|
if (!Array.isArray(updates.redirectUris) || updates.redirectUris.length === 0) throw new Error("redirectUris must not be empty");
|
|
5362
|
-
|
|
5393
|
+
validateRegisteredRedirectUris(updates.redirectUris, this.provider.serverCapabilities);
|
|
5363
5394
|
}
|
|
5364
5395
|
const authMethodWasExplicit = updates.tokenEndpointAuthMethod !== void 0;
|
|
5365
5396
|
const authMethod = updates.tokenEndpointAuthMethod || client.tokenEndpointAuthMethod || "client_secret_basic";
|
|
@@ -71,7 +71,7 @@ CIMD validation follows [draft-ietf-oauth-client-id-metadata-document-00](https:
|
|
|
71
71
|
|
|
72
72
|
- An HTTPS Client Identifier URL with a path component and no userinfo, fragment, or dot path segments.
|
|
73
73
|
- A document `client_id` exactly matching its URL.
|
|
74
|
-
- Non-empty `client_name` and `redirect_uris` fields, as MCP requires. Redirect URIs must use `https`, or `http` on a loopback host (`localhost`, `127.0.0.0/8`, `::1`), with no userinfo or fragment.
|
|
74
|
+
- Non-empty `client_name` and `redirect_uris` fields, as MCP requires. Redirect URIs must use `https`, or `http` on a loopback host (`localhost`, `127.0.0.0/8`, `::1`), with no userinfo or fragment. Every authorization request is held to it, so older clients are too. A client may register, or list in its CIMD document, other redirect URIs next to a compliant one, because it signs in from several places: Cursor registers `cursor://anysphere.cursor-mcp/oauth/callback` beside its https and loopback callbacks. Those are stored, and refused whenever a request uses one. Dynamic registration, `createClient()` and `updateClient()` need at least one compliant redirect URI, and refuse any with a dangerous scheme such as `javascript:`, a fragment or userinfo; a dangerous scheme also rejects a whole CIMD document. Native apps using RFC 8252 private-use schemes (`com.example.app:/cb`) need `allowPrivateUseRedirectUris: true`; remote `http` is never accepted.
|
|
75
75
|
- Exact authorization-request redirect URI validation, with RFC 8252 loopback port handling.
|
|
76
76
|
- A 5 KB response size limit and a 10 second timeout covering both headers and body.
|
|
77
77
|
- Valid UTF-8 JSON object syntax and safe URI schemes for client metadata fields.
|
package/docs/migration-1.0.md
CHANGED
|
@@ -103,7 +103,7 @@ If you have your own clients on either flow, move them to the authorization code
|
|
|
103
103
|
|
|
104
104
|
### Redirect URIs must be HTTPS or loopback HTTP (1.2)
|
|
105
105
|
|
|
106
|
-
Redirect URIs must use `https`, or `http` on `localhost`, `127.0.0.0/8` or `::1`, with no userinfo or fragment. That's what MCP and OAuth 2.1 require.
|
|
106
|
+
Redirect URIs must use `https`, or `http` on `localhost`, `127.0.0.0/8` or `::1`, with no userinfo or fragment. That's what MCP and OAuth 2.1 require. Every authorization request is held to it. A registration (dynamic registration, `createClient()`, `updateClient()`) needs at least one redirect URI that follows it, and may list others next to it, as Cursor lists `cursor://…` beside its https and loopback callbacks; so may a CIMD document. A request that uses one of those is refused. Registrations still refuse dangerous schemes such as `javascript:`, fragments and userinfo.
|
|
107
107
|
|
|
108
108
|
Because it applies at authorization too, clients registered before 1.2 are held to it. A client with a remote `http` redirect URI gets a locally rendered `invalid_request` ("Invalid redirect URI") and is never redirected. It has to register a compliant URI.
|
|
109
109
|
|