mcp-trellis 1.0.0 → 2.2.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 +65 -93
- 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 +13 -30
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +55 -101
- 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 -89
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +143 -63
- 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 +19 -4
- package/dist/auth/bearer.js.map +1 -1
- package/dist/auth/signed-token.d.ts +26 -0
- package/dist/auth/signed-token.d.ts.map +1 -0
- package/dist/auth/signed-token.js +96 -0
- package/dist/auth/signed-token.js.map +1 -0
- package/dist/clients.js +1 -1
- 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/{body.d.ts → http/body.d.ts} +27 -6
- package/dist/http/body.d.ts.map +1 -0
- package/dist/{body.js → http/body.js} +33 -13
- 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/{dispatch.d.ts → mcp/dispatch.d.ts} +1 -1
- 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} +9 -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/{methods.d.ts → mcp/methods.d.ts} +60 -11
- 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/{protocol.d.ts → mcp/protocol.d.ts} +1 -3
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/{protocol.js → mcp/protocol.js} +1 -3
- 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} +20 -1
- 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} +18 -21
- package/dist/mcp/validate.d.ts.map +1 -0
- package/dist/{validate.js → mcp/validate.js} +59 -41
- 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 +11 -0
- package/dist/oauth/constants.d.ts.map +1 -1
- package/dist/oauth/constants.js +11 -0
- package/dist/oauth/constants.js.map +1 -1
- package/dist/oauth/{base64url.d.ts → crypto/base64url.d.ts} +0 -1
- package/dist/oauth/crypto/base64url.d.ts.map +1 -0
- package/dist/oauth/{base64url.js → crypto/base64url.js} +0 -1
- 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/{sealed.d.ts → crypto/sealed.d.ts} +12 -3
- package/dist/oauth/crypto/sealed.d.ts.map +1 -0
- package/dist/oauth/{sealed.js → crypto/sealed.js} +22 -14
- package/dist/oauth/crypto/sealed.js.map +1 -0
- package/dist/oauth/{secrethash.d.ts → crypto/secrethash.d.ts} +14 -0
- package/dist/oauth/crypto/secrethash.d.ts.map +1 -0
- package/dist/oauth/{secrethash.js → crypto/secrethash.js} +27 -11
- 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} +8 -2
- 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} +8 -18
- 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} +32 -25
- 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 -9
- package/dist/oauth/endpoints/wellknown.js.map +1 -0
- package/dist/oauth/policy/redirect.d.ts.map +1 -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/{resource.d.ts → policy/resource.d.ts} +1 -1
- package/dist/oauth/policy/resource.d.ts.map +1 -0
- package/dist/oauth/{resource.js → policy/resource.js} +2 -2
- package/dist/oauth/policy/resource.js.map +1 -0
- package/dist/oauth/{scope.d.ts → policy/scope.d.ts} +2 -2
- package/dist/oauth/policy/scope.d.ts.map +1 -0
- package/dist/oauth/{scope.js → policy/scope.js} +3 -3
- package/dist/oauth/policy/scope.js.map +1 -0
- package/dist/oauth/router.d.ts +10 -13
- package/dist/oauth/router.d.ts.map +1 -1
- package/dist/oauth/router.js +93 -115
- package/dist/oauth/router.js.map +1 -1
- package/dist/oauth/types.d.ts +99 -45
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js +1 -90
- package/dist/oauth/types.js.map +1 -1
- package/dist/server.d.ts +13 -12
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +10 -8
- 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 +37 -47
- package/docs/compatibility.md +86 -0
- package/docs/diagrams/demo-tool-result.png +0 -0
- package/docs/diagrams/mcp-trellis-architecture.html +305 -0
- package/docs/guide.md +46 -16
- package/docs/reference.md +55 -22
- package/docs/security.md +58 -9
- package/docs/troubleshooting.md +19 -0
- package/package.json +27 -10
- package/dist/adapters/origins.d.ts +0 -19
- package/dist/adapters/origins.d.ts.map +0 -1
- package/dist/adapters/origins.js +0 -52
- package/dist/adapters/origins.js.map +0 -1
- package/dist/body.d.ts.map +0 -1
- package/dist/body.js.map +0 -1
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -119
- 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 -18
- package/dist/jsonrpc.js.map +0 -1
- package/dist/methods.d.ts.map +0 -1
- package/dist/methods.js +0 -130
- 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 -141
- 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 -24
- package/dist/oauth/clientauth.d.ts.map +0 -1
- package/dist/oauth/clientauth.js +0 -119
- 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 -85
- package/dist/oauth/codes.js.map +0 -1
- package/dist/oauth/consent.d.ts +0 -65
- package/dist/oauth/consent.d.ts.map +0 -1
- package/dist/oauth/consent.js +0 -173
- package/dist/oauth/consent.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.map +0 -1
- package/dist/oauth/reqbody.d.ts +0 -10
- package/dist/oauth/reqbody.d.ts.map +0 -1
- package/dist/oauth/reqbody.js +0 -29
- package/dist/oauth/reqbody.js.map +0 -1
- package/dist/oauth/resource.d.ts.map +0 -1
- 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.map +0 -1
- package/dist/oauth/scope.js.map +0 -1
- package/dist/oauth/sealed.d.ts.map +0 -1
- package/dist/oauth/sealed.js.map +0 -1
- package/dist/oauth/secrethash.d.ts.map +0 -1
- package/dist/oauth/secrethash.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.map +0 -1
- 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 -77
- package/dist/registry.js.map +0 -1
- package/dist/tools.d.ts.map +0 -1
- package/dist/tools.js +0 -98
- package/dist/tools.js.map +0 -1
- package/dist/validate.d.ts.map +0 -1
- package/dist/validate.js.map +0 -1
- /package/dist/oauth/{pkce.d.ts → crypto/pkce.d.ts} +0 -0
- /package/dist/oauth/{redirect.d.ts → policy/redirect.d.ts} +0 -0
- /package/dist/oauth/{redirect.js → policy/redirect.js} +0 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** OAuth construction asserts and small response helpers. */
|
|
2
|
+
import { jsonResponse } from "../http/http.js";
|
|
3
|
+
import { DEFAULT_SCOPE } from "./constants.js";
|
|
4
|
+
/** Default true — see `OAuthRouterOptions.allowUnregisteredClients`. */
|
|
5
|
+
export const unregisteredClientsAllowed = (options) => options.allowUnregisteredClients !== false;
|
|
6
|
+
/** Default true since 1.0 — see `OAuthRouterOptions.requireRegisteredClients`. */
|
|
7
|
+
export const registeredClientsRequired = (options) => options.requireRegisteredClients !== false;
|
|
8
|
+
/** Scopes this AS advertises and is willing to grant. */
|
|
9
|
+
export const advertisedScopes = (options) => options.scopes ?? [DEFAULT_SCOPE];
|
|
10
|
+
/**
|
|
11
|
+
* Scopes granted when a client omits `scope`. Falls back to the full
|
|
12
|
+
* advertised set only when that set has at most one entry — see
|
|
13
|
+
* `assertScopeConfig`, which forbids the ambiguous multi-scope case at
|
|
14
|
+
* construction instead of silently over-granting here.
|
|
15
|
+
*/
|
|
16
|
+
export const defaultScopes = (options) => options.defaultScopes ?? advertisedScopes(options);
|
|
17
|
+
/**
|
|
18
|
+
* A multi-scope server MUST say what an omitted `scope` grants — the
|
|
19
|
+
* alternative (silently granting everything advertised) is exactly the
|
|
20
|
+
* escalation least-privilege scoping exists to prevent.
|
|
21
|
+
*
|
|
22
|
+
* This controls only what a *token* is granted at authorize time —
|
|
23
|
+
* `defaultScopes: []` still leaves any tool that omits `ToolDef.scope`
|
|
24
|
+
* callable by a principal holding zero scopes, since that check happens
|
|
25
|
+
* per tool-call, not here. `createMcpApp` has the matching guard on the
|
|
26
|
+
* tool side: it refuses to construct with an unscoped tool once `scopes`
|
|
27
|
+
* has more than one entry (`assertToolScopesConfigured` in `app.ts`).
|
|
28
|
+
*/
|
|
29
|
+
export const assertScopeConfig = (options) => {
|
|
30
|
+
const advertised = advertisedScopes(options);
|
|
31
|
+
if (advertised.length > 1 && !options.defaultScopes) {
|
|
32
|
+
throw new Error("scopes has more than one entry — defaultScopes must say what an omitted " +
|
|
33
|
+
"scope request grants (e.g. defaultScopes: [] for least privilege)");
|
|
34
|
+
}
|
|
35
|
+
const unknown = (options.defaultScopes ?? []).find((scope) => !advertised.includes(scope));
|
|
36
|
+
if (unknown !== undefined) {
|
|
37
|
+
throw new Error(`defaultScopes contains "${unknown}", which is not in scopes`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* `codeSecret` can forge an auth code for any userId/scope/resource, so a
|
|
42
|
+
* weak or copy-pasted one is a full authorization bypass, not a footgun.
|
|
43
|
+
* Copy-paste from this package's docs is the realistic failure mode: every
|
|
44
|
+
* published example secret is either under the length floor or carries
|
|
45
|
+
* the marker below (enforced by test/oauth/crypto/codesecret.test.ts).
|
|
46
|
+
*/
|
|
47
|
+
const MIN_CODE_SECRET_LENGTH = 32;
|
|
48
|
+
const DENYLISTED_CODE_SECRET_MARKER = "do-not-reuse";
|
|
49
|
+
const GENERATE_HINT = "generate one with `openssl rand -base64 32`";
|
|
50
|
+
export const assertCodeSecret = (secret) => {
|
|
51
|
+
// Checked before the length rule so a copied example is named for what it is.
|
|
52
|
+
if (secret.includes(DENYLISTED_CODE_SECRET_MARKER)) {
|
|
53
|
+
throw new Error(`codeSecret must not be a literal published in this package's own docs or examples — ${GENERATE_HINT}`);
|
|
54
|
+
}
|
|
55
|
+
if (secret.length < MIN_CODE_SECRET_LENGTH) {
|
|
56
|
+
throw new Error(`codeSecret must be at least ${MIN_CODE_SECRET_LENGTH} characters (got ${secret.length}) — ${GENERATE_HINT}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Resolves `codeSecret` to a non-empty key list — a single string becomes a
|
|
61
|
+
* one-element array. The **first** entry is the one every caller here uses
|
|
62
|
+
* to seal new material or hash new client secrets; the full array is what a
|
|
63
|
+
* caller trying to unseal/verify existing material tries in order (see
|
|
64
|
+
* `unsealAny`, `verifyClientSecretAny`). Every entry is validated the same
|
|
65
|
+
* way a lone string always was.
|
|
66
|
+
*/
|
|
67
|
+
export const resolveSecrets = async (ports, request) => {
|
|
68
|
+
const resolved = typeof ports.codeSecret === "function" ? await ports.codeSecret(request) : ports.codeSecret;
|
|
69
|
+
const secrets = Array.isArray(resolved) ? resolved : [resolved];
|
|
70
|
+
if (secrets.length === 0) {
|
|
71
|
+
throw new Error("codeSecret must resolve to at least one key, not an empty array");
|
|
72
|
+
}
|
|
73
|
+
secrets.forEach(assertCodeSecret);
|
|
74
|
+
// Cast is sound: the length check above guarantees at least one element.
|
|
75
|
+
return secrets;
|
|
76
|
+
};
|
|
77
|
+
export const oauthError = (error, status, description) => jsonResponse({
|
|
78
|
+
data: description ? { error, error_description: description } : { error },
|
|
79
|
+
status,
|
|
80
|
+
cors: false,
|
|
81
|
+
});
|
|
82
|
+
const UNREGISTERED_CLIENT_DESCRIPTION = "unknown client_id — this server only serves pre-registered clients";
|
|
83
|
+
/** Direct JSON error when this AS does not serve unknown `client_id`s. */
|
|
84
|
+
export const unregisteredClientError = (options, reject) => {
|
|
85
|
+
if (unregisteredClientsAllowed(options))
|
|
86
|
+
return null;
|
|
87
|
+
return oauthError(reject.code, reject.status, UNREGISTERED_CLIENT_DESCRIPTION);
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* `grantedScope` is what the auth code carried (or a narrowed refresh
|
|
91
|
+
* request); the port may narrow it further. With neither, `scope` is
|
|
92
|
+
* omitted (optional when unchanged, RFC 6749 §5.1) rather than guessed.
|
|
93
|
+
*/
|
|
94
|
+
export const tokenResponse = (minted, grantedScope) => {
|
|
95
|
+
const scope = minted.scope ?? grantedScope;
|
|
96
|
+
return jsonResponse({
|
|
97
|
+
data: {
|
|
98
|
+
access_token: minted.accessToken,
|
|
99
|
+
token_type: minted.tokenType ?? "bearer",
|
|
100
|
+
expires_in: minted.expiresIn,
|
|
101
|
+
...(scope !== undefined ? { scope } : {}),
|
|
102
|
+
...(minted.refreshToken ? { refresh_token: minted.refreshToken } : {}),
|
|
103
|
+
},
|
|
104
|
+
status: 200,
|
|
105
|
+
cors: false,
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/oauth/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,6 +8,8 @@ export declare const OAUTH_ERRORS: {
|
|
|
8
8
|
readonly invalidGrant: "invalid_grant";
|
|
9
9
|
readonly unsupportedGrantType: "unsupported_grant_type";
|
|
10
10
|
readonly invalidRedirectUri: "invalid_redirect_uri";
|
|
11
|
+
/** RFC 7591 §3.2.2 — DCR `/register`: a client metadata field's value itself is invalid. */
|
|
12
|
+
readonly invalidClientMetadata: "invalid_client_metadata";
|
|
11
13
|
/** Token endpoint — client authentication failed, or the client is unknown. */
|
|
12
14
|
readonly invalidClient: "invalid_client";
|
|
13
15
|
/** Authorization endpoint — this client may not use this method. RFC 6749 §4.1.2.1. */
|
|
@@ -33,4 +35,13 @@ export declare const TOKEN_ENDPOINT_AUTH_METHODS: {
|
|
|
33
35
|
};
|
|
34
36
|
export type TokenEndpointAuthMethod = (typeof TOKEN_ENDPOINT_AUTH_METHODS)[keyof typeof TOKEN_ENDPOINT_AUTH_METHODS];
|
|
35
37
|
export declare const DEFAULT_SCOPE = "mcp";
|
|
38
|
+
/** Default OAuth path prefix when `oauthPath` is unset. Router, `/authorize`, and metadata must agree. */
|
|
39
|
+
export declare const DEFAULT_OAUTH_PATH = "/mcp/oauth";
|
|
40
|
+
/**
|
|
41
|
+
* Cap on a client's redirect URIs, self-registered or from a CIMD document.
|
|
42
|
+
* RFC 7591 sets none, but `/register` seals the whole list into the
|
|
43
|
+
* `client_id`, which then travels as an `/authorize` query parameter with
|
|
44
|
+
* real length ceilings (browsers, proxies, logs). A client needs a handful.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MAX_CLIENT_REDIRECT_URIS = 10;
|
|
36
47
|
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/oauth/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW;;;CAGd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEvE,eAAO,MAAM,YAAY;;;;;IAKvB,+EAA+E;;IAE/E,uFAAuF;;IAEvF,kGAAkG;;IAElG,2DAA2D;;IAE3D,uDAAuD;;IAEvD,iFAAiF;;IAEjF,6DAA6D;;CAErD,CAAC;AAEX,eAAO,MAAM,2BAA2B;IACtC,8CAA8C;;IAE9C,+CAA+C;;IAE/C,uCAAuC;;CAE/B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,OAAO,2BAA2B,CAAC,CAAC;AAEjF,eAAO,MAAM,aAAa,QAAQ,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/oauth/constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,WAAW;;;CAGd,CAAC;AAEX,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC;AAEvE,eAAO,MAAM,YAAY;;;;;IAKvB,4FAA4F;;IAE5F,+EAA+E;;IAE/E,uFAAuF;;IAEvF,kGAAkG;;IAElG,2DAA2D;;IAE3D,uDAAuD;;IAEvD,iFAAiF;;IAEjF,6DAA6D;;CAErD,CAAC;AAEX,eAAO,MAAM,2BAA2B;IACtC,8CAA8C;;IAE9C,+CAA+C;;IAE/C,uCAAuC;;CAE/B,CAAC;AAEX,MAAM,MAAM,uBAAuB,GACjC,CAAC,OAAO,2BAA2B,CAAC,CAAC,MAAM,OAAO,2BAA2B,CAAC,CAAC;AAEjF,eAAO,MAAM,aAAa,QAAQ,CAAC;AAEnC,0GAA0G;AAC1G,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,KAAK,CAAC"}
|
package/dist/oauth/constants.js
CHANGED
|
@@ -7,6 +7,8 @@ export const OAUTH_ERRORS = {
|
|
|
7
7
|
invalidGrant: "invalid_grant",
|
|
8
8
|
unsupportedGrantType: "unsupported_grant_type",
|
|
9
9
|
invalidRedirectUri: "invalid_redirect_uri",
|
|
10
|
+
/** RFC 7591 §3.2.2 — DCR `/register`: a client metadata field's value itself is invalid. */
|
|
11
|
+
invalidClientMetadata: "invalid_client_metadata",
|
|
10
12
|
/** Token endpoint — client authentication failed, or the client is unknown. */
|
|
11
13
|
invalidClient: "invalid_client",
|
|
12
14
|
/** Authorization endpoint — this client may not use this method. RFC 6749 §4.1.2.1. */
|
|
@@ -31,4 +33,13 @@ export const TOKEN_ENDPOINT_AUTH_METHODS = {
|
|
|
31
33
|
post: "client_secret_post",
|
|
32
34
|
};
|
|
33
35
|
export const DEFAULT_SCOPE = "mcp";
|
|
36
|
+
/** Default OAuth path prefix when `oauthPath` is unset. Router, `/authorize`, and metadata must agree. */
|
|
37
|
+
export const DEFAULT_OAUTH_PATH = "/mcp/oauth";
|
|
38
|
+
/**
|
|
39
|
+
* Cap on a client's redirect URIs, self-registered or from a CIMD document.
|
|
40
|
+
* RFC 7591 sets none, but `/register` seals the whole list into the
|
|
41
|
+
* `client_id`, which then travels as an `/authorize` query parameter with
|
|
42
|
+
* real length ceilings (browsers, proxies, logs). A client needs a handful.
|
|
43
|
+
*/
|
|
44
|
+
export const MAX_CLIENT_REDIRECT_URIS = 10;
|
|
34
45
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/oauth/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iBAAiB,EAAE,oBAAoB;IACvC,YAAY,EAAE,eAAe;CACrB,CAAC;AAIX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc,EAAE,iBAAiB;IACjC,YAAY,EAAE,eAAe;IAC7B,oBAAoB,EAAE,wBAAwB;IAC9C,kBAAkB,EAAE,sBAAsB;IAC1C,+EAA+E;IAC/E,aAAa,EAAE,gBAAgB;IAC/B,uFAAuF;IACvF,kBAAkB,EAAE,qBAAqB;IACzC,kGAAkG;IAClG,uBAAuB,EAAE,2BAA2B;IACpD,2DAA2D;IAC3D,YAAY,EAAE,eAAe;IAC7B,uDAAuD;IACvD,aAAa,EAAE,gBAAgB;IAC/B,iFAAiF;IACjF,WAAW,EAAE,cAAc;IAC3B,6DAA6D;IAC7D,YAAY,EAAE,eAAe;CACrB,CAAC;AAEX,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,8CAA8C;IAC9C,IAAI,EAAE,MAAM;IACZ,+CAA+C;IAC/C,KAAK,EAAE,qBAAqB;IAC5B,uCAAuC;IACvC,IAAI,EAAE,oBAAoB;CAClB,CAAC;AAKX,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC"}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/oauth/constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,iBAAiB,EAAE,oBAAoB;IACvC,YAAY,EAAE,eAAe;CACrB,CAAC;AAIX,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,cAAc,EAAE,iBAAiB;IACjC,YAAY,EAAE,eAAe;IAC7B,oBAAoB,EAAE,wBAAwB;IAC9C,kBAAkB,EAAE,sBAAsB;IAC1C,4FAA4F;IAC5F,qBAAqB,EAAE,yBAAyB;IAChD,+EAA+E;IAC/E,aAAa,EAAE,gBAAgB;IAC/B,uFAAuF;IACvF,kBAAkB,EAAE,qBAAqB;IACzC,kGAAkG;IAClG,uBAAuB,EAAE,2BAA2B;IACpD,2DAA2D;IAC3D,YAAY,EAAE,eAAe;IAC7B,uDAAuD;IACvD,aAAa,EAAE,gBAAgB;IAC/B,iFAAiF;IACjF,WAAW,EAAE,cAAc;IAC3B,6DAA6D;IAC7D,YAAY,EAAE,eAAe;CACrB,CAAC;AAEX,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,8CAA8C;IAC9C,IAAI,EAAE,MAAM;IACZ,+CAA+C;IAC/C,KAAK,EAAE,qBAAqB;IAC5B,uCAAuC;IACvC,IAAI,EAAE,oBAAoB;CAClB,CAAC;AAKX,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,CAAC;AAEnC,0GAA0G;AAC1G,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,EAAE,CAAC"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
/** Shared base64url helpers (WebCrypto / DOM — no Node Buffer). */
|
|
2
2
|
export declare const bytesToBase64Url: (bytes: ArrayBuffer | Uint8Array) => string;
|
|
3
|
-
export declare const textToBase64Url: (text: string) => string;
|
|
4
3
|
export declare const fromBase64Url: (text: string) => Uint8Array<ArrayBuffer>;
|
|
5
4
|
//# sourceMappingURL=base64url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/base64url.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,eAAO,MAAM,gBAAgB,GAAI,OAAO,WAAW,GAAG,UAAU,KAAG,MAIlE,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,MAAM,KAAG,UAAU,CAAC,WAAW,CAKlE,CAAC"}
|
|
@@ -4,7 +4,6 @@ export const bytesToBase64Url = (bytes) => {
|
|
|
4
4
|
const binary = Array.from(u8, (byte) => String.fromCharCode(byte)).join("");
|
|
5
5
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
6
6
|
};
|
|
7
|
-
export const textToBase64Url = (text) => bytesToBase64Url(new TextEncoder().encode(text));
|
|
8
7
|
export const fromBase64Url = (text) => {
|
|
9
8
|
const padded = text.replace(/-/g, "+").replace(/_/g, "/");
|
|
10
9
|
const pad = padded.length % 4 === 0 ? "" : "=".repeat(4 - (padded.length % 4));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64url.js","sourceRoot":"","sources":["../../../src/oauth/crypto/base64url.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAA+B,EAAU,EAAE;IAC1E,MAAM,EAAE,GAAG,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC;IACvE,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5E,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACjF,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,IAAY,EAA2B,EAAE;IACrE,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Token-endpoint client authentication (RFC 6749 §2.3.1). */
|
|
2
|
+
import { type TokenEndpointAuthMethod } from "../constants.js";
|
|
3
|
+
import type { OAuthRouterOptions } from "../types.js";
|
|
4
|
+
export type ClientAuth = {
|
|
5
|
+
clientId: string;
|
|
6
|
+
secret: string | null;
|
|
7
|
+
method: TokenEndpointAuthMethod;
|
|
8
|
+
};
|
|
9
|
+
/** Credentials from the Authorization header, else from the body. */
|
|
10
|
+
export declare const readClientAuth: (request: Request, body: Record<string, string>) => ClientAuth | null;
|
|
11
|
+
/** Enforce the registered auth method for known clients — see the helpers above for each branch. */
|
|
12
|
+
export declare const firstClientAuthError: (auth: ClientAuth, options: OAuthRouterOptions, request: Request) => Promise<Response | null>;
|
|
13
|
+
//# sourceMappingURL=clientauth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientauth.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/clientauth.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAU9D,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAGV,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAIrB,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,uBAAuB,CAAC;CACjC,CAAC;AA6BF,qEAAqE;AACrE,eAAO,MAAM,cAAc,GACzB,SAAS,OAAO,EAChB,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC3B,UAAU,GAAG,IAaf,CAAC;AA2LF,oGAAoG;AACpG,eAAO,MAAM,oBAAoB,GAC/B,MAAM,UAAU,EAChB,SAAS,kBAAkB,EAC3B,SAAS,OAAO,KACf,OAAO,CAAC,QAAQ,GAAG,IAAI,CAMzB,CAAC"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/** Token-endpoint client authentication (RFC 6749 §2.3.1). */
|
|
2
|
+
import { auditLegacyKeyUsage, safeOAuthAudit } from "../audit.js";
|
|
3
|
+
import { cimdFetchOpts, isCimdClientId, resolveCimdClient } from "../cimd/cimd.js";
|
|
4
|
+
import { oauthError, registeredClientsRequired, resolveSecrets, unregisteredClientsAllowed, } from "../config.js";
|
|
5
|
+
import { OAUTH_ERRORS, TOKEN_ENDPOINT_AUTH_METHODS, } from "../constants.js";
|
|
6
|
+
import { unsealAny } from "./sealed.js";
|
|
7
|
+
import { verifyClientSecretAny } from "./secrethash.js";
|
|
8
|
+
/** RFC 7235 §2.1 — the auth-scheme token is case-insensitive. */
|
|
9
|
+
const BASIC_SCHEME_RE = /^basic\s+/i;
|
|
10
|
+
/** RFC 6749 §2.3.1 — id and secret are form-urlencoded, so `+` means a space. */
|
|
11
|
+
const decodeFormComponent = (value) => decodeURIComponent(value.replace(/\+/g, " "));
|
|
12
|
+
/** `Authorization: Basic base64(urlencode(id):urlencode(secret))`. */
|
|
13
|
+
const parseBasicAuth = (header) => {
|
|
14
|
+
const scheme = header ? BASIC_SCHEME_RE.exec(header) : null;
|
|
15
|
+
if (!header || !scheme)
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
const decoded = atob(header.slice(scheme[0].length).trim());
|
|
19
|
+
const separator = decoded.indexOf(":");
|
|
20
|
+
if (separator < 0)
|
|
21
|
+
return null;
|
|
22
|
+
const clientId = decodeFormComponent(decoded.slice(0, separator));
|
|
23
|
+
if (!clientId)
|
|
24
|
+
return null;
|
|
25
|
+
return {
|
|
26
|
+
clientId,
|
|
27
|
+
secret: decodeFormComponent(decoded.slice(separator + 1)),
|
|
28
|
+
method: TOKEN_ENDPOINT_AUTH_METHODS.basic,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/** Credentials from the Authorization header, else from the body. */
|
|
36
|
+
export const readClientAuth = (request, body) => {
|
|
37
|
+
const basic = parseBasicAuth(request.headers.get("Authorization"));
|
|
38
|
+
if (basic)
|
|
39
|
+
return basic;
|
|
40
|
+
const clientId = body.client_id ?? "";
|
|
41
|
+
if (!clientId)
|
|
42
|
+
return null;
|
|
43
|
+
const secret = body.client_secret ?? "";
|
|
44
|
+
return {
|
|
45
|
+
clientId,
|
|
46
|
+
secret: secret.length > 0 ? secret : null,
|
|
47
|
+
method: secret.length > 0 ? TOKEN_ENDPOINT_AUTH_METHODS.post : TOKEN_ENDPOINT_AUTH_METHODS.none,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Every failure below returns the same code, status, and description.
|
|
52
|
+
* Distinguishable messages here ("client requires client_secret_basic" vs.
|
|
53
|
+
* "unknown client_id" vs. "client_secret required" vs. …) let an
|
|
54
|
+
* unauthenticated caller enumerate which client_ids this server actually
|
|
55
|
+
* knows about and how each is configured, just by reading error text —
|
|
56
|
+
* a real information leak on the one endpoint whose entire job is to
|
|
57
|
+
* reject an untrusted caller. `/authorize`'s `unauthorized_client` stays
|
|
58
|
+
* descriptive on purpose: it's a human debugging an OAuth flow in a
|
|
59
|
+
* browser, not a bare API response, and a locked-down server already
|
|
60
|
+
* announces the same fact by omitting `registration_endpoint` from its
|
|
61
|
+
* metadata.
|
|
62
|
+
*/
|
|
63
|
+
const CLIENT_AUTH_FAILED = "client authentication failed";
|
|
64
|
+
/** The generic response every failure below returns to the caller. */
|
|
65
|
+
const invalidClient = () => oauthError(OAUTH_ERRORS.invalidClient, 401, CLIENT_AUTH_FAILED);
|
|
66
|
+
/** The response above, plus the real reason to `ports.audit` — never to the caller. */
|
|
67
|
+
const invalidClientAudited = async ({ auth, options }, reason) => {
|
|
68
|
+
await safeOAuthAudit(options, { event: "client_auth_failed", clientId: auth.clientId, reason });
|
|
69
|
+
return invalidClient();
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Never a real credential — exists only so the locked-down branch below
|
|
73
|
+
* does the same amount of comparison work whether or not a secret was
|
|
74
|
+
* actually presented, and whether or not a stored hash actually exists.
|
|
75
|
+
*/
|
|
76
|
+
const DUMMY_SECRET = "dummy-secret-for-constant-time-padding";
|
|
77
|
+
const DUMMY_STORED_HASH = "hmac-sha256$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
|
|
78
|
+
/**
|
|
79
|
+
* Rejects with `reason`, but only after paying the same
|
|
80
|
+
* `verifyClientSecretAny` cost a real (wrong-secret) rejection would — a
|
|
81
|
+
* missing or empty secret is replaced by `DUMMY_SECRET`, so a caller who
|
|
82
|
+
* presented nothing can't be told apart, by timing, from a wrong secret.
|
|
83
|
+
*/
|
|
84
|
+
const rejectWithDummyCompare = async (context, reason) => {
|
|
85
|
+
const codeSecretValues = await resolveSecrets(context.options.ports, context.request);
|
|
86
|
+
const presented = context.auth.secret || DUMMY_SECRET;
|
|
87
|
+
await verifyClientSecretAny(presented, DUMMY_STORED_HASH, codeSecretValues);
|
|
88
|
+
return invalidClientAudited(context, reason);
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* True when `clientId` is this server's own sealed `/register` assertion or,
|
|
92
|
+
* with CIMD enabled, a resolvable CIMD document. URL-shaped ids are always
|
|
93
|
+
* re-resolved, never accepted by shape alone.
|
|
94
|
+
*/
|
|
95
|
+
const isSelfRegisteredOrCimdClient = async ({ auth, options, request, }) => {
|
|
96
|
+
const codeSecretValues = await resolveSecrets(options.ports, request);
|
|
97
|
+
const unsealed = await unsealAny(codeSecretValues, "client", auth.clientId);
|
|
98
|
+
if (unsealed) {
|
|
99
|
+
await auditLegacyKeyUsage(options, {
|
|
100
|
+
clientId: auth.clientId,
|
|
101
|
+
keyIndex: unsealed.keyIndex,
|
|
102
|
+
source: "client assertion unsealed",
|
|
103
|
+
});
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
if (options.cimd !== true || !isCimdClientId(auth.clientId))
|
|
107
|
+
return false;
|
|
108
|
+
if (await resolveCimdClient(auth.clientId, cimdFetchOpts(options)))
|
|
109
|
+
return true;
|
|
110
|
+
// Without `cimdCache` every refresh/revoke re-fetches — a flaky document
|
|
111
|
+
// host logs users out. Surface that distinctly.
|
|
112
|
+
await safeOAuthAudit(options, {
|
|
113
|
+
event: "cimd_resolve_failed",
|
|
114
|
+
clientId: auth.clientId,
|
|
115
|
+
reason: "CIMD document could not be resolved at token/revoke time",
|
|
116
|
+
});
|
|
117
|
+
return false;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* An unknown client_id is treated as public — PKCE is its only proof —
|
|
121
|
+
* unless `allowUnregisteredClients` is false, in which case it is rejected.
|
|
122
|
+
* On that locked-down path a dummy secret comparison runs even for a
|
|
123
|
+
* client_id this server has never heard of, so timing can't distinguish
|
|
124
|
+
* "unknown" from "known, wrong secret". Public clients never pay that cost.
|
|
125
|
+
*
|
|
126
|
+
* Under `requireRegisteredClients`, a client_id resolved by neither
|
|
127
|
+
* `clientStore`, this server's own sealed `/register` assertion, nor CIMD is
|
|
128
|
+
* rejected — the same rule `/authorize` enforces. It lives in this shared
|
|
129
|
+
* gate so the `refresh_token` grant and `/revoke` cannot be reached with an
|
|
130
|
+
* invented id that never went through `/authorize`.
|
|
131
|
+
*/
|
|
132
|
+
const unregisteredClientAuthError = async (context) => {
|
|
133
|
+
if (!unregisteredClientsAllowed(context.options)) {
|
|
134
|
+
return rejectWithDummyCompare(context, "unknown client_id");
|
|
135
|
+
}
|
|
136
|
+
if (!registeredClientsRequired(context.options))
|
|
137
|
+
return null;
|
|
138
|
+
if (await isSelfRegisteredOrCimdClient(context))
|
|
139
|
+
return null;
|
|
140
|
+
return invalidClientAudited(context, "client_id must come from clientStore or this server's own /register");
|
|
141
|
+
};
|
|
142
|
+
/** Compare against `ClientStore.secretHash` — the same cost whether a hash is on file or not. */
|
|
143
|
+
const secretHashAuthError = async (context, secret, secretHash) => {
|
|
144
|
+
const { auth, options, request } = context;
|
|
145
|
+
const codeSecretValues = await resolveSecrets(options.ports, request);
|
|
146
|
+
const stored = await secretHash(auth.clientId);
|
|
147
|
+
const { ok, keyIndex } = await verifyClientSecretAny(secret, stored ?? DUMMY_STORED_HASH, codeSecretValues);
|
|
148
|
+
if (ok && stored !== null) {
|
|
149
|
+
await auditLegacyKeyUsage(options, {
|
|
150
|
+
clientId: auth.clientId,
|
|
151
|
+
keyIndex,
|
|
152
|
+
source: "client_secret_hash verified",
|
|
153
|
+
});
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
return invalidClientAudited(context, stored === null ? "no secretHash on file for this client" : "secretHash mismatch");
|
|
157
|
+
};
|
|
158
|
+
/** Delegate the comparison to the host's `ClientStore.verifySecret`. */
|
|
159
|
+
const verifySecretAuthError = async (context, secret, verifySecret) => {
|
|
160
|
+
if (!verifySecret) {
|
|
161
|
+
return invalidClientAudited(context, "clientStore.verifySecret is not configured");
|
|
162
|
+
}
|
|
163
|
+
if (!(await verifySecret(context.auth.clientId, secret))) {
|
|
164
|
+
return invalidClientAudited(context, "verifySecret returned false");
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
};
|
|
168
|
+
/** Enforce a known client's own `tokenEndpointAuthMethod`. */
|
|
169
|
+
const registeredClientAuthError = async (context, registered) => {
|
|
170
|
+
const { auth, options } = context;
|
|
171
|
+
const expected = registered.tokenEndpointAuthMethod;
|
|
172
|
+
if (expected === TOKEN_ENDPOINT_AUTH_METHODS.none)
|
|
173
|
+
return null;
|
|
174
|
+
// A wrong method or a missing secret pays the same comparison cost as a
|
|
175
|
+
// wrong secret, so neither branch leaks "this client exists and needs a
|
|
176
|
+
// secret" through response timing.
|
|
177
|
+
if (auth.method !== expected) {
|
|
178
|
+
return rejectWithDummyCompare(context, `client requires ${expected}, got ${auth.method}`);
|
|
179
|
+
}
|
|
180
|
+
if (!auth.secret)
|
|
181
|
+
return rejectWithDummyCompare(context, "client_secret required");
|
|
182
|
+
const store = options.ports.clientStore;
|
|
183
|
+
return store?.secretHash
|
|
184
|
+
? secretHashAuthError(context, auth.secret, store.secretHash)
|
|
185
|
+
: verifySecretAuthError(context, auth.secret, store?.verifySecret);
|
|
186
|
+
};
|
|
187
|
+
/** Enforce the registered auth method for known clients — see the helpers above for each branch. */
|
|
188
|
+
export const firstClientAuthError = async (auth, options, request) => {
|
|
189
|
+
const context = { auth, options, request };
|
|
190
|
+
const registered = (await options.ports.clientStore?.get(auth.clientId)) ?? null;
|
|
191
|
+
return registered
|
|
192
|
+
? registeredClientAuthError(context, registered)
|
|
193
|
+
: unregisteredClientAuthError(context);
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=clientauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clientauth.js","sourceRoot":"","sources":["../../../src/oauth/crypto/clientauth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export type AuthCodeRecord = {
|
|
2
|
+
clientId: string;
|
|
3
|
+
redirectUri: string;
|
|
4
|
+
codeChallenge: string;
|
|
5
|
+
/** Consenting user id — required so minted tokens are bound. */
|
|
6
|
+
userId: string;
|
|
7
|
+
/** RFC 8707 resource indicator — audience for the eventual access token. */
|
|
8
|
+
resource: string;
|
|
9
|
+
/** Space-delimited scope granted at authorize time. */
|
|
10
|
+
scope: string;
|
|
11
|
+
exp: number;
|
|
12
|
+
};
|
|
13
|
+
export type CodeStore = {
|
|
14
|
+
/**
|
|
15
|
+
* Mark a code jti as consumed; return false if already used.
|
|
16
|
+
* `expMs` is the code expiry (ms since epoch) for TTL-backed stores.
|
|
17
|
+
*/
|
|
18
|
+
consume: (jti: string, expMs: number) => Promise<boolean> | boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Process-local jti → expMs map when no shared codeStore is provided.
|
|
22
|
+
* ponytail: single-process only; multi-instance → pass ports.codeStore (KV/Redis SET NX EX).
|
|
23
|
+
*/
|
|
24
|
+
export declare const createMemoryCodeStore: () => CodeStore;
|
|
25
|
+
/**
|
|
26
|
+
* The one process-wide in-memory store. Shared rather than per router: an
|
|
27
|
+
* app built per request (`buildApp(env).fetch(req)`) would otherwise get a
|
|
28
|
+
* fresh map each time and lose single-use entirely. `createOAuthRouter`
|
|
29
|
+
* resolves it into `ports.codeStore` only when `allowInMemoryCodeStore` is
|
|
30
|
+
* set; endpoint handlers never fall back on their own.
|
|
31
|
+
*/
|
|
32
|
+
export declare const processCodeStore: CodeStore;
|
|
33
|
+
/**
|
|
34
|
+
* Issue an AES-GCM sealed auth code (v2). Single-use via codeStore, or
|
|
35
|
+
* pruning in-memory Map when omitted.
|
|
36
|
+
*/
|
|
37
|
+
export declare const issueAuthCode: (secret: string, record: Omit<AuthCodeRecord, "exp">, nowMs?: number) => Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Rotation-aware `consumeAuthCode`, also reporting which `secret` entry
|
|
40
|
+
* actually unsealed the code — used only by `token.ts` to audit
|
|
41
|
+
* `"legacy_code_secret_used"` without a second decrypt pass. Not part of
|
|
42
|
+
* the public API; `consumeAuthCode` below is the stable, exported surface.
|
|
43
|
+
*/
|
|
44
|
+
export declare const consumeAuthCodeDetailed: (secret: string | readonly string[], code: string, options?: {
|
|
45
|
+
codeStore?: CodeStore | undefined;
|
|
46
|
+
nowMs?: number | undefined;
|
|
47
|
+
}) => Promise<{
|
|
48
|
+
record: AuthCodeRecord;
|
|
49
|
+
keyIndex: number;
|
|
50
|
+
} | null>;
|
|
51
|
+
/**
|
|
52
|
+
* `secret` may be a single key or a list — every entry is tried, so a code
|
|
53
|
+
* sealed under an older `codeSecret` keeps redeeming during rotation. See
|
|
54
|
+
* `resolveSecrets`/`OAuthPorts.codeSecret` for the rotation contract.
|
|
55
|
+
*
|
|
56
|
+
* Omitting `options.codeStore` is deprecated: it falls back to a
|
|
57
|
+
* process-local map that cannot enforce single use across instances, and
|
|
58
|
+
* `codeStore` becomes required in 3.0.
|
|
59
|
+
*/
|
|
60
|
+
export declare const consumeAuthCode: (secret: string | readonly string[], code: string, options?: {
|
|
61
|
+
codeStore?: CodeStore | undefined;
|
|
62
|
+
nowMs?: number | undefined;
|
|
63
|
+
}) => Promise<AuthCodeRecord | null>;
|
|
64
|
+
export declare const newClientId: () => string;
|
|
65
|
+
//# sourceMappingURL=codes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codes.d.ts","sourceRoot":"","sources":["../../../src/oauth/crypto/codes.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB;;;OAGG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACrE,CAAC;AAQF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,QAAO,SAiBxC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAmC,CAAC;AAEnE;;;GAGG;AACH,eAAO,MAAM,aAAa,GACxB,QAAQ,MAAM,EACd,QAAQ,IAAI,CAAC,cAAc,EAAE,KAAK,CAAC,EACnC,cAAkB,KACjB,OAAO,CAAC,MAAM,CAOhB,CAAC;AAmCF;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,MAAM,GAAG,SAAS,MAAM,EAAE,EAClC,MAAM,MAAM,EACZ,UAAS;IAAE,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,KAC9E,OAAO,CAAC;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAqB7D,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,GAAG,SAAS,MAAM,EAAE,EAClC,MAAM,MAAM,EACZ,UAAS;IAAE,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,KAC9E,OAAO,CAAC,cAAc,GAAG,IAAI,CAG/B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAO,MAA6B,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { randomBase64Url } from "./pkce.js";
|
|
2
|
+
import { seal, unsealAny } from "./sealed.js";
|
|
3
|
+
const CODE_TTL_MS = 600_000;
|
|
4
|
+
/** AES-GCM sealed auth codes — authenticated and encrypted (see `sealed.ts`). */
|
|
5
|
+
const SEALED_PREFIX = "v2.";
|
|
6
|
+
/**
|
|
7
|
+
* Prune the in-memory store at most this often: a full `Map` scan on every
|
|
8
|
+
* `consume` would run for every code and consent-ticket redemption.
|
|
9
|
+
*/
|
|
10
|
+
const PRUNE_INTERVAL_MS = 60_000;
|
|
11
|
+
/**
|
|
12
|
+
* Process-local jti → expMs map when no shared codeStore is provided.
|
|
13
|
+
* ponytail: single-process only; multi-instance → pass ports.codeStore (KV/Redis SET NX EX).
|
|
14
|
+
*/
|
|
15
|
+
export const createMemoryCodeStore = () => {
|
|
16
|
+
const used = new Map();
|
|
17
|
+
let lastPrunedAtMs = 0;
|
|
18
|
+
return {
|
|
19
|
+
consume: (jti, expMs) => {
|
|
20
|
+
const nowMs = Date.now();
|
|
21
|
+
if (nowMs - lastPrunedAtMs >= PRUNE_INTERVAL_MS) {
|
|
22
|
+
lastPrunedAtMs = nowMs;
|
|
23
|
+
for (const [key, exp] of used) {
|
|
24
|
+
if (exp < nowMs)
|
|
25
|
+
used.delete(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (used.has(jti))
|
|
29
|
+
return false;
|
|
30
|
+
used.set(jti, expMs);
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The one process-wide in-memory store. Shared rather than per router: an
|
|
37
|
+
* app built per request (`buildApp(env).fetch(req)`) would otherwise get a
|
|
38
|
+
* fresh map each time and lose single-use entirely. `createOAuthRouter`
|
|
39
|
+
* resolves it into `ports.codeStore` only when `allowInMemoryCodeStore` is
|
|
40
|
+
* set; endpoint handlers never fall back on their own.
|
|
41
|
+
*/
|
|
42
|
+
export const processCodeStore = createMemoryCodeStore();
|
|
43
|
+
/**
|
|
44
|
+
* Issue an AES-GCM sealed auth code (v2). Single-use via codeStore, or
|
|
45
|
+
* pruning in-memory Map when omitted.
|
|
46
|
+
*/
|
|
47
|
+
export const issueAuthCode = async (secret, record, nowMs = Date.now()) => {
|
|
48
|
+
const body = {
|
|
49
|
+
...record,
|
|
50
|
+
exp: nowMs + CODE_TTL_MS,
|
|
51
|
+
jti: randomBase64Url(12),
|
|
52
|
+
};
|
|
53
|
+
return `${SEALED_PREFIX}${await seal(secret, "code", body)}`;
|
|
54
|
+
};
|
|
55
|
+
const shapeCheckedRecord = (data, nowMs) => {
|
|
56
|
+
if (typeof data.clientId !== "string" ||
|
|
57
|
+
typeof data.redirectUri !== "string" ||
|
|
58
|
+
typeof data.codeChallenge !== "string" ||
|
|
59
|
+
typeof data.userId !== "string" ||
|
|
60
|
+
typeof data.resource !== "string" ||
|
|
61
|
+
data.resource.length === 0 ||
|
|
62
|
+
typeof data.scope !== "string" ||
|
|
63
|
+
typeof data.exp !== "number" ||
|
|
64
|
+
data.exp < nowMs ||
|
|
65
|
+
typeof data.jti !== "string" ||
|
|
66
|
+
data.jti.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
clientId: data.clientId,
|
|
71
|
+
redirectUri: data.redirectUri,
|
|
72
|
+
codeChallenge: data.codeChallenge,
|
|
73
|
+
userId: data.userId,
|
|
74
|
+
resource: data.resource,
|
|
75
|
+
scope: data.scope,
|
|
76
|
+
exp: data.exp,
|
|
77
|
+
jti: data.jti,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Rotation-aware `consumeAuthCode`, also reporting which `secret` entry
|
|
82
|
+
* actually unsealed the code — used only by `token.ts` to audit
|
|
83
|
+
* `"legacy_code_secret_used"` without a second decrypt pass. Not part of
|
|
84
|
+
* the public API; `consumeAuthCode` below is the stable, exported surface.
|
|
85
|
+
*/
|
|
86
|
+
export const consumeAuthCodeDetailed = async (secret, code, options = {}) => {
|
|
87
|
+
const secrets = (Array.isArray(secret) ? secret : [secret]).filter((candidate) => Boolean(candidate));
|
|
88
|
+
if (!code || secrets.length === 0)
|
|
89
|
+
return null;
|
|
90
|
+
// Only AES-GCM sealed (`v2.`) codes redeem.
|
|
91
|
+
if (!code.startsWith(SEALED_PREFIX))
|
|
92
|
+
return null;
|
|
93
|
+
const nowMs = options.nowMs ?? Date.now();
|
|
94
|
+
const unsealed = await unsealAny(secrets, "code", code.slice(SEALED_PREFIX.length));
|
|
95
|
+
if (!unsealed)
|
|
96
|
+
return null;
|
|
97
|
+
const record = shapeCheckedRecord(unsealed.value, nowMs);
|
|
98
|
+
if (!record)
|
|
99
|
+
return null;
|
|
100
|
+
const store = options.codeStore ?? processCodeStore;
|
|
101
|
+
const fresh = await store.consume(record.jti, record.exp);
|
|
102
|
+
if (!fresh)
|
|
103
|
+
return null;
|
|
104
|
+
const { jti: _jti, ...rest } = record;
|
|
105
|
+
return { record: rest, keyIndex: unsealed.keyIndex };
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* `secret` may be a single key or a list — every entry is tried, so a code
|
|
109
|
+
* sealed under an older `codeSecret` keeps redeeming during rotation. See
|
|
110
|
+
* `resolveSecrets`/`OAuthPorts.codeSecret` for the rotation contract.
|
|
111
|
+
*
|
|
112
|
+
* Omitting `options.codeStore` is deprecated: it falls back to a
|
|
113
|
+
* process-local map that cannot enforce single use across instances, and
|
|
114
|
+
* `codeStore` becomes required in 3.0.
|
|
115
|
+
*/
|
|
116
|
+
export const consumeAuthCode = async (secret, code, options = {}) => {
|
|
117
|
+
const result = await consumeAuthCodeDetailed(secret, code, options);
|
|
118
|
+
return result?.record ?? null;
|
|
119
|
+
};
|
|
120
|
+
export const newClientId = () => randomBase64Url(16);
|
|
121
|
+
//# sourceMappingURL=codes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codes.js","sourceRoot":"","sources":["../../../src/oauth/crypto/codes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,GAAG,OAAO,CAAC;AAC5B,iFAAiF;AACjF,MAAM,aAAa,GAAG,KAAK,CAAC;AAuB5B;;;GAGG;AACH,MAAM,iBAAiB,GAAG,MAAM,CAAC;AAEjC;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAc,EAAE;IACnD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,IAAI,cAAc,GAAG,CAAC,CAAC;IACvB,OAAO;QACL,OAAO,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;YACtB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACzB,IAAI,KAAK,GAAG,cAAc,IAAI,iBAAiB,EAAE,CAAC;gBAChD,cAAc,GAAG,KAAK,CAAC;gBACvB,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;oBAC9B,IAAI,GAAG,GAAG,KAAK;wBAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACpC,CAAC;YACH,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YAChC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;YACrB,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAc,qBAAqB,EAAE,CAAC;AAEnE;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,MAAc,EACd,MAAmC,EACnC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,EACD,EAAE;IACnB,MAAM,IAAI,GAAqC;QAC7C,GAAG,MAAM;QACT,GAAG,EAAE,KAAK,GAAG,WAAW;QACxB,GAAG,EAAE,eAAe,CAAC,EAAE,CAAC;KACzB,CAAC;IACF,OAAO,GAAG,aAAa,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,CAAC;AAC/D,CAAC,CAAC;AAIF,MAAM,kBAAkB,GAAG,CACzB,IAAiB,EACjB,KAAa,EAC8B,EAAE;IAC7C,IACE,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;QACjC,OAAO,IAAI,CAAC,WAAW,KAAK,QAAQ;QACpC,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ;QACtC,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ;QAC/B,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ;QACjC,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;QAC1B,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ;QAC9B,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ;QAC5B,IAAI,CAAC,GAAG,GAAG,KAAK;QAChB,OAAO,IAAI,CAAC,GAAG,KAAK,QAAQ;QAC5B,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,CAAC,EACrB,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,GAAG,EAAE,IAAI,CAAC,GAAG;QACb,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC;AACJ,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,EAC1C,MAAkC,EAClC,IAAY,EACZ,UAA6E,EAAE,EACjB,EAAE;IAChE,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAChE,CAAC,SAAS,EAAuB,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,CACvD,CAAC;IACF,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,4CAA4C;IAC5C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IAC1C,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAc,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;IACjG,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACzD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,MAAM,KAAK,GAAG,OAAO,CAAC,SAAS,IAAI,gBAAgB,CAAC;IACpD,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IACtC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;AACvD,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAClC,MAAkC,EAClC,IAAY,EACZ,UAA6E,EAAE,EAC/C,EAAE;IAClC,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACpE,OAAO,MAAM,EAAE,MAAM,IAAI,IAAI,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Purpose-bound keys derived from `codeSecret` with HKDF-SHA256, cached so
|
|
3
|
+
* rotation (`unsealAny`, `verifyClientSecretAny`) doesn't re-run
|
|
4
|
+
* `importKey` + `deriveKey` for every entry on every request.
|
|
5
|
+
*/
|
|
6
|
+
/** What a derived key is for. `info` is the domain separator: one value per purpose. */
|
|
7
|
+
export type HkdfKeyPurpose = {
|
|
8
|
+
info: string;
|
|
9
|
+
algorithm: AesKeyGenParams | HmacKeyGenParams;
|
|
10
|
+
usage: KeyUsage;
|
|
11
|
+
};
|
|
12
|
+
export declare const hkdfKey: (secret: string, purpose: HkdfKeyPurpose) => Promise<CryptoKey>;
|
|
13
|
+
//# sourceMappingURL=hkdf.d.ts.map
|