mcp-trellis 0.2.1 → 2.1.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/README.md +70 -620
- package/dist/adapters/node-request.d.ts +53 -0
- package/dist/adapters/node-request.d.ts.map +1 -0
- package/dist/adapters/node-request.js +165 -0
- package/dist/adapters/node-request.js.map +1 -0
- package/dist/adapters/node.d.ts +16 -24
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +69 -88
- package/dist/adapters/node.js.map +1 -1
- package/dist/advanced.d.ts +18 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +18 -0
- package/dist/advanced.js.map +1 -0
- package/dist/app-options.d.ts +132 -0
- package/dist/app-options.d.ts.map +1 -0
- package/dist/app-options.js +2 -0
- package/dist/app-options.js.map +1 -0
- package/dist/app.d.ts +2 -66
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +150 -57
- package/dist/app.js.map +1 -1
- package/dist/audit.d.ts +5 -5
- package/dist/audit.d.ts.map +1 -1
- package/dist/audit.js +19 -8
- package/dist/audit.js.map +1 -1
- package/dist/auth/bearer.d.ts +4 -1
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +25 -5
- package/dist/auth/bearer.js.map +1 -1
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js +3 -6
- package/dist/clients.js.map +1 -1
- package/dist/deprecated.d.ts +42 -0
- package/dist/deprecated.d.ts.map +1 -0
- package/dist/deprecated.js +35 -0
- package/dist/deprecated.js.map +1 -0
- package/dist/http/body.d.ts +69 -0
- package/dist/http/body.d.ts.map +1 -0
- package/dist/http/body.js +165 -0
- package/dist/http/body.js.map +1 -0
- package/dist/http/http.d.ts +42 -0
- package/dist/http/http.d.ts.map +1 -0
- package/dist/http/http.js +100 -0
- package/dist/http/http.js.map +1 -0
- package/dist/http/origins.d.ts +37 -0
- package/dist/http/origins.d.ts.map +1 -0
- package/dist/http/origins.js +87 -0
- package/dist/http/origins.js.map +1 -0
- package/dist/mcp/dispatch.d.ts +7 -0
- package/dist/mcp/dispatch.d.ts.map +1 -0
- package/dist/mcp/dispatch.js +99 -0
- package/dist/mcp/dispatch.js.map +1 -0
- package/dist/{jsonrpc.d.ts → mcp/jsonrpc.d.ts} +10 -0
- package/dist/mcp/jsonrpc.d.ts.map +1 -0
- package/dist/mcp/jsonrpc.js +88 -0
- package/dist/mcp/jsonrpc.js.map +1 -0
- package/dist/mcp/methods.d.ts +133 -0
- package/dist/mcp/methods.d.ts.map +1 -0
- package/dist/mcp/methods.js +84 -0
- package/dist/mcp/methods.js.map +1 -0
- package/dist/mcp/protocol.d.ts +16 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +22 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/registry.d.ts +58 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +81 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/rpc-handlers.d.ts +11 -0
- package/dist/mcp/rpc-handlers.d.ts.map +1 -0
- package/dist/mcp/rpc-handlers.js +133 -0
- package/dist/mcp/rpc-handlers.js.map +1 -0
- package/dist/{tools.d.ts → mcp/tools.d.ts} +30 -2
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +166 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/{validate.d.ts → mcp/validate.d.ts} +24 -12
- package/dist/mcp/validate.d.ts.map +1 -0
- package/dist/mcp/validate.js +186 -0
- package/dist/mcp/validate.js.map +1 -0
- package/dist/oauth/audit.d.ts +20 -0
- package/dist/oauth/audit.d.ts.map +1 -0
- package/dist/oauth/audit.js +29 -0
- package/dist/oauth/audit.js.map +1 -0
- package/dist/oauth/cimd/cimd-parse.d.ts +4 -0
- package/dist/oauth/cimd/cimd-parse.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd-parse.js +70 -0
- package/dist/oauth/cimd/cimd-parse.js.map +1 -0
- package/dist/oauth/cimd/cimd-ssrf.d.ts +8 -0
- package/dist/oauth/cimd/cimd-ssrf.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd-ssrf.js +193 -0
- package/dist/oauth/cimd/cimd-ssrf.js.map +1 -0
- package/dist/oauth/cimd/cimd.d.ts +36 -0
- package/dist/oauth/cimd/cimd.d.ts.map +1 -0
- package/dist/oauth/cimd/cimd.js +132 -0
- package/dist/oauth/cimd/cimd.js.map +1 -0
- package/dist/oauth/cimd/types.d.ts +12 -0
- package/dist/oauth/cimd/types.d.ts.map +1 -0
- package/dist/oauth/cimd/types.js +3 -0
- package/dist/oauth/cimd/types.js.map +1 -0
- package/dist/oauth/config.d.ts +51 -0
- package/dist/oauth/config.d.ts.map +1 -0
- package/dist/oauth/config.js +108 -0
- package/dist/oauth/config.js.map +1 -0
- package/dist/oauth/constants.d.ts +15 -0
- package/dist/oauth/constants.d.ts.map +1 -1
- package/dist/oauth/constants.js +15 -0
- package/dist/oauth/constants.js.map +1 -1
- package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +1 -2
- package/dist/oauth/crypto/base64url.d.ts.map +1 -0
- package/dist/oauth/{base64url.js → crypto/base64url.js} +1 -5
- package/dist/oauth/crypto/base64url.js.map +1 -0
- package/dist/oauth/crypto/clientauth.d.ts +13 -0
- package/dist/oauth/crypto/clientauth.d.ts.map +1 -0
- package/dist/oauth/crypto/clientauth.js +195 -0
- package/dist/oauth/crypto/clientauth.js.map +1 -0
- package/dist/oauth/crypto/codes.d.ts +65 -0
- package/dist/oauth/crypto/codes.d.ts.map +1 -0
- package/dist/oauth/crypto/codes.js +121 -0
- package/dist/oauth/crypto/codes.js.map +1 -0
- package/dist/oauth/crypto/hkdf.d.ts +13 -0
- package/dist/oauth/crypto/hkdf.d.ts.map +1 -0
- package/dist/oauth/crypto/hkdf.js +30 -0
- package/dist/oauth/crypto/hkdf.js.map +1 -0
- package/dist/oauth/crypto/keycache.d.ts +19 -0
- package/dist/oauth/crypto/keycache.d.ts.map +1 -0
- package/dist/oauth/crypto/keycache.js +44 -0
- package/dist/oauth/crypto/keycache.js.map +1 -0
- package/dist/oauth/crypto/pkce.d.ts.map +1 -0
- package/dist/oauth/{pkce.js → crypto/pkce.js} +1 -1
- package/dist/oauth/crypto/pkce.js.map +1 -0
- package/dist/oauth/crypto/sealed.d.ts +26 -0
- package/dist/oauth/crypto/sealed.d.ts.map +1 -0
- package/dist/oauth/crypto/sealed.js +65 -0
- package/dist/oauth/crypto/sealed.js.map +1 -0
- package/dist/oauth/crypto/secrethash.d.ts +58 -0
- package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
- package/dist/oauth/crypto/secrethash.js +82 -0
- package/dist/oauth/crypto/secrethash.js.map +1 -0
- package/dist/oauth/deprecated.d.ts +52 -0
- package/dist/oauth/deprecated.d.ts.map +1 -0
- package/dist/oauth/deprecated.js +49 -0
- package/dist/oauth/deprecated.js.map +1 -0
- package/dist/oauth/endpoints/authorize.d.ts +3 -0
- package/dist/oauth/endpoints/authorize.d.ts.map +1 -0
- package/dist/oauth/endpoints/authorize.js +209 -0
- package/dist/oauth/endpoints/authorize.js.map +1 -0
- package/dist/oauth/endpoints/callback.d.ts +20 -0
- package/dist/oauth/endpoints/callback.d.ts.map +1 -0
- package/dist/oauth/endpoints/callback.js +31 -0
- package/dist/oauth/endpoints/callback.js.map +1 -0
- package/dist/oauth/endpoints/consent-page.d.ts +5 -0
- package/dist/oauth/endpoints/consent-page.d.ts.map +1 -0
- package/dist/oauth/endpoints/consent-page.js +63 -0
- package/dist/oauth/endpoints/consent-page.js.map +1 -0
- package/dist/oauth/endpoints/consent.d.ts +29 -0
- package/dist/oauth/endpoints/consent.d.ts.map +1 -0
- package/dist/oauth/endpoints/consent.js +98 -0
- package/dist/oauth/endpoints/consent.js.map +1 -0
- package/dist/oauth/{metadata.d.ts → endpoints/metadata.d.ts} +14 -8
- package/dist/oauth/endpoints/metadata.d.ts.map +1 -0
- package/dist/oauth/{metadata.js → endpoints/metadata.js} +10 -4
- package/dist/oauth/endpoints/metadata.js.map +1 -0
- package/dist/oauth/endpoints/register.d.ts +10 -0
- package/dist/oauth/endpoints/register.d.ts.map +1 -0
- package/dist/oauth/endpoints/register.js +96 -0
- package/dist/oauth/endpoints/register.js.map +1 -0
- package/dist/oauth/{revoke.d.ts → endpoints/revoke.d.ts} +1 -1
- package/dist/oauth/endpoints/revoke.d.ts.map +1 -0
- package/dist/oauth/{revoke.js → endpoints/revoke.js} +11 -9
- package/dist/oauth/endpoints/revoke.js.map +1 -0
- package/dist/oauth/endpoints/token.d.ts +3 -0
- package/dist/oauth/endpoints/token.d.ts.map +1 -0
- package/dist/oauth/{token.js → endpoints/token.js} +33 -47
- package/dist/oauth/endpoints/token.js.map +1 -0
- package/dist/oauth/{wellknown.d.ts → endpoints/wellknown.d.ts} +1 -1
- package/dist/oauth/endpoints/wellknown.d.ts.map +1 -0
- package/dist/oauth/{wellknown.js → endpoints/wellknown.js} +7 -7
- package/dist/oauth/endpoints/wellknown.js.map +1 -0
- package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +6 -1
- package/dist/oauth/policy/redirect.d.ts.map +1 -0
- package/dist/oauth/policy/redirect.js +39 -0
- package/dist/oauth/policy/redirect.js.map +1 -0
- package/dist/oauth/policy/reqbody.d.ts +18 -0
- package/dist/oauth/policy/reqbody.d.ts.map +1 -0
- package/dist/oauth/policy/reqbody.js +68 -0
- package/dist/oauth/policy/reqbody.js.map +1 -0
- package/dist/oauth/policy/resource.d.ts +37 -0
- package/dist/oauth/policy/resource.d.ts.map +1 -0
- package/dist/oauth/policy/resource.js +74 -0
- package/dist/oauth/policy/resource.js.map +1 -0
- package/dist/oauth/policy/scope.d.ts +20 -0
- package/dist/oauth/policy/scope.d.ts.map +1 -0
- package/dist/oauth/policy/scope.js +35 -0
- package/dist/oauth/policy/scope.js.map +1 -0
- package/dist/oauth/router.d.ts +12 -12
- package/dist/oauth/router.d.ts.map +1 -1
- package/dist/oauth/router.js +105 -87
- package/dist/oauth/router.js.map +1 -1
- package/dist/oauth/types.d.ts +177 -26
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js +1 -27
- package/dist/oauth/types.js.map +1 -1
- package/dist/server.d.ts +14 -13
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -9
- package/dist/server.js.map +1 -1
- package/dist/util/defined.d.ts +11 -0
- package/dist/util/defined.d.ts.map +1 -0
- package/dist/util/defined.js +15 -0
- package/dist/util/defined.js.map +1 -0
- package/dist/util/json.d.ts +3 -0
- package/dist/util/json.d.ts.map +1 -0
- package/dist/util/json.js +3 -0
- package/dist/util/json.js.map +1 -0
- package/docs/ROADMAP.md +283 -0
- package/docs/compatibility.md +84 -0
- package/docs/diagrams/mcp-trellis-architecture.html +305 -0
- package/docs/guide.md +441 -0
- package/docs/reference.md +210 -0
- package/docs/security.md +112 -0
- package/docs/troubleshooting.md +19 -0
- package/package.json +24 -8
- package/dist/adapters/origins.d.ts +0 -19
- package/dist/adapters/origins.d.ts.map +0 -1
- package/dist/adapters/origins.js +0 -55
- package/dist/adapters/origins.js.map +0 -1
- package/dist/dispatch.d.ts +0 -7
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -79
- package/dist/dispatch.js.map +0 -1
- package/dist/http.d.ts +0 -17
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -38
- package/dist/http.js.map +0 -1
- package/dist/jsonrpc.d.ts.map +0 -1
- package/dist/jsonrpc.js +0 -17
- package/dist/jsonrpc.js.map +0 -1
- package/dist/methods.d.ts +0 -61
- package/dist/methods.d.ts.map +0 -1
- package/dist/methods.js +0 -114
- package/dist/methods.js.map +0 -1
- package/dist/oauth/authorize.d.ts +0 -3
- package/dist/oauth/authorize.d.ts.map +0 -1
- package/dist/oauth/authorize.js +0 -109
- package/dist/oauth/authorize.js.map +0 -1
- package/dist/oauth/base64url.d.ts.map +0 -1
- package/dist/oauth/base64url.js.map +0 -1
- package/dist/oauth/clientauth.d.ts +0 -19
- package/dist/oauth/clientauth.d.ts.map +0 -1
- package/dist/oauth/clientauth.js +0 -76
- package/dist/oauth/clientauth.js.map +0 -1
- package/dist/oauth/codes.d.ts +0 -30
- package/dist/oauth/codes.d.ts.map +0 -1
- package/dist/oauth/codes.js +0 -93
- package/dist/oauth/codes.js.map +0 -1
- package/dist/oauth/metadata.d.ts.map +0 -1
- package/dist/oauth/metadata.js.map +0 -1
- package/dist/oauth/pkce.d.ts.map +0 -1
- package/dist/oauth/pkce.js.map +0 -1
- package/dist/oauth/redirect.d.ts.map +0 -1
- package/dist/oauth/redirect.js +0 -26
- package/dist/oauth/redirect.js.map +0 -1
- package/dist/oauth/resource.d.ts +0 -7
- package/dist/oauth/resource.d.ts.map +0 -1
- package/dist/oauth/resource.js +0 -44
- package/dist/oauth/resource.js.map +0 -1
- package/dist/oauth/revoke.d.ts.map +0 -1
- package/dist/oauth/revoke.js.map +0 -1
- package/dist/oauth/scope.d.ts +0 -11
- package/dist/oauth/scope.d.ts.map +0 -1
- package/dist/oauth/scope.js +0 -24
- package/dist/oauth/scope.js.map +0 -1
- package/dist/oauth/token.d.ts +0 -3
- package/dist/oauth/token.d.ts.map +0 -1
- package/dist/oauth/token.js.map +0 -1
- package/dist/oauth/wellknown.d.ts.map +0 -1
- package/dist/oauth/wellknown.js.map +0 -1
- package/dist/protocol.d.ts +0 -4
- package/dist/protocol.d.ts.map +0 -1
- package/dist/protocol.js +0 -14
- package/dist/protocol.js.map +0 -1
- package/dist/registry.d.ts +0 -37
- package/dist/registry.d.ts.map +0 -1
- package/dist/registry.js +0 -69
- package/dist/registry.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -97
- package/dist/tools.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js +0 -130
- package/dist/validate.js.map +0 -1
- /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The built-in consent interstitial, used when the host supplies no `consent.render`. */
|
|
2
|
+
import type { ConsentRequest } from "../types.js";
|
|
3
|
+
/** Built-in hardened interstitial — used whenever the host doesn't supply `consent.render`. */
|
|
4
|
+
export declare const renderBuiltInConsent: (input: ConsentRequest) => Response;
|
|
5
|
+
//# sourceMappingURL=consent-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-page.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent-page.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAE1F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA+BlD,+FAA+F;AAC/F,eAAO,MAAM,oBAAoB,GAAI,OAAO,cAAc,KAAG,QAmC5D,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** The built-in consent interstitial, used when the host supplies no `consent.render`. */
|
|
2
|
+
const ESCAPE_HTML = {
|
|
3
|
+
"&": "&",
|
|
4
|
+
"<": "<",
|
|
5
|
+
">": ">",
|
|
6
|
+
'"': """,
|
|
7
|
+
"'": "'",
|
|
8
|
+
};
|
|
9
|
+
const escapeHtml = (value) => value.replace(/[&<>"']/g, (ch) => ESCAPE_HTML[ch] ?? ch);
|
|
10
|
+
/**
|
|
11
|
+
* `form-action` governs where a form (and the redirect chain it triggers)
|
|
12
|
+
* may end up — not just the immediate POST target. Chromium enforces it on
|
|
13
|
+
* the 302 this page's own submission produces, so `'self'` alone blocks the
|
|
14
|
+
* approve/deny POST to `${oauthPath}/consent` from ever reaching a
|
|
15
|
+
* `redirectUri` on another origin (the common case — Claude, Gemini,
|
|
16
|
+
* loopback clients). `redirectUri` is already validated by `authorize.ts`
|
|
17
|
+
* before this renders, so its origin is safe to add here.
|
|
18
|
+
*/
|
|
19
|
+
const consentSecurityHeaders = (redirectUri) => ({
|
|
20
|
+
"Content-Security-Policy": `default-src 'none'; style-src 'unsafe-inline'; ` +
|
|
21
|
+
`form-action 'self' ${new URL(redirectUri).origin}; frame-ancestors 'none'`,
|
|
22
|
+
"X-Frame-Options": "DENY",
|
|
23
|
+
"Referrer-Policy": "no-referrer",
|
|
24
|
+
"Cache-Control": "no-store",
|
|
25
|
+
});
|
|
26
|
+
/** Built-in hardened interstitial — used whenever the host doesn't supply `consent.render`. */
|
|
27
|
+
export const renderBuiltInConsent = (input) => {
|
|
28
|
+
const html = `<!doctype html>
|
|
29
|
+
<html lang="en">
|
|
30
|
+
<head>
|
|
31
|
+
<meta charset="utf-8">
|
|
32
|
+
<title>Authorize access</title>
|
|
33
|
+
<style>
|
|
34
|
+
body { font: 16px system-ui, sans-serif; max-width: 32rem; margin: 3rem auto; padding: 0 1rem; }
|
|
35
|
+
dl { display: grid; grid-template-columns: auto 1fr; gap: 0.25rem 1rem; }
|
|
36
|
+
dt { font-weight: 600; }
|
|
37
|
+
button { font-size: 1rem; padding: 0.5rem 1.25rem; margin-right: 0.5rem; }
|
|
38
|
+
</style>
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
41
|
+
<h1>Authorize access</h1>
|
|
42
|
+
<p><strong>${escapeHtml(input.clientId)}</strong> is requesting access to your account.</p>
|
|
43
|
+
<dl>
|
|
44
|
+
<dt>Redirect URI</dt><dd>${escapeHtml(input.redirectUri)}</dd>
|
|
45
|
+
<dt>Scope</dt><dd>${escapeHtml(input.scope.join(" ") || "(none)")}</dd>
|
|
46
|
+
<dt>Resource</dt><dd>${escapeHtml(input.resource)}</dd>
|
|
47
|
+
</dl>
|
|
48
|
+
<form method="POST" action="${escapeHtml(input.oauthPath)}/consent">
|
|
49
|
+
<input type="hidden" name="consent_ticket" value="${escapeHtml(input.ticket)}">
|
|
50
|
+
<button type="submit" name="approved" value="true">Allow</button>
|
|
51
|
+
<button type="submit" name="approved" value="false">Deny</button>
|
|
52
|
+
</form>
|
|
53
|
+
</body>
|
|
54
|
+
</html>`;
|
|
55
|
+
return new Response(html, {
|
|
56
|
+
status: 200,
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
59
|
+
...consentSecurityHeaders(input.redirectUri),
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=consent-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent-page.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent-page.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAI1F,MAAM,WAAW,GAA2B;IAC1C,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAC3C,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;AAE3D;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG,CAAC,WAAmB,EAA0B,EAAE,CAAC,CAAC;IAC/E,yBAAyB,EACvB,iDAAiD;QACjD,sBAAsB,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,0BAA0B;IAC7E,iBAAiB,EAAE,MAAM;IACzB,iBAAiB,EAAE,aAAa;IAChC,eAAe,EAAE,UAAU;CAC5B,CAAC,CAAC;AAEH,+FAA+F;AAC/F,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,KAAqB,EAAY,EAAE;IACtE,MAAM,IAAI,GAAG;;;;;;;;;;;;;;aAcF,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;;6BAEV,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC;sBACpC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;yBAC1C,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;;8BAErB,UAAU,CAAC,KAAK,CAAC,SAAS,CAAC;sDACH,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC;;;;;QAKtE,CAAC;IACP,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,GAAG;QACX,OAAO,EAAE;YACP,cAAc,EAAE,0BAA0B;YAC1C,GAAG,sBAAsB,CAAC,KAAK,CAAC,WAAW,CAAC;SAC7C;KACF,CAAC,CAAC;AACL,CAAC,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consent step between a resolved user and code issuance (RFC 6749
|
|
3
|
+
* §4.1.1 "the resource owner grants or denies the ... request").
|
|
4
|
+
*
|
|
5
|
+
* `/authorize` seals everything the eventual `/consent` POST needs into an
|
|
6
|
+
* opaque, single-use ticket — the ticket carries `clientId`, `redirectUri`,
|
|
7
|
+
* `codeChallenge`, `resource`, and `scope` from the *validated* request, so
|
|
8
|
+
* `handleConsent` trusts nothing from the POST body except which ticket and
|
|
9
|
+
* whether the user approved it. Nothing else in that body is read.
|
|
10
|
+
*/
|
|
11
|
+
import type { ConsentRequest, OAuthRouterOptions, RegisteredClient, ResolvedOAuthRouterOptions } from "../types.js";
|
|
12
|
+
type ConsentTicketPayload = {
|
|
13
|
+
clientId: string;
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
codeChallenge: string;
|
|
16
|
+
userId: string;
|
|
17
|
+
resource: string;
|
|
18
|
+
scope: string;
|
|
19
|
+
state: string;
|
|
20
|
+
exp: number;
|
|
21
|
+
jti: string;
|
|
22
|
+
};
|
|
23
|
+
export declare const issueConsentTicket: (secret: string, payload: Omit<ConsentTicketPayload, "exp" | "jti">) => Promise<string>;
|
|
24
|
+
export declare const renderConsent: (options: OAuthRouterOptions, input: ConsentRequest) => Promise<Response>;
|
|
25
|
+
/** Only honored when `clientStore` actually resolves the id — never a self-invented one. */
|
|
26
|
+
export declare const isPreApproved: (options: OAuthRouterOptions, clientId: string, registered: RegisteredClient | null) => boolean;
|
|
27
|
+
export declare const handleConsent: (request: Request, options: ResolvedOAuthRouterOptions) => Promise<Response>;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=consent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AASH,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAElB,gBAAgB,EAChB,0BAA0B,EAC3B,MAAM,aAAa,CAAC;AAMrB,KAAK,oBAAoB,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,SAAS,IAAI,CAAC,oBAAoB,EAAE,KAAK,GAAG,KAAK,CAAC,KACjD,OAAO,CAAC,MAAM,CAKkB,CAAC;AAEpC,eAAO,MAAM,aAAa,GACxB,SAAS,kBAAkB,EAC3B,OAAO,cAAc,KACpB,OAAO,CAAC,QAAQ,CAA6D,CAAC;AAEjF,4FAA4F;AAC5F,eAAO,MAAM,aAAa,GACxB,SAAS,kBAAkB,EAC3B,UAAU,MAAM,EAChB,YAAY,gBAAgB,GAAG,IAAI,KAClC,OAC0F,CAAC;AAyD9F,eAAO,MAAM,aAAa,GACxB,SAAS,OAAO,EAChB,SAAS,0BAA0B,KAClC,OAAO,CAAC,QAAQ,CAmClB,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The consent step between a resolved user and code issuance (RFC 6749
|
|
3
|
+
* §4.1.1 "the resource owner grants or denies the ... request").
|
|
4
|
+
*
|
|
5
|
+
* `/authorize` seals everything the eventual `/consent` POST needs into an
|
|
6
|
+
* opaque, single-use ticket — the ticket carries `clientId`, `redirectUri`,
|
|
7
|
+
* `codeChallenge`, `resource`, and `scope` from the *validated* request, so
|
|
8
|
+
* `handleConsent` trusts nothing from the POST body except which ticket and
|
|
9
|
+
* whether the user approved it. Nothing else in that body is read.
|
|
10
|
+
*/
|
|
11
|
+
import { NO_CORS, POST_ONLY, requireHttpMethod } from "../../http/http.js";
|
|
12
|
+
import { auditLegacyKeyUsage } from "../audit.js";
|
|
13
|
+
import { oauthError, resolveSecrets } from "../config.js";
|
|
14
|
+
import { OAUTH_ERRORS } from "../constants.js";
|
|
15
|
+
import { issueAuthCode } from "../crypto/codes.js";
|
|
16
|
+
import { seal, unsealAny } from "../crypto/sealed.js";
|
|
17
|
+
import { readBodyOrError, readOAuthBody } from "../policy/reqbody.js";
|
|
18
|
+
import { codeRedirect, errorRedirect } from "./callback.js";
|
|
19
|
+
import { renderBuiltInConsent } from "./consent-page.js";
|
|
20
|
+
const CONSENT_TICKET_TTL_MS = 300_000;
|
|
21
|
+
export const issueConsentTicket = (secret, payload) => seal(secret, "consent", {
|
|
22
|
+
...payload,
|
|
23
|
+
exp: Date.now() + CONSENT_TICKET_TTL_MS,
|
|
24
|
+
jti: crypto.randomUUID(),
|
|
25
|
+
});
|
|
26
|
+
export const renderConsent = async (options, input) => (options.consent?.render ?? renderBuiltInConsent)(input);
|
|
27
|
+
/** Only honored when `clientStore` actually resolves the id — never a self-invented one. */
|
|
28
|
+
export const isPreApproved = (options, clientId, registered) => Boolean(registered) && (options.consent?.preApprovedClientIds?.includes(clientId) ?? false);
|
|
29
|
+
/** Unseals `ticket`, checks its expiry/jti shape, and audits a legacy-key unseal. */
|
|
30
|
+
const readValidTicket = async (request, options, ticket) => {
|
|
31
|
+
const secrets = await resolveSecrets(options.ports, request);
|
|
32
|
+
const unsealed = await unsealAny(secrets, "consent", ticket);
|
|
33
|
+
const payload = unsealed?.value ?? null;
|
|
34
|
+
if (!payload ||
|
|
35
|
+
typeof payload.exp !== "number" ||
|
|
36
|
+
payload.exp < Date.now() ||
|
|
37
|
+
typeof payload.jti !== "string" ||
|
|
38
|
+
!payload.jti) {
|
|
39
|
+
return oauthError(OAUTH_ERRORS.invalidGrant, 400, "invalid or expired consent_ticket");
|
|
40
|
+
}
|
|
41
|
+
await auditLegacyKeyUsage(options, {
|
|
42
|
+
clientId: payload.clientId,
|
|
43
|
+
keyIndex: unsealed?.keyIndex,
|
|
44
|
+
source: "consent ticket unsealed",
|
|
45
|
+
});
|
|
46
|
+
return { payload, secrets };
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* The resolved user, once the ticket is confirmed as theirs and burned.
|
|
50
|
+
* Consumed only after the user check — a POST from the wrong session must
|
|
51
|
+
* not burn the rightful user's ticket — but before either outcome (code or
|
|
52
|
+
* denial), so single-use holds. Auth codes share this store; the `ct:`
|
|
53
|
+
* prefix keeps a ticket's jti apart from a code's.
|
|
54
|
+
*/
|
|
55
|
+
const consumeTicketForUser = async (request, options, payload) => {
|
|
56
|
+
const user = await options.ports.resolveUser(request);
|
|
57
|
+
if (!user || user.id !== payload.userId) {
|
|
58
|
+
return oauthError(OAUTH_ERRORS.invalidGrant, 400, "consent_ticket does not match the current user");
|
|
59
|
+
}
|
|
60
|
+
const fresh = await options.ports.codeStore.consume(`ct:${payload.jti}`, payload.exp);
|
|
61
|
+
return fresh ? user : oauthError(OAUTH_ERRORS.invalidGrant, 400, "consent_ticket already used");
|
|
62
|
+
};
|
|
63
|
+
export const handleConsent = async (request, options) => {
|
|
64
|
+
const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
|
|
65
|
+
if (methodError)
|
|
66
|
+
return methodError;
|
|
67
|
+
const body = await readBodyOrError(() => readOAuthBody(request));
|
|
68
|
+
if (body instanceof Response)
|
|
69
|
+
return body;
|
|
70
|
+
const ticket = body.consent_ticket ?? "";
|
|
71
|
+
if (!ticket) {
|
|
72
|
+
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "consent_ticket required");
|
|
73
|
+
}
|
|
74
|
+
const resolved = await readValidTicket(request, options, ticket);
|
|
75
|
+
if (resolved instanceof Response)
|
|
76
|
+
return resolved;
|
|
77
|
+
const { payload, secrets } = resolved;
|
|
78
|
+
const user = await consumeTicketForUser(request, options, payload);
|
|
79
|
+
if (user instanceof Response)
|
|
80
|
+
return user;
|
|
81
|
+
const callback = {
|
|
82
|
+
redirectUri: payload.redirectUri,
|
|
83
|
+
state: payload.state,
|
|
84
|
+
issuer: new URL(request.url).origin,
|
|
85
|
+
};
|
|
86
|
+
if (body.approved !== "true")
|
|
87
|
+
return errorRedirect(callback, OAUTH_ERRORS.accessDenied);
|
|
88
|
+
const code = await issueAuthCode(secrets[0], {
|
|
89
|
+
clientId: payload.clientId,
|
|
90
|
+
redirectUri: payload.redirectUri,
|
|
91
|
+
codeChallenge: payload.codeChallenge,
|
|
92
|
+
userId: user.id,
|
|
93
|
+
resource: payload.resource,
|
|
94
|
+
scope: payload.scope,
|
|
95
|
+
});
|
|
96
|
+
return codeRedirect(callback, code);
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/consent.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type GrantType, type TokenEndpointAuthMethod } from "
|
|
2
|
-
export type { GrantType } from "
|
|
1
|
+
import { type GrantType, type TokenEndpointAuthMethod } from "../constants.js";
|
|
2
|
+
export type { GrantType } from "../constants.js";
|
|
3
3
|
export type ProtectedResourceMetadata = {
|
|
4
4
|
resource: string;
|
|
5
5
|
authorization_servers: string[];
|
|
@@ -22,22 +22,28 @@ export type AuthorizationServerMetadata = {
|
|
|
22
22
|
/** Omitted when `revokeToken` is not configured. */
|
|
23
23
|
revocation_endpoint?: string;
|
|
24
24
|
revocation_endpoint_auth_methods_supported?: TokenEndpointAuthMethod[];
|
|
25
|
+
/** RFC 9207 — authorize redirect includes `iss`. */
|
|
26
|
+
authorization_response_iss_parameter_supported?: boolean;
|
|
27
|
+
/** Client ID Metadata Documents (MCP 2026-07-28). */
|
|
28
|
+
client_id_metadata_document_supported?: boolean;
|
|
25
29
|
};
|
|
26
30
|
export type MetadataOptions = {
|
|
27
31
|
origin: string;
|
|
28
32
|
/** MCP resource path, e.g. "/mcp". */
|
|
29
|
-
resourcePath?: string;
|
|
33
|
+
resourcePath?: string | undefined;
|
|
30
34
|
/** OAuth path prefix, e.g. "/mcp/oauth". */
|
|
31
|
-
oauthPath?: string;
|
|
35
|
+
oauthPath?: string | undefined;
|
|
32
36
|
/** Only advertise grants that are actually handled. */
|
|
33
37
|
grantTypes: GrantType[];
|
|
34
|
-
scopes?: string[];
|
|
38
|
+
scopes?: string[] | undefined;
|
|
35
39
|
/** Only advertise auth methods the configured clients actually use. */
|
|
36
|
-
tokenEndpointAuthMethods?: TokenEndpointAuthMethod[];
|
|
40
|
+
tokenEndpointAuthMethods?: TokenEndpointAuthMethod[] | undefined;
|
|
37
41
|
/** Advertise `registration_endpoint`. Default true. */
|
|
38
|
-
dcrEnabled?: boolean;
|
|
42
|
+
dcrEnabled?: boolean | undefined;
|
|
39
43
|
/** Advertise `revocation_endpoint`. Default false. */
|
|
40
|
-
revocationEnabled?: boolean;
|
|
44
|
+
revocationEnabled?: boolean | undefined;
|
|
45
|
+
/** Advertise CIMD support. Default false. */
|
|
46
|
+
cimdSupported?: boolean | undefined;
|
|
41
47
|
};
|
|
42
48
|
export declare const protectedResourceMetadata: (options: MetadataOptions) => ProtectedResourceMetadata;
|
|
43
49
|
export declare const authorizationServerMetadata: (options: MetadataOptions) => AuthorizationServerMetadata;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/metadata.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,SAAS,EAEd,KAAK,uBAAuB,EAC7B,MAAM,iBAAiB,CAAC;AAGzB,YAAY,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB,EAAE,MAAM,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,gCAAgC,EAAE,MAAM,EAAE,CAAC;IAC3C,qCAAqC,EAAE,uBAAuB,EAAE,CAAC;IACjE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,kEAAkE;IAClE,4BAA4B,EAAE,OAAO,CAAC;IACtC,oDAAoD;IACpD,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0CAA0C,CAAC,EAAE,uBAAuB,EAAE,CAAC;IACvE,oDAAoD;IACpD,8CAA8C,CAAC,EAAE,OAAO,CAAC;IACzD,qDAAqD;IACrD,qCAAqC,CAAC,EAAE,OAAO,CAAC;CACjD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,uDAAuD;IACvD,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9B,uEAAuE;IACvE,wBAAwB,CAAC,EAAE,uBAAuB,EAAE,GAAG,SAAS,CAAC;IACjE,uDAAuD;IACvD,UAAU,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACjC,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACxC,6CAA6C;IAC7C,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACrC,CAAC;AAEF,eAAO,MAAM,yBAAyB,GAAI,SAAS,eAAe,KAAG,yBAKnE,CAAC;AAEH,eAAO,MAAM,2BAA2B,GACtC,SAAS,eAAe,KACvB,2BA2BF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,MAAM,EACd,qBAAoC,EACpC,cAAa,KACZ,MAIC,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { wwwAuthenticateHeader } from "../../auth/bearer.js";
|
|
2
|
+
import { DEFAULT_OAUTH_PATH, DEFAULT_SCOPE, TOKEN_ENDPOINT_AUTH_METHODS, } from "../constants.js";
|
|
3
|
+
import { canonicalResource, DEFAULT_RESOURCE_PATH } from "../policy/resource.js";
|
|
3
4
|
export const protectedResourceMetadata = (options) => ({
|
|
4
5
|
resource: canonicalResource(options.origin, options.resourcePath),
|
|
5
6
|
authorization_servers: [options.origin],
|
|
@@ -7,7 +8,7 @@ export const protectedResourceMetadata = (options) => ({
|
|
|
7
8
|
scopes_supported: options.scopes ?? [DEFAULT_SCOPE],
|
|
8
9
|
});
|
|
9
10
|
export const authorizationServerMetadata = (options) => {
|
|
10
|
-
const oauthPath = options.oauthPath ??
|
|
11
|
+
const oauthPath = options.oauthPath ?? DEFAULT_OAUTH_PATH;
|
|
11
12
|
const tokenEndpointAuthMethods = options.tokenEndpointAuthMethods ?? [
|
|
12
13
|
TOKEN_ENDPOINT_AUTH_METHODS.none,
|
|
13
14
|
];
|
|
@@ -30,7 +31,12 @@ export const authorizationServerMetadata = (options) => {
|
|
|
30
31
|
token_endpoint_auth_methods_supported: tokenEndpointAuthMethods,
|
|
31
32
|
scopes_supported: options.scopes ?? [DEFAULT_SCOPE],
|
|
32
33
|
resource_parameter_supported: true,
|
|
34
|
+
authorization_response_iss_parameter_supported: true,
|
|
35
|
+
...(options.cimdSupported ? { client_id_metadata_document_supported: true } : {}),
|
|
33
36
|
};
|
|
34
37
|
};
|
|
35
|
-
export const mcpWwwAuthenticate = (origin, resourcePath = DEFAULT_RESOURCE_PATH, realm = "mcp") =>
|
|
38
|
+
export const mcpWwwAuthenticate = (origin, resourcePath = DEFAULT_RESOURCE_PATH, realm = "mcp") => wwwAuthenticateHeader({
|
|
39
|
+
realm,
|
|
40
|
+
resourceMetadataUrl: `${origin}/.well-known/oauth-protected-resource${resourcePath}`,
|
|
41
|
+
});
|
|
36
42
|
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,kBAAkB,EAClB,aAAa,EAEb,2BAA2B,GAE5B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAoDjF,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,OAAwB,EAA6B,EAAE,CAAC,CAAC;IACjG,QAAQ,EAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC;IACjE,qBAAqB,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IACvC,wBAAwB,EAAE,CAAC,QAAQ,CAAC;IACpC,gBAAgB,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC;CACpD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAAwB,EACK,EAAE;IAC/B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC;IAC1D,MAAM,wBAAwB,GAAG,OAAO,CAAC,wBAAwB,IAAI;QACnE,2BAA2B,CAAC,IAAI;KACjC,CAAC;IACF,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,sBAAsB,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,YAAY;QACjE,cAAc,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,QAAQ;QACrD,GAAG,CAAC,OAAO,CAAC,UAAU,KAAK,KAAK;YAC9B,CAAC,CAAC,EAAE,qBAAqB,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,WAAW,EAAE;YACrE,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,OAAO,CAAC,iBAAiB;YAC3B,CAAC,CAAC;gBACE,mBAAmB,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,SAAS;gBAC3D,0CAA0C,EAAE,wBAAwB;aACrE;YACH,CAAC,CAAC,EAAE,CAAC;QACP,wBAAwB,EAAE,CAAC,MAAM,CAAC;QAClC,qBAAqB,EAAE,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC;QAC9C,gCAAgC,EAAE,CAAC,MAAM,CAAC;QAC1C,qCAAqC,EAAE,wBAAwB;QAC/D,gBAAgB,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,aAAa,CAAC;QACnD,4BAA4B,EAAE,IAAI;QAClC,8CAA8C,EAAE,IAAI;QACpD,GAAG,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,qCAAqC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,MAAc,EACd,YAAY,GAAG,qBAAqB,EACpC,KAAK,GAAG,KAAK,EACL,EAAE,CACV,qBAAqB,CAAC;IACpB,KAAK;IACL,mBAAmB,EAAE,GAAG,MAAM,wCAAwC,YAAY,EAAE;CACrF,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 7591 Dynamic Client Registration — `POST ${oauthPath}/register`.
|
|
3
|
+
*
|
|
4
|
+
* Zero storage: the issued `client_id` is a sealed assertion of the validated
|
|
5
|
+
* `redirect_uris` (see `sealed.ts`). Unauthenticated callers can still mint
|
|
6
|
+
* their own id; consent + `requireRegisteredClients` address impersonation.
|
|
7
|
+
*/
|
|
8
|
+
import type { OAuthRouterOptions } from "../types.js";
|
|
9
|
+
export declare const handleRegister: (request: Request, options: OAuthRouterOptions) => Promise<Response>;
|
|
10
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/register.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,OAAO,KAAK,EAAmB,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAsFvE,eAAO,MAAM,cAAc,GACzB,SAAS,OAAO,EAChB,SAAS,kBAAkB,KAC1B,OAAO,CAAC,QAAQ,CAgBlB,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 7591 Dynamic Client Registration — `POST ${oauthPath}/register`.
|
|
3
|
+
*
|
|
4
|
+
* Zero storage: the issued `client_id` is a sealed assertion of the validated
|
|
5
|
+
* `redirect_uris` (see `sealed.ts`). Unauthenticated callers can still mint
|
|
6
|
+
* their own id; consent + `requireRegisteredClients` address impersonation.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_OAUTH_BODY_LIMIT, readBoundedText } from "../../http/body.js";
|
|
9
|
+
import { jsonResponse, POST_ONLY, requireHttpMethod } from "../../http/http.js";
|
|
10
|
+
import { isJsonObject } from "../../util/json.js";
|
|
11
|
+
import { oauthError, resolveSecrets } from "../config.js";
|
|
12
|
+
import { GRANT_TYPES, MAX_CLIENT_REDIRECT_URIS, OAUTH_ERRORS } from "../constants.js";
|
|
13
|
+
import { seal } from "../crypto/sealed.js";
|
|
14
|
+
import { CLAUDE_CALLBACK, isAllowedRedirectUri } from "../policy/redirect.js";
|
|
15
|
+
import { readBodyOrError } from "../policy/reqbody.js";
|
|
16
|
+
/**
|
|
17
|
+
* Malformed JSON or a non-object body is an error, never "no redirect_uris
|
|
18
|
+
* supplied" — that would silently register the Claude-callback default for
|
|
19
|
+
* a request that was never understood.
|
|
20
|
+
*/
|
|
21
|
+
const parseRegisterBody = (text) => {
|
|
22
|
+
let parsed;
|
|
23
|
+
try {
|
|
24
|
+
parsed = JSON.parse(text);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
parsed = undefined;
|
|
28
|
+
}
|
|
29
|
+
return isJsonObject(parsed)
|
|
30
|
+
? parsed
|
|
31
|
+
: oauthError(OAUTH_ERRORS.invalidClientMetadata, 400, "request body must be a JSON object");
|
|
32
|
+
};
|
|
33
|
+
/** The allowed, non-empty `redirect_uris` to seal, or the error explaining why there are none. */
|
|
34
|
+
const resolveRegisterRedirectUris = (body, options) => {
|
|
35
|
+
const raw = body.redirect_uris;
|
|
36
|
+
if (raw === undefined) {
|
|
37
|
+
// Fall back to the Claude callback only if this server's own allowlist
|
|
38
|
+
// accepts it — never advertise a redirect_uri `/authorize` would reject
|
|
39
|
+
// (e.g. `clients` without `"claude"`, where `allowClaude` is false).
|
|
40
|
+
return isAllowedRedirectUri(CLAUDE_CALLBACK, options.redirect)
|
|
41
|
+
? [CLAUDE_CALLBACK]
|
|
42
|
+
: oauthError(OAUTH_ERRORS.invalidRedirectUri, 400, "redirect_uris required — this server has no default redirect_uri");
|
|
43
|
+
}
|
|
44
|
+
if (!Array.isArray(raw)) {
|
|
45
|
+
return oauthError(OAUTH_ERRORS.invalidClientMetadata, 400, "redirect_uris must be an array");
|
|
46
|
+
}
|
|
47
|
+
if (raw.length > MAX_CLIENT_REDIRECT_URIS) {
|
|
48
|
+
return oauthError(OAUTH_ERRORS.invalidClientMetadata, 400, `redirect_uris must have at most ${MAX_CLIENT_REDIRECT_URIS} entries`);
|
|
49
|
+
}
|
|
50
|
+
const allowed = raw.map(String).filter((uri) => isAllowedRedirectUri(uri, options.redirect));
|
|
51
|
+
return allowed.length > 0
|
|
52
|
+
? allowed
|
|
53
|
+
: oauthError(OAUTH_ERRORS.invalidRedirectUri, 400, "no allowed redirect_uris");
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Zero storage: the client_id *is* the registration record — a sealed,
|
|
57
|
+
* self-verifying assertion of the redirect_uris just validated, which
|
|
58
|
+
* `/authorize` binds the client to. Always sealed with the primary
|
|
59
|
+
* `codeSecret`, so new ids verify with the fewest keys going forward.
|
|
60
|
+
*/
|
|
61
|
+
const issueRegistration = async (request, options, redirectUris) => {
|
|
62
|
+
const secrets = await resolveSecrets(options.ports, request);
|
|
63
|
+
const issuedAt = Math.floor(Date.now() / 1000);
|
|
64
|
+
const clientId = await seal(secrets[0], "client", {
|
|
65
|
+
redirectUris,
|
|
66
|
+
iat: issuedAt,
|
|
67
|
+
});
|
|
68
|
+
// RFC 7591 §3.2.1: a successful registration is 201 Created.
|
|
69
|
+
return jsonResponse({
|
|
70
|
+
status: 201,
|
|
71
|
+
data: {
|
|
72
|
+
client_id: clientId,
|
|
73
|
+
client_id_issued_at: issuedAt,
|
|
74
|
+
token_endpoint_auth_method: "none",
|
|
75
|
+
redirect_uris: redirectUris,
|
|
76
|
+
grant_types: [GRANT_TYPES.authorizationCode],
|
|
77
|
+
response_types: ["code"],
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
export const handleRegister = async (request, options) => {
|
|
82
|
+
const methodError = requireHttpMethod(request, POST_ONLY);
|
|
83
|
+
if (methodError)
|
|
84
|
+
return methodError;
|
|
85
|
+
const text = await readBodyOrError(() => readBoundedText(request, DEFAULT_OAUTH_BODY_LIMIT), OAUTH_ERRORS.invalidClientMetadata);
|
|
86
|
+
if (text instanceof Response)
|
|
87
|
+
return text;
|
|
88
|
+
const body = parseRegisterBody(text);
|
|
89
|
+
if (body instanceof Response)
|
|
90
|
+
return body;
|
|
91
|
+
const redirectUris = resolveRegisterRedirectUris(body, options);
|
|
92
|
+
if (redirectUris instanceof Response)
|
|
93
|
+
return redirectUris;
|
|
94
|
+
return issueRegistration(request, options, redirectUris);
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/register.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** RFC 7009 token revocation. */
|
|
2
|
-
import {
|
|
2
|
+
import type { OAuthRouterOptions } from "../types.js";
|
|
3
3
|
export declare const handleRevoke: (request: Request, options: OAuthRouterOptions) => Promise<Response>;
|
|
4
4
|
//# sourceMappingURL=revoke.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAOjC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAKtD,eAAO,MAAM,YAAY,GACvB,SAAS,OAAO,EAChB,SAAS,kBAAkB,KAC1B,OAAO,CAAC,QAAQ,CAuBlB,CAAC"}
|
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
/** RFC 7009 token revocation. */
|
|
2
|
-
import { requireHttpMethod } from "
|
|
3
|
-
import {
|
|
4
|
-
import { OAUTH_ERRORS } from "
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
const NO_CORS = { cors: false };
|
|
2
|
+
import { NO_CORS, POST_ONLY, requireHttpMethod } from "../../http/http.js";
|
|
3
|
+
import { oauthError } from "../config.js";
|
|
4
|
+
import { OAUTH_ERRORS } from "../constants.js";
|
|
5
|
+
import { firstClientAuthError, readClientAuth } from "../crypto/clientauth.js";
|
|
6
|
+
import { readBodyOrError, readOAuthBody } from "../policy/reqbody.js";
|
|
8
7
|
const tokenTypeHintOf = (hint) => hint === "access_token" || hint === "refresh_token" ? hint : undefined;
|
|
9
8
|
export const handleRevoke = async (request, options) => {
|
|
10
9
|
const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
|
|
11
10
|
if (methodError)
|
|
12
11
|
return methodError;
|
|
13
|
-
const body =
|
|
12
|
+
const body = await readBodyOrError(() => readOAuthBody(request));
|
|
13
|
+
if (body instanceof Response)
|
|
14
|
+
return body;
|
|
14
15
|
const token = body.token ?? "";
|
|
15
16
|
if (!token)
|
|
16
17
|
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "token required");
|
|
17
18
|
const auth = readClientAuth(request, body);
|
|
18
19
|
if (!auth)
|
|
19
20
|
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "client_id required");
|
|
20
|
-
const clientAuthError = await firstClientAuthError(auth, options);
|
|
21
|
+
const clientAuthError = await firstClientAuthError(auth, options, request);
|
|
21
22
|
if (clientAuthError)
|
|
22
23
|
return clientAuthError;
|
|
24
|
+
const hint = tokenTypeHintOf(body.token_type_hint);
|
|
23
25
|
await options.ports.revokeToken?.({
|
|
24
26
|
token,
|
|
25
27
|
clientId: auth.clientId,
|
|
26
|
-
tokenTypeHint:
|
|
28
|
+
...(hint !== undefined ? { tokenTypeHint: hint } : {}),
|
|
27
29
|
});
|
|
28
30
|
return new Response(null, { status: 200, headers: { "Cache-Control": "no-store" } });
|
|
29
31
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../../src/oauth/endpoints/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGtE,MAAM,eAAe,GAAG,CAAC,IAAwB,EAAgD,EAAE,CACjG,IAAI,KAAK,cAAc,IAAI,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAEzE,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,OAAgB,EAChB,OAA2B,EACR,EAAE;IACrB,MAAM,WAAW,GAAG,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IACnE,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IAEpC,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;IACjE,IAAI,IAAI,YAAY,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK;QAAE,OAAO,UAAU,CAAC,YAAY,CAAC,cAAc,EAAE,GAAG,EAAE,gBAAgB,CAAC,CAAC;IAElF,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,UAAU,CAAC,YAAY,CAAC,cAAc,EAAE,GAAG,EAAE,oBAAoB,CAAC,CAAC;IAErF,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC3E,IAAI,eAAe;QAAE,OAAO,eAAe,CAAC;IAE5C,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IACnD,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAChC,KAAK;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACvD,CAAC,CAAC;IAEH,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC;AACvF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/oauth/endpoints/token.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AA+G9D,eAAO,MAAM,WAAW,GACtB,SAAS,OAAO,EAChB,SAAS,0BAA0B,KAClC,OAAO,CAAC,QAAQ,CAgClB,CAAC"}
|
|
@@ -1,35 +1,14 @@
|
|
|
1
|
-
import { requireHttpMethod } from "
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { GRANT_TYPES, OAUTH_ERRORS } from "
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
13
|
-
throw new Error("malformed request body");
|
|
14
|
-
}
|
|
15
|
-
return Object.fromEntries(Object.entries(body)
|
|
16
|
-
.filter(([, value]) => value != null)
|
|
17
|
-
.map(([key, value]) => [key, String(value)]));
|
|
18
|
-
};
|
|
19
|
-
const readFormBody = async (request) => {
|
|
20
|
-
const params = new URLSearchParams(await request.text());
|
|
21
|
-
return Object.fromEntries([...params.entries()]);
|
|
22
|
-
};
|
|
23
|
-
const BODY_READERS = [
|
|
24
|
-
{ match: (ctype) => ctype.includes("application/json"), read: readJsonBody },
|
|
25
|
-
{ match: () => true, read: readFormBody },
|
|
26
|
-
];
|
|
27
|
-
const readTokenBody = async (request) => {
|
|
28
|
-
const ctype = request.headers.get("Content-Type") ?? "";
|
|
29
|
-
const reader = BODY_READERS.find((entry) => entry.match(ctype))?.read ?? readFormBody;
|
|
30
|
-
return reader(request);
|
|
31
|
-
};
|
|
32
|
-
const handleRefresh = async ({ body, options, expectedResource, clientId, }) => {
|
|
1
|
+
import { NO_CORS, POST_ONLY, requireHttpMethod } from "../../http/http.js";
|
|
2
|
+
import { auditLegacyKeyUsage } from "../audit.js";
|
|
3
|
+
import { advertisedScopes, oauthError, resolveSecrets, tokenResponse } from "../config.js";
|
|
4
|
+
import { GRANT_TYPES, OAUTH_ERRORS } from "../constants.js";
|
|
5
|
+
import { firstClientAuthError, readClientAuth } from "../crypto/clientauth.js";
|
|
6
|
+
import { consumeAuthCodeDetailed } from "../crypto/codes.js";
|
|
7
|
+
import { verifyPkceS256 } from "../crypto/pkce.js";
|
|
8
|
+
import { readBodyOrError, readOAuthBody } from "../policy/reqbody.js";
|
|
9
|
+
import { canonicalResource, firstResourceError, resourcesEqual } from "../policy/resource.js";
|
|
10
|
+
import { firstScopeError, formatScope, requestedScopes } from "../policy/scope.js";
|
|
11
|
+
const handleRefresh = async ({ body, options, expectedResource, clientId }) => {
|
|
33
12
|
if (!options.ports.refreshAccessToken) {
|
|
34
13
|
return oauthError(OAUTH_ERRORS.unsupportedGrantType, 400, "refresh_token not enabled");
|
|
35
14
|
}
|
|
@@ -40,15 +19,26 @@ const handleRefresh = async ({ body, options, expectedResource, clientId, }) =>
|
|
|
40
19
|
const resourceError = firstResourceError(body.resource ?? "", expectedResource);
|
|
41
20
|
if (resourceError)
|
|
42
21
|
return resourceError;
|
|
43
|
-
//
|
|
22
|
+
// RFC 6749 §6: client MAY request a reduced scope; omit → host keeps original.
|
|
23
|
+
let requestedScope;
|
|
24
|
+
if (body.scope !== undefined && body.scope !== "") {
|
|
25
|
+
const requested = requestedScopes(body.scope, []);
|
|
26
|
+
const scopeError = firstScopeError(requested, advertisedScopes(options));
|
|
27
|
+
if (scopeError)
|
|
28
|
+
return scopeError;
|
|
29
|
+
requestedScope = formatScope(requested);
|
|
30
|
+
}
|
|
31
|
+
// ponytail: AS cannot verify the refresh token's original audience/grant; the port owns those checks.
|
|
44
32
|
const minted = await options.ports.refreshAccessToken({
|
|
45
33
|
refreshToken,
|
|
46
34
|
clientId,
|
|
47
35
|
resource: expectedResource,
|
|
36
|
+
...(requestedScope !== undefined ? { scope: requestedScope } : {}),
|
|
48
37
|
});
|
|
49
38
|
if (!minted) {
|
|
50
39
|
return oauthError(OAUTH_ERRORS.invalidGrant, 400, "invalid refresh_token");
|
|
51
40
|
}
|
|
41
|
+
// Scope in the response must come from the port — not the client's request.
|
|
52
42
|
return tokenResponse(minted);
|
|
53
43
|
};
|
|
54
44
|
const firstAuthCodeMismatch = (record, body, clientId) => {
|
|
@@ -62,21 +52,21 @@ const firstAuthCodeMismatch = (record, body, clientId) => {
|
|
|
62
52
|
description: "client_id mismatch",
|
|
63
53
|
},
|
|
64
54
|
].find((entry) => !entry.ok);
|
|
65
|
-
return rule
|
|
66
|
-
? oauthError(OAUTH_ERRORS.invalidGrant, 400, rule.description)
|
|
67
|
-
: null;
|
|
55
|
+
return rule ? oauthError(OAUTH_ERRORS.invalidGrant, 400, rule.description) : null;
|
|
68
56
|
};
|
|
69
57
|
const handleAuthCode = async ({ request, body, options, expectedResource, clientId, }) => {
|
|
70
58
|
const resourceError = firstResourceError(body.resource ?? "", expectedResource);
|
|
71
59
|
if (resourceError)
|
|
72
60
|
return resourceError;
|
|
73
|
-
const
|
|
74
|
-
const
|
|
61
|
+
const secrets = await resolveSecrets(options.ports, request);
|
|
62
|
+
const consumed = await consumeAuthCodeDetailed(secrets, body.code ?? "", {
|
|
75
63
|
codeStore: options.ports.codeStore,
|
|
76
64
|
});
|
|
77
|
-
if (!
|
|
65
|
+
if (!consumed) {
|
|
78
66
|
return oauthError(OAUTH_ERRORS.invalidGrant, 400, "invalid or expired code");
|
|
79
67
|
}
|
|
68
|
+
const { record, keyIndex } = consumed;
|
|
69
|
+
await auditLegacyKeyUsage(options, { clientId, keyIndex, source: "auth code redeemed" });
|
|
80
70
|
const mismatch = firstAuthCodeMismatch(record, body, clientId);
|
|
81
71
|
if (mismatch)
|
|
82
72
|
return mismatch;
|
|
@@ -102,18 +92,14 @@ export const handleToken = async (request, options) => {
|
|
|
102
92
|
const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
|
|
103
93
|
if (methodError)
|
|
104
94
|
return methodError;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
body
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "malformed request body");
|
|
111
|
-
}
|
|
95
|
+
const body = await readBodyOrError(() => readOAuthBody(request));
|
|
96
|
+
if (body instanceof Response)
|
|
97
|
+
return body;
|
|
112
98
|
const auth = readClientAuth(request, body);
|
|
113
99
|
if (!auth) {
|
|
114
100
|
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "client_id required");
|
|
115
101
|
}
|
|
116
|
-
const clientAuthError = await firstClientAuthError(auth, options);
|
|
102
|
+
const clientAuthError = await firstClientAuthError(auth, options, request);
|
|
117
103
|
if (clientAuthError)
|
|
118
104
|
return clientAuthError;
|
|
119
105
|
const url = new URL(request.url);
|