mcp-trellis 0.2.1 → 1.0.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 +41 -561
- package/dist/adapters/node.d.ts +14 -5
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +56 -29
- package/dist/adapters/node.js.map +1 -1
- package/dist/adapters/origins.d.ts.map +1 -1
- package/dist/adapters/origins.js +1 -4
- package/dist/adapters/origins.js.map +1 -1
- package/dist/app.d.ts +27 -4
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +31 -18
- package/dist/app.js.map +1 -1
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +6 -1
- package/dist/auth/bearer.js.map +1 -1
- package/dist/body.d.ts +48 -0
- package/dist/body.d.ts.map +1 -0
- package/dist/body.js +145 -0
- package/dist/body.js.map +1 -0
- package/dist/clients.d.ts.map +1 -1
- package/dist/clients.js +2 -5
- package/dist/clients.js.map +1 -1
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +58 -18
- package/dist/dispatch.js.map +1 -1
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js.map +1 -1
- package/dist/jsonrpc.d.ts +1 -0
- package/dist/jsonrpc.d.ts.map +1 -1
- package/dist/jsonrpc.js +1 -0
- package/dist/jsonrpc.js.map +1 -1
- package/dist/methods.d.ts +38 -15
- package/dist/methods.d.ts.map +1 -1
- package/dist/methods.js +38 -22
- package/dist/methods.js.map +1 -1
- package/dist/oauth/authorize.d.ts.map +1 -1
- package/dist/oauth/authorize.js +100 -68
- package/dist/oauth/authorize.js.map +1 -1
- package/dist/oauth/base64url.d.ts +1 -1
- package/dist/oauth/base64url.d.ts.map +1 -1
- package/dist/oauth/base64url.js +1 -4
- package/dist/oauth/base64url.js.map +1 -1
- package/dist/oauth/clientauth.d.ts +7 -2
- package/dist/oauth/clientauth.d.ts.map +1 -1
- package/dist/oauth/clientauth.js +59 -16
- package/dist/oauth/clientauth.js.map +1 -1
- package/dist/oauth/codes.d.ts +4 -4
- package/dist/oauth/codes.d.ts.map +1 -1
- package/dist/oauth/codes.js +43 -51
- package/dist/oauth/codes.js.map +1 -1
- package/dist/oauth/consent.d.ts +65 -0
- package/dist/oauth/consent.d.ts.map +1 -0
- package/dist/oauth/consent.js +173 -0
- package/dist/oauth/consent.js.map +1 -0
- package/dist/oauth/constants.d.ts +4 -0
- package/dist/oauth/constants.d.ts.map +1 -1
- package/dist/oauth/constants.js +4 -0
- package/dist/oauth/constants.js.map +1 -1
- package/dist/oauth/metadata.d.ts +6 -6
- package/dist/oauth/metadata.d.ts.map +1 -1
- package/dist/oauth/metadata.js.map +1 -1
- package/dist/oauth/pkce.d.ts.map +1 -1
- package/dist/oauth/pkce.js +1 -1
- package/dist/oauth/pkce.js.map +1 -1
- package/dist/oauth/redirect.d.ts +6 -1
- package/dist/oauth/redirect.d.ts.map +1 -1
- package/dist/oauth/redirect.js +23 -10
- package/dist/oauth/redirect.js.map +1 -1
- package/dist/oauth/reqbody.d.ts +10 -0
- package/dist/oauth/reqbody.d.ts.map +1 -0
- package/dist/oauth/reqbody.js +29 -0
- package/dist/oauth/reqbody.js.map +1 -0
- package/dist/oauth/resource.d.ts +30 -0
- package/dist/oauth/resource.d.ts.map +1 -1
- package/dist/oauth/resource.js +38 -8
- package/dist/oauth/resource.js.map +1 -1
- package/dist/oauth/revoke.d.ts.map +1 -1
- package/dist/oauth/revoke.js +15 -3
- package/dist/oauth/revoke.js.map +1 -1
- package/dist/oauth/router.d.ts +14 -11
- package/dist/oauth/router.d.ts.map +1 -1
- package/dist/oauth/router.js +62 -22
- package/dist/oauth/router.js.map +1 -1
- package/dist/oauth/scope.d.ts +11 -2
- package/dist/oauth/scope.d.ts.map +1 -1
- package/dist/oauth/scope.js +18 -7
- package/dist/oauth/scope.js.map +1 -1
- package/dist/oauth/sealed.d.ts +17 -0
- package/dist/oauth/sealed.d.ts.map +1 -0
- package/dist/oauth/sealed.js +57 -0
- package/dist/oauth/sealed.js.map +1 -0
- package/dist/oauth/secrethash.d.ts +44 -0
- package/dist/oauth/secrethash.d.ts.map +1 -0
- package/dist/oauth/secrethash.js +66 -0
- package/dist/oauth/secrethash.js.map +1 -0
- package/dist/oauth/token.d.ts.map +1 -1
- package/dist/oauth/token.js +13 -34
- package/dist/oauth/token.js.map +1 -1
- package/dist/oauth/types.d.ts +106 -9
- package/dist/oauth/types.d.ts.map +1 -1
- package/dist/oauth/types.js +67 -4
- package/dist/oauth/types.js.map +1 -1
- package/dist/oauth/wellknown.d.ts.map +1 -1
- package/dist/oauth/wellknown.js +6 -4
- package/dist/oauth/wellknown.js.map +1 -1
- package/dist/protocol.d.ts +15 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +16 -6
- package/dist/protocol.js.map +1 -1
- package/dist/registry.d.ts +4 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +15 -7
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts +14 -13
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +8 -8
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts +11 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +14 -13
- package/dist/tools.js.map +1 -1
- package/dist/validate.d.ts +16 -1
- package/dist/validate.d.ts.map +1 -1
- package/dist/validate.js +46 -8
- package/dist/validate.js.map +1 -1
- package/docs/ROADMAP.md +295 -0
- package/docs/guide.md +417 -0
- package/docs/reference.md +178 -0
- package/docs/security.md +63 -0
- package/package.json +6 -3
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared JSON/form body reader for `/token` and `/revoke`. `/token` has
|
|
3
|
+
* always accepted both; `/revoke` used to parse form-only, so a client that
|
|
4
|
+
* revoked with `Content-Type: application/json` (matching how it likely
|
|
5
|
+
* called `/token`) got a silent `invalid_request "token required"` — the
|
|
6
|
+
* caller believes the token is dead when the request was never understood.
|
|
7
|
+
*/
|
|
8
|
+
import { DEFAULT_OAUTH_BODY_LIMIT, readBoundedText } from "../body.js";
|
|
9
|
+
const readJsonBody = async (request) => {
|
|
10
|
+
const text = await readBoundedText(request, DEFAULT_OAUTH_BODY_LIMIT);
|
|
11
|
+
const body = JSON.parse(text);
|
|
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 text = await readBoundedText(request, DEFAULT_OAUTH_BODY_LIMIT);
|
|
21
|
+
const params = new URLSearchParams(text);
|
|
22
|
+
return Object.fromEntries([...params.entries()]);
|
|
23
|
+
};
|
|
24
|
+
/** JSON when `Content-Type` says so; form-encoded otherwise (the RFC 6749 default). */
|
|
25
|
+
export const readOAuthBody = async (request) => {
|
|
26
|
+
const ctype = request.headers.get("Content-Type") ?? "";
|
|
27
|
+
return ctype.includes("application/json") ? readJsonBody(request) : readFormBody(request);
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=reqbody.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reqbody.js","sourceRoot":"","sources":["../../src/oauth/reqbody.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEvE,MAAM,YAAY,GAAG,KAAK,EAAE,OAAgB,EAAmC,EAAE;IAC/E,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC;IACtE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;IACzC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,CACvB,MAAM,CAAC,OAAO,CAAC,IAA+B,CAAC;SAC5C,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,IAAI,CAAC;SACpC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAC/C,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,KAAK,EAAE,OAAgB,EAAmC,EAAE;IAC/E,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,wBAAwB,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,IAAI,CAAC,CAAC;IACzC,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;AACnD,CAAC,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,OAAgB,EAAmC,EAAE;IACvF,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IACxD,OAAO,KAAK,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AAC5F,CAAC,CAAC"}
|
package/dist/oauth/resource.d.ts
CHANGED
|
@@ -1,7 +1,37 @@
|
|
|
1
1
|
/** Canonical MCP resource URI (RFC 8707 / RFC 9728). */
|
|
2
|
+
import { type OAuthErrorInfo } from "./types.js";
|
|
2
3
|
export declare const DEFAULT_RESOURCE_PATH = "/mcp";
|
|
4
|
+
/**
|
|
5
|
+
* Strips a trailing slash from a *configured* path (`resourcePath`,
|
|
6
|
+
* `oauthPath`) so `"/mcp/"` and `"/mcp"` can't produce divergent canonical
|
|
7
|
+
* resources — `canonicalResource` doesn't strip one itself, but
|
|
8
|
+
* `normalizeResource` (used to compare an incoming request's resource
|
|
9
|
+
* against it) does, so leaving a trailing slash in the *configured* value
|
|
10
|
+
* silently made every request fail to match. `"/"` stays `"/"`. Apply once
|
|
11
|
+
* at construction, not per request.
|
|
12
|
+
*/
|
|
13
|
+
export declare const normalizeConfiguredPath: (path: string) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Normalizes `resourcePath` internally (trailing slash stripped) so every
|
|
16
|
+
* caller gets a consistent result regardless of whether it read a raw
|
|
17
|
+
* `options.resourcePath` or a value some other layer already normalized —
|
|
18
|
+
* `"/mcp/"` and `"/mcp"` must produce the same canonical resource, since
|
|
19
|
+
* `resourcesEqual` compares against a normalized incoming request either
|
|
20
|
+
* way (see `normalizeResource` below). Centralizing the fix here, instead
|
|
21
|
+
* of only in the callers that happen to normalize their own config,
|
|
22
|
+
* removes an entire class of "which of these read the normalized value"
|
|
23
|
+
* bugs at the source.
|
|
24
|
+
*/
|
|
3
25
|
export declare const canonicalResource: (origin: string, resourcePath?: string) => string;
|
|
4
26
|
export declare const resourcesEqual: (requested: string, expected: string) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Missing → invalid_request; mismatch → invalid_target. Info-only variant
|
|
29
|
+
* (no `Response` built) so a caller can choose how to deliver it —
|
|
30
|
+
* `/token` returns it directly as JSON via `firstResourceError` below;
|
|
31
|
+
* `/authorize` redirects it to the client's callback per RFC 6749 §4.1.2.1
|
|
32
|
+
* once `redirect_uri` itself is trusted (see `authorize.ts`).
|
|
33
|
+
*/
|
|
34
|
+
export declare const resourceErrorInfo: (requested: string, expected: string) => OAuthErrorInfo | null;
|
|
5
35
|
/** Missing → invalid_request; mismatch → invalid_target. */
|
|
6
36
|
export declare const firstResourceError: (requested: string, expected: string) => Response | null;
|
|
7
37
|
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/oauth/resource.ts"],"names":[],"mappings":"AAAA,wDAAwD;
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../src/oauth/resource.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAGxD,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,YAAY,CAAC;AAE7D,eAAO,MAAM,qBAAqB,SAAS,CAAC;AAE5C;;;;;;;;GAQG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,MACA,CAAC;AAExD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,EAAE,qBAAoC,KAAG,MAIxF,CAAC;AAmBF,eAAO,MAAM,cAAc,GAAI,WAAW,MAAM,EAAE,UAAU,MAAM,KAAG,OAGpE,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,MAAM,EAAE,UAAU,MAAM,KAAG,cAAc,GAAG,IAQxF,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,EAAE,UAAU,MAAM,KAAG,QAAQ,GAAG,IAGnF,CAAC"}
|
package/dist/oauth/resource.js
CHANGED
|
@@ -2,12 +2,31 @@
|
|
|
2
2
|
import { OAUTH_ERRORS } from "./constants.js";
|
|
3
3
|
import { oauthError } from "./types.js";
|
|
4
4
|
export const DEFAULT_RESOURCE_PATH = "/mcp";
|
|
5
|
+
/**
|
|
6
|
+
* Strips a trailing slash from a *configured* path (`resourcePath`,
|
|
7
|
+
* `oauthPath`) so `"/mcp/"` and `"/mcp"` can't produce divergent canonical
|
|
8
|
+
* resources — `canonicalResource` doesn't strip one itself, but
|
|
9
|
+
* `normalizeResource` (used to compare an incoming request's resource
|
|
10
|
+
* against it) does, so leaving a trailing slash in the *configured* value
|
|
11
|
+
* silently made every request fail to match. `"/"` stays `"/"`. Apply once
|
|
12
|
+
* at construction, not per request.
|
|
13
|
+
*/
|
|
14
|
+
export const normalizeConfiguredPath = (path) => path === "/" ? path : path.replace(/\/+$/, "") || "/";
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes `resourcePath` internally (trailing slash stripped) so every
|
|
17
|
+
* caller gets a consistent result regardless of whether it read a raw
|
|
18
|
+
* `options.resourcePath` or a value some other layer already normalized —
|
|
19
|
+
* `"/mcp/"` and `"/mcp"` must produce the same canonical resource, since
|
|
20
|
+
* `resourcesEqual` compares against a normalized incoming request either
|
|
21
|
+
* way (see `normalizeResource` below). Centralizing the fix here, instead
|
|
22
|
+
* of only in the callers that happen to normalize their own config,
|
|
23
|
+
* removes an entire class of "which of these read the normalized value"
|
|
24
|
+
* bugs at the source.
|
|
25
|
+
*/
|
|
5
26
|
export const canonicalResource = (origin, resourcePath = DEFAULT_RESOURCE_PATH) => {
|
|
6
27
|
const base = origin.replace(/\/$/, "");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
: `/${resourcePath}`;
|
|
10
|
-
return `${base}${path}`;
|
|
28
|
+
const withLeadingSlash = resourcePath.startsWith("/") ? resourcePath : `/${resourcePath}`;
|
|
29
|
+
return `${base}${normalizeConfiguredPath(withLeadingSlash)}`;
|
|
11
30
|
};
|
|
12
31
|
/**
|
|
13
32
|
* Normalize for RFC 8707 comparison.
|
|
@@ -31,14 +50,25 @@ export const resourcesEqual = (requested, expected) => {
|
|
|
31
50
|
const a = normalizeResource(requested);
|
|
32
51
|
return a !== null && a === normalizeResource(expected);
|
|
33
52
|
};
|
|
34
|
-
/**
|
|
35
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Missing → invalid_request; mismatch → invalid_target. Info-only variant
|
|
55
|
+
* (no `Response` built) so a caller can choose how to deliver it —
|
|
56
|
+
* `/token` returns it directly as JSON via `firstResourceError` below;
|
|
57
|
+
* `/authorize` redirects it to the client's callback per RFC 6749 §4.1.2.1
|
|
58
|
+
* once `redirect_uri` itself is trusted (see `authorize.ts`).
|
|
59
|
+
*/
|
|
60
|
+
export const resourceErrorInfo = (requested, expected) => {
|
|
36
61
|
if (!requested) {
|
|
37
|
-
return
|
|
62
|
+
return { code: OAUTH_ERRORS.invalidRequest, description: "resource required (RFC 8707)" };
|
|
38
63
|
}
|
|
39
64
|
if (!resourcesEqual(requested, expected)) {
|
|
40
|
-
return
|
|
65
|
+
return { code: OAUTH_ERRORS.invalidTarget, description: "resource does not match this server" };
|
|
41
66
|
}
|
|
42
67
|
return null;
|
|
43
68
|
};
|
|
69
|
+
/** Missing → invalid_request; mismatch → invalid_target. */
|
|
70
|
+
export const firstResourceError = (requested, expected) => {
|
|
71
|
+
const issue = resourceErrorInfo(requested, expected);
|
|
72
|
+
return issue ? oauthError(issue.code, 400, issue.description) : null;
|
|
73
|
+
};
|
|
44
74
|
//# sourceMappingURL=resource.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/oauth/resource.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"resource.js","sourceRoot":"","sources":["../../src/oauth/resource.ts"],"names":[],"mappings":"AAAA,wDAAwD;AAExD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAuB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,IAAY,EAAU,EAAE,CAC9D,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AAExD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAE,YAAY,GAAG,qBAAqB,EAAU,EAAE;IAChG,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACvC,MAAM,gBAAgB,GAAG,YAAY,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,YAAY,EAAE,CAAC;IAC1F,OAAO,GAAG,IAAI,GAAG,uBAAuB,CAAC,gBAAgB,CAAC,EAAE,CAAC;AAC/D,CAAC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,CAAC,GAAW,EAAiB,EAAE;IACvD,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACxB,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;QAClD,OAAO,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAiB,EAAE,QAAgB,EAAW,EAAE;IAC7E,MAAM,CAAC,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACvC,OAAO,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AACzD,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,SAAiB,EAAE,QAAgB,EAAyB,EAAE;IAC9F,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,cAAc,EAAE,WAAW,EAAE,8BAA8B,EAAE,CAAC;IAC5F,CAAC;IACD,IAAI,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC;QACzC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,aAAa,EAAE,WAAW,EAAE,qCAAqC,EAAE,CAAC;IAClG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC;AAEF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,SAAiB,EAAE,QAAgB,EAAmB,EAAE;IACzF,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACrD,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../src/oauth/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;
|
|
1
|
+
{"version":3,"file":"revoke.d.ts","sourceRoot":"","sources":["../../src/oauth/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAOjC,OAAO,EAAE,KAAK,kBAAkB,EAAc,MAAM,YAAY,CAAC;AAQjE,eAAO,MAAM,YAAY,GACvB,SAAS,OAAO,EAChB,SAAS,kBAAkB,KAC1B,OAAO,CAAC,QAAQ,CA8BlB,CAAC"}
|
package/dist/oauth/revoke.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/** RFC 7009 token revocation. */
|
|
2
|
+
import { BodyTooLargeError } from "../body.js";
|
|
2
3
|
import { requireHttpMethod } from "../http.js";
|
|
3
4
|
import { firstClientAuthError, readClientAuth } from "./clientauth.js";
|
|
4
5
|
import { OAUTH_ERRORS } from "./constants.js";
|
|
6
|
+
import { readOAuthBody } from "./reqbody.js";
|
|
5
7
|
import { oauthError } from "./types.js";
|
|
6
8
|
const POST_ONLY = new Set(["POST"]);
|
|
7
9
|
const NO_CORS = { cors: false };
|
|
@@ -10,20 +12,30 @@ export const handleRevoke = async (request, options) => {
|
|
|
10
12
|
const methodError = requireHttpMethod(request, POST_ONLY, NO_CORS);
|
|
11
13
|
if (methodError)
|
|
12
14
|
return methodError;
|
|
13
|
-
|
|
15
|
+
let body;
|
|
16
|
+
try {
|
|
17
|
+
body = await readOAuthBody(request);
|
|
18
|
+
}
|
|
19
|
+
catch (exc) {
|
|
20
|
+
if (exc instanceof BodyTooLargeError) {
|
|
21
|
+
return oauthError(OAUTH_ERRORS.invalidRequest, 413, "request body too large");
|
|
22
|
+
}
|
|
23
|
+
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "malformed request body");
|
|
24
|
+
}
|
|
14
25
|
const token = body.token ?? "";
|
|
15
26
|
if (!token)
|
|
16
27
|
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "token required");
|
|
17
28
|
const auth = readClientAuth(request, body);
|
|
18
29
|
if (!auth)
|
|
19
30
|
return oauthError(OAUTH_ERRORS.invalidRequest, 400, "client_id required");
|
|
20
|
-
const clientAuthError = await firstClientAuthError(auth, options);
|
|
31
|
+
const clientAuthError = await firstClientAuthError(auth, options, request);
|
|
21
32
|
if (clientAuthError)
|
|
22
33
|
return clientAuthError;
|
|
34
|
+
const hint = tokenTypeHintOf(body.token_type_hint);
|
|
23
35
|
await options.ports.revokeToken?.({
|
|
24
36
|
token,
|
|
25
37
|
clientId: auth.clientId,
|
|
26
|
-
tokenTypeHint:
|
|
38
|
+
...(hint !== undefined ? { tokenTypeHint: hint } : {}),
|
|
27
39
|
});
|
|
28
40
|
return new Response(null, { status: 200, headers: { "Cache-Control": "no-store" } });
|
|
29
41
|
};
|
package/dist/oauth/revoke.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../src/oauth/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../src/oauth/revoke.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAA2B,UAAU,EAAE,MAAM,YAAY,CAAC;AAEjE,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACpC,MAAM,OAAO,GAAG,EAAE,IAAI,EAAE,KAAK,EAAW,CAAC;AAEzC,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,IAAI,IAA4B,CAAC;IACjC,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,iBAAiB,EAAE,CAAC;YACrC,OAAO,UAAU,CAAC,YAAY,CAAC,cAAc,EAAE,GAAG,EAAE,wBAAwB,CAAC,CAAC;QAChF,CAAC;QACD,OAAO,UAAU,CAAC,YAAY,CAAC,cAAc,EAAE,GAAG,EAAE,wBAAwB,CAAC,CAAC;IAChF,CAAC;IACD,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"}
|
package/dist/oauth/router.d.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { type OAuthRouterOptions } from "./types.js";
|
|
2
|
-
export type {
|
|
2
|
+
export type { MintedToken, OAuthAuditEntry, OAuthPorts, OAuthRouterOptions, OAuthUser, } from "./types.js";
|
|
3
3
|
export type OAuthRouter = {
|
|
4
4
|
/** Returns a Response when this request is OAuth-related; otherwise null. */
|
|
5
5
|
tryHandle: (request: Request) => Promise<Response | null>;
|
|
6
6
|
};
|
|
7
7
|
export declare const createOAuthRouter: (options: OAuthRouterOptions) => OAuthRouter;
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
10
|
-
export {
|
|
11
|
-
export {
|
|
12
|
-
export {
|
|
13
|
-
export {
|
|
14
|
-
export {
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
8
|
+
export { type ClientAuth, firstClientAuthError, readClientAuth } from "./clientauth.js";
|
|
9
|
+
export { type AuthCodeRecord, type CodeStore, consumeAuthCode, issueAuthCode, newClientId, } from "./codes.js";
|
|
10
|
+
export type { ConsentOptions, ConsentRequest } from "./consent.js";
|
|
11
|
+
export { buildErrorRedirectUrl } from "./consent.js";
|
|
12
|
+
export { DEFAULT_SCOPE, GRANT_TYPES, OAUTH_ERRORS, TOKEN_ENDPOINT_AUTH_METHODS, type TokenEndpointAuthMethod, } from "./constants.js";
|
|
13
|
+
export { authorizationServerMetadata, mcpWwwAuthenticate, protectedResourceMetadata, } from "./metadata.js";
|
|
14
|
+
export { randomBase64Url, sha256Base64Url, verifyPkceS256 } from "./pkce.js";
|
|
15
|
+
export { CLAUDE_CALLBACK, isAllowedRedirectUri } from "./redirect.js";
|
|
16
|
+
export { canonicalResource, DEFAULT_RESOURCE_PATH, firstResourceError, normalizeConfiguredPath, resourceErrorInfo, resourcesEqual, } from "./resource.js";
|
|
17
|
+
export { firstScopeError, formatScope, parseScope, requestedScopes, scopeErrorInfo, } from "./scope.js";
|
|
18
|
+
export { hashClientSecret, verifyClientSecret } from "./secrethash.js";
|
|
19
|
+
export type { ClientAssertion, ClientStore, MintAccessTokenInput, OAuthErrorInfo, RefreshAccessTokenInput, RegisteredClient, RevokeTokenInput, } from "./types.js";
|
|
20
|
+
export { advertisedScopes, defaultScopes, registeredClientsRequired, unregisteredClientsAllowed, } from "./types.js";
|
|
18
21
|
//# sourceMappingURL=router.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/oauth/router.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/oauth/router.ts"],"names":[],"mappings":"AAUA,OAAO,EAIL,KAAK,kBAAkB,EAKxB,MAAM,YAAY,CAAC;AAGpB,YAAY,EACV,WAAW,EACX,eAAe,EACf,UAAU,EACV,kBAAkB,EAClB,SAAS,GACV,MAAM,YAAY,CAAC;AA8DpB,MAAM,MAAM,WAAW,GAAG;IACxB,6EAA6E;IAC7E,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CAC3D,CAAC;AAIF,eAAO,MAAM,iBAAiB,GAAI,SAAS,kBAAkB,KAAG,WA0F/D,CAAC;AAEF,OAAO,EAAE,KAAK,UAAU,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxF,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,eAAe,EACf,aAAa,EACb,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,aAAa,EACb,WAAW,EACX,YAAY,EACZ,2BAA2B,EAC3B,KAAK,uBAAuB,GAC7B,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,yBAAyB,GAC1B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,uBAAuB,EACvB,iBAAiB,EACjB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,eAAe,EACf,WAAW,EACX,UAAU,EACV,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACvE,YAAY,EACV,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,cAAc,EACd,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,gBAAgB,EAChB,aAAa,EACb,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,YAAY,CAAC"}
|
package/dist/oauth/router.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { BodyTooLargeError, DEFAULT_OAUTH_BODY_LIMIT, readBoundedText } from "../body.js";
|
|
1
2
|
import { INTERNAL_ERROR, jsonResponse, requireHttpMethod } from "../http.js";
|
|
2
|
-
import {
|
|
3
|
+
import { handleAuthorize } from "./authorize.js";
|
|
4
|
+
import { handleConsent } from "./consent.js";
|
|
3
5
|
import { GRANT_TYPES, OAUTH_ERRORS } from "./constants.js";
|
|
4
6
|
import { CLAUDE_CALLBACK, isAllowedRedirectUri } from "./redirect.js";
|
|
5
|
-
import { DEFAULT_RESOURCE_PATH } from "./resource.js";
|
|
6
|
-
import { oauthError, unregisteredClientsAllowed, } from "./types.js";
|
|
7
|
-
import { handleAuthorize } from "./authorize.js";
|
|
8
|
-
import { handleToken } from "./token.js";
|
|
7
|
+
import { DEFAULT_RESOURCE_PATH, normalizeConfiguredPath } from "./resource.js";
|
|
9
8
|
import { handleRevoke } from "./revoke.js";
|
|
9
|
+
import { seal } from "./sealed.js";
|
|
10
|
+
import { handleToken } from "./token.js";
|
|
11
|
+
import { assertCodeSecret, assertScopeConfig, oauthError, resolveSecret, safeOAuthAudit, unregisteredClientsAllowed, } from "./types.js";
|
|
10
12
|
import { handleWellKnown } from "./wellknown.js";
|
|
11
13
|
const POST_ONLY = new Set(["POST"]);
|
|
12
14
|
const handleRegister = async (request, options) => {
|
|
@@ -16,7 +18,8 @@ const handleRegister = async (request, options) => {
|
|
|
16
18
|
let suppliedRedirectUris = false;
|
|
17
19
|
let redirectUris = [];
|
|
18
20
|
try {
|
|
19
|
-
const
|
|
21
|
+
const text = await readBoundedText(request, DEFAULT_OAUTH_BODY_LIMIT);
|
|
22
|
+
const body = JSON.parse(text);
|
|
20
23
|
const raw = body.redirect_uris;
|
|
21
24
|
if (Array.isArray(raw)) {
|
|
22
25
|
suppliedRedirectUris = true;
|
|
@@ -25,7 +28,10 @@ const handleRegister = async (request, options) => {
|
|
|
25
28
|
.filter((uri) => isAllowedRedirectUri(uri, options.redirect));
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
|
-
catch {
|
|
31
|
+
catch (exc) {
|
|
32
|
+
if (exc instanceof BodyTooLargeError) {
|
|
33
|
+
return oauthError(OAUTH_ERRORS.invalidRequest, 413, "request body too large");
|
|
34
|
+
}
|
|
29
35
|
redirectUris = [];
|
|
30
36
|
}
|
|
31
37
|
// No redirect_uris in the body: fall back to the Claude callback only if
|
|
@@ -40,8 +46,14 @@ const handleRegister = async (request, options) => {
|
|
|
40
46
|
? "no allowed redirect_uris"
|
|
41
47
|
: "redirect_uris required — this server has no default redirect_uri");
|
|
42
48
|
}
|
|
49
|
+
// Zero storage: the client_id *is* the registration record — a sealed,
|
|
50
|
+
// self-verifying assertion of the redirect_uris this call just validated.
|
|
51
|
+
// `/authorize` unseals it and binds the client to exactly this list,
|
|
52
|
+
// the same protection a stored registration would give a pre-registered id.
|
|
53
|
+
const secret = await resolveSecret(options.ports, request);
|
|
54
|
+
const clientId = await seal(secret, "client", { redirectUris });
|
|
43
55
|
return jsonResponse({
|
|
44
|
-
client_id:
|
|
56
|
+
client_id: clientId,
|
|
45
57
|
client_id_issued_at: Math.floor(Date.now() / 1000),
|
|
46
58
|
token_endpoint_auth_method: "none",
|
|
47
59
|
redirect_uris: redirectUris,
|
|
@@ -54,8 +66,26 @@ export const createOAuthRouter = (options) => {
|
|
|
54
66
|
throw new Error("allowUnregisteredClients: false requires ports.clientStore " +
|
|
55
67
|
"so pre-registered clients can be resolved");
|
|
56
68
|
}
|
|
57
|
-
|
|
69
|
+
// Function-form codeSecret is re-validated on every call in resolveSecret,
|
|
70
|
+
// since its value can vary per request; a string form is fixed for the
|
|
71
|
+
// life of this router, so fail fast at construction instead of at the
|
|
72
|
+
// first request.
|
|
73
|
+
if (typeof options.ports.codeSecret === "string") {
|
|
74
|
+
assertCodeSecret(options.ports.codeSecret);
|
|
75
|
+
}
|
|
76
|
+
assertScopeConfig(options);
|
|
77
|
+
// Normalized once here, not per request — "/mcp/" and "/mcp" must never
|
|
78
|
+
// produce divergent canonical resources (canonicalResource doesn't strip
|
|
79
|
+
// a trailing slash itself; normalizeResource, used to compare an
|
|
80
|
+
// incoming request's resource against it, does).
|
|
81
|
+
const resourcePath = normalizeConfiguredPath(options.resourcePath ?? DEFAULT_RESOURCE_PATH);
|
|
58
82
|
const oauthPath = options.oauthPath ?? "/mcp/oauth";
|
|
83
|
+
if (oauthPath === resourcePath) {
|
|
84
|
+
throw new Error(`oauthPath (${oauthPath}) must not equal resourcePath — they would shadow each other`);
|
|
85
|
+
}
|
|
86
|
+
if (resourcePath.startsWith("/.well-known")) {
|
|
87
|
+
throw new Error(`resourcePath (${resourcePath}) must not start with /.well-known — reserved for OAuth discovery documents`);
|
|
88
|
+
}
|
|
59
89
|
const prmPaths = new Set([
|
|
60
90
|
"/.well-known/oauth-protected-resource",
|
|
61
91
|
`/.well-known/oauth-protected-resource${resourcePath}`,
|
|
@@ -71,6 +101,7 @@ export const createOAuthRouter = (options) => {
|
|
|
71
101
|
}
|
|
72
102
|
: {}),
|
|
73
103
|
[`${oauthPath}/authorize`]: (request) => handleAuthorize(request, options),
|
|
104
|
+
[`${oauthPath}/consent`]: (request) => handleConsent(request, options),
|
|
74
105
|
[`${oauthPath}/token`]: (request) => handleToken(request, options),
|
|
75
106
|
...(options.ports.revokeToken
|
|
76
107
|
? {
|
|
@@ -93,22 +124,31 @@ export const createOAuthRouter = (options) => {
|
|
|
93
124
|
const route = routes[path];
|
|
94
125
|
return route ? await route(request) : null;
|
|
95
126
|
}
|
|
96
|
-
catch {
|
|
97
|
-
// A host port (resolveUser/mintAccessToken/clientStore/...) threw
|
|
98
|
-
//
|
|
99
|
-
//
|
|
127
|
+
catch (exc) {
|
|
128
|
+
// A host port (resolveUser/mintAccessToken/clientStore/...) threw,
|
|
129
|
+
// or codeSecret failed validation (assertCodeSecret, e.g. a
|
|
130
|
+
// per-request function returning something too short). We only
|
|
131
|
+
// ever reach here on a route this router owns, so answer with a
|
|
132
|
+
// real error instead of throwing out of `tryHandle` — the caller
|
|
133
|
+
// gets the generic server_error; ports.audit gets the real reason.
|
|
134
|
+
await safeOAuthAudit(options, {
|
|
135
|
+
event: "server_error",
|
|
136
|
+
reason: exc instanceof Error ? exc.message : String(exc),
|
|
137
|
+
});
|
|
100
138
|
return oauthError(OAUTH_ERRORS.serverError, 500, INTERNAL_ERROR);
|
|
101
139
|
}
|
|
102
140
|
},
|
|
103
141
|
};
|
|
104
142
|
};
|
|
105
|
-
export {
|
|
106
|
-
export {
|
|
107
|
-
export {
|
|
108
|
-
export {
|
|
109
|
-
export {
|
|
110
|
-
export {
|
|
111
|
-
export {
|
|
112
|
-
export {
|
|
113
|
-
export {
|
|
143
|
+
export { firstClientAuthError, readClientAuth } from "./clientauth.js";
|
|
144
|
+
export { consumeAuthCode, issueAuthCode, newClientId, } from "./codes.js";
|
|
145
|
+
export { buildErrorRedirectUrl } from "./consent.js";
|
|
146
|
+
export { DEFAULT_SCOPE, GRANT_TYPES, OAUTH_ERRORS, TOKEN_ENDPOINT_AUTH_METHODS, } from "./constants.js";
|
|
147
|
+
export { authorizationServerMetadata, mcpWwwAuthenticate, protectedResourceMetadata, } from "./metadata.js";
|
|
148
|
+
export { randomBase64Url, sha256Base64Url, verifyPkceS256 } from "./pkce.js";
|
|
149
|
+
export { CLAUDE_CALLBACK, isAllowedRedirectUri } from "./redirect.js";
|
|
150
|
+
export { canonicalResource, DEFAULT_RESOURCE_PATH, firstResourceError, normalizeConfiguredPath, resourceErrorInfo, resourcesEqual, } from "./resource.js";
|
|
151
|
+
export { firstScopeError, formatScope, parseScope, requestedScopes, scopeErrorInfo, } from "./scope.js";
|
|
152
|
+
export { hashClientSecret, verifyClientSecret } from "./secrethash.js";
|
|
153
|
+
export { advertisedScopes, defaultScopes, registeredClientsRequired, unregisteredClientsAllowed, } from "./types.js";
|
|
114
154
|
//# sourceMappingURL=router.js.map
|
package/dist/oauth/router.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/oauth/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/oauth/router.ts"],"names":[],"mappings":"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"}
|
package/dist/oauth/scope.d.ts
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
/** OAuth scope handling — space-delimited sets (RFC 6749 §3.3). */
|
|
2
|
+
import { type OAuthErrorInfo } from "./types.js";
|
|
2
3
|
export declare const parseScope: (raw: string) => string[];
|
|
3
4
|
export declare const formatScope: (scopes: string[]) => string;
|
|
4
5
|
/**
|
|
5
|
-
* Scopes the client asked for, or
|
|
6
|
+
* Scopes the client asked for, or `fallback` when `scope` is omitted —
|
|
7
|
+
* `fallback` is the full advertised set only for single-scope servers; see
|
|
8
|
+
* `defaultScopes`/`assertScopeConfig` in `types.ts` for the multi-scope rule.
|
|
6
9
|
* Duplicates are collapsed so the granted string is stable.
|
|
7
10
|
*/
|
|
8
|
-
export declare const requestedScopes: (raw: string,
|
|
11
|
+
export declare const requestedScopes: (raw: string, fallback: string[]) => string[];
|
|
12
|
+
/**
|
|
13
|
+
* Any scope outside the advertised set → invalid_scope. Info-only variant
|
|
14
|
+
* — see `resource.ts`'s `resourceErrorInfo` for why this isn't a
|
|
15
|
+
* `Response` directly: `/authorize` needs to redirect this, not return it.
|
|
16
|
+
*/
|
|
17
|
+
export declare const scopeErrorInfo: (requested: string[], advertised: string[]) => OAuthErrorInfo | null;
|
|
9
18
|
/** Any scope outside the advertised set → invalid_scope. */
|
|
10
19
|
export declare const firstScopeError: (requested: string[], advertised: string[]) => Response | null;
|
|
11
20
|
//# sourceMappingURL=scope.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/oauth/scope.ts"],"names":[],"mappings":"AAAA,mEAAmE;
|
|
1
|
+
{"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/oauth/scope.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAGnE,OAAO,EAAE,KAAK,cAAc,EAAc,MAAM,YAAY,CAAC;AAE7D,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAAM,EAIP,CAAC;AAEzC,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM,EAAE,KAAG,MAA0B,CAAC;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,EAAE,UAAU,MAAM,EAAE,KAAG,MAAM,EAGvE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,GACzB,WAAW,MAAM,EAAE,EACnB,YAAY,MAAM,EAAE,KACnB,cAAc,GAAG,IAKnB,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,eAAe,GAAI,WAAW,MAAM,EAAE,EAAE,YAAY,MAAM,EAAE,KAAG,QAAQ,GAAG,IAGtF,CAAC"}
|
package/dist/oauth/scope.js
CHANGED
|
@@ -7,18 +7,29 @@ export const parseScope = (raw) => raw
|
|
|
7
7
|
.filter((entry) => entry.length > 0);
|
|
8
8
|
export const formatScope = (scopes) => scopes.join(" ");
|
|
9
9
|
/**
|
|
10
|
-
* Scopes the client asked for, or
|
|
10
|
+
* Scopes the client asked for, or `fallback` when `scope` is omitted —
|
|
11
|
+
* `fallback` is the full advertised set only for single-scope servers; see
|
|
12
|
+
* `defaultScopes`/`assertScopeConfig` in `types.ts` for the multi-scope rule.
|
|
11
13
|
* Duplicates are collapsed so the granted string is stable.
|
|
12
14
|
*/
|
|
13
|
-
export const requestedScopes = (raw,
|
|
14
|
-
const parsed = raw ? parseScope(raw) : [...
|
|
15
|
+
export const requestedScopes = (raw, fallback) => {
|
|
16
|
+
const parsed = raw ? parseScope(raw) : [...fallback];
|
|
15
17
|
return [...new Set(parsed)];
|
|
16
18
|
};
|
|
19
|
+
/**
|
|
20
|
+
* Any scope outside the advertised set → invalid_scope. Info-only variant
|
|
21
|
+
* — see `resource.ts`'s `resourceErrorInfo` for why this isn't a
|
|
22
|
+
* `Response` directly: `/authorize` needs to redirect this, not return it.
|
|
23
|
+
*/
|
|
24
|
+
export const scopeErrorInfo = (requested, advertised) => {
|
|
25
|
+
const unknown = requested.find((scope) => !advertised.includes(scope));
|
|
26
|
+
return unknown === undefined
|
|
27
|
+
? null
|
|
28
|
+
: { code: OAUTH_ERRORS.invalidScope, description: `unsupported scope: ${unknown}` };
|
|
29
|
+
};
|
|
17
30
|
/** Any scope outside the advertised set → invalid_scope. */
|
|
18
31
|
export const firstScopeError = (requested, advertised) => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
return null;
|
|
22
|
-
return oauthError(OAUTH_ERRORS.invalidScope, 400, `unsupported scope: ${unknown}`);
|
|
32
|
+
const issue = scopeErrorInfo(requested, advertised);
|
|
33
|
+
return issue ? oauthError(issue.code, 400, issue.description) : null;
|
|
23
34
|
};
|
|
24
35
|
//# sourceMappingURL=scope.js.map
|
package/dist/oauth/scope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/oauth/scope.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,
|
|
1
|
+
{"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/oauth/scope.ts"],"names":[],"mappings":"AAAA,mEAAmE;AAEnE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAuB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7D,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAY,EAAE,CAClD,GAAG;KACA,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;KAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAgB,EAAU,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE1E;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAW,EAAE,QAAkB,EAAY,EAAE;IAC3E,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9B,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAC5B,SAAmB,EACnB,UAAoB,EACG,EAAE;IACzB,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IACvE,OAAO,OAAO,KAAK,SAAS;QAC1B,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,CAAC,YAAY,EAAE,WAAW,EAAE,sBAAsB,OAAO,EAAE,EAAE,CAAC;AACxF,CAAC,CAAC;AAEF,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,SAAmB,EAAE,UAAoB,EAAmB,EAAE;IAC5F,MAAM,KAAK,GAAG,cAAc,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-GCM sealing for short-lived, self-describing tokens derived from the
|
|
3
|
+
* same `codeSecret` as auth codes (HMAC today, AES-GCM from Phase 2 —
|
|
4
|
+
* see `codes.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Domain separation is mandatory, not a nice-to-have: a consent ticket must
|
|
7
|
+
* never unseal as a client-id assertion, and neither must ever unseal as an
|
|
8
|
+
* auth code. HKDF derives an independent AES-256 key per `SealedType` from
|
|
9
|
+
* the same secret, and the type is also bound in as AEAD associated data —
|
|
10
|
+
* two independent reasons decryption fails across types, not one.
|
|
11
|
+
*/
|
|
12
|
+
export type SealedType = "consent" | "client" | "code";
|
|
13
|
+
/** Seals `payload` as an opaque, type-bound string. */
|
|
14
|
+
export declare const seal: <T>(secret: string, type: SealedType, payload: T) => Promise<string>;
|
|
15
|
+
/** Unseals a string sealed with the same `secret` and `type`; `null` on any mismatch or tamper. */
|
|
16
|
+
export declare const unseal: <T>(secret: string, type: SealedType, sealed: string) => Promise<T | null>;
|
|
17
|
+
//# sourceMappingURL=sealed.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed.d.ts","sourceRoot":"","sources":["../../src/oauth/sealed.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAC;AA+BvD,uDAAuD;AACvD,eAAO,MAAM,IAAI,GAAU,CAAC,EAAE,QAAQ,MAAM,EAAE,MAAM,UAAU,EAAE,SAAS,CAAC,KAAG,OAAO,CAAC,MAAM,CAW1F,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,MAAM,GAAU,CAAC,EAC5B,QAAQ,MAAM,EACd,MAAM,UAAU,EAChB,QAAQ,MAAM,KACb,OAAO,CAAC,CAAC,GAAG,IAAI,CAsBlB,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-GCM sealing for short-lived, self-describing tokens derived from the
|
|
3
|
+
* same `codeSecret` as auth codes (HMAC today, AES-GCM from Phase 2 —
|
|
4
|
+
* see `codes.ts`).
|
|
5
|
+
*
|
|
6
|
+
* Domain separation is mandatory, not a nice-to-have: a consent ticket must
|
|
7
|
+
* never unseal as a client-id assertion, and neither must ever unseal as an
|
|
8
|
+
* auth code. HKDF derives an independent AES-256 key per `SealedType` from
|
|
9
|
+
* the same secret, and the type is also bound in as AEAD associated data —
|
|
10
|
+
* two independent reasons decryption fails across types, not one.
|
|
11
|
+
*/
|
|
12
|
+
import { bytesToBase64Url, fromBase64Url } from "./base64url.js";
|
|
13
|
+
const HKDF_INFO_PREFIX = "mcp-trellis:sealed:";
|
|
14
|
+
const IV_BYTES = 12;
|
|
15
|
+
const deriveKey = async (secret, type, usage) => {
|
|
16
|
+
const material = await crypto.subtle.importKey("raw", new TextEncoder().encode(secret), "HKDF", false, ["deriveKey"]);
|
|
17
|
+
return crypto.subtle.deriveKey({
|
|
18
|
+
name: "HKDF",
|
|
19
|
+
hash: "SHA-256",
|
|
20
|
+
salt: new Uint8Array(0),
|
|
21
|
+
info: new TextEncoder().encode(`${HKDF_INFO_PREFIX}${type}`),
|
|
22
|
+
}, material, { name: "AES-GCM", length: 256 }, false, [usage]);
|
|
23
|
+
};
|
|
24
|
+
/** Seals `payload` as an opaque, type-bound string. */
|
|
25
|
+
export const seal = async (secret, type, payload) => {
|
|
26
|
+
const key = await deriveKey(secret, type, "encrypt");
|
|
27
|
+
const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
|
|
28
|
+
const additionalData = new TextEncoder().encode(type);
|
|
29
|
+
const plaintext = new TextEncoder().encode(JSON.stringify(payload));
|
|
30
|
+
const ciphertext = await crypto.subtle.encrypt({ name: "AES-GCM", iv, additionalData }, key, plaintext);
|
|
31
|
+
return `${bytesToBase64Url(iv)}.${bytesToBase64Url(ciphertext)}`;
|
|
32
|
+
};
|
|
33
|
+
/** Unseals a string sealed with the same `secret` and `type`; `null` on any mismatch or tamper. */
|
|
34
|
+
export const unseal = async (secret, type, sealed) => {
|
|
35
|
+
if (!sealed)
|
|
36
|
+
return null;
|
|
37
|
+
const parts = sealed.split(".");
|
|
38
|
+
if (parts.length !== 2)
|
|
39
|
+
return null;
|
|
40
|
+
const [ivPart, ciphertextPart] = parts;
|
|
41
|
+
if (!ivPart || !ciphertextPart)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
const key = await deriveKey(secret, type, "decrypt");
|
|
45
|
+
const iv = fromBase64Url(ivPart);
|
|
46
|
+
if (iv.byteLength !== IV_BYTES)
|
|
47
|
+
return null;
|
|
48
|
+
const ciphertext = fromBase64Url(ciphertextPart);
|
|
49
|
+
const additionalData = new TextEncoder().encode(type);
|
|
50
|
+
const plaintext = await crypto.subtle.decrypt({ name: "AES-GCM", iv, additionalData }, key, ciphertext);
|
|
51
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=sealed.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sealed.js","sourceRoot":"","sources":["../../src/oauth/sealed.ts"],"names":[],"mappings":"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"}
|