@webpieces/core-util 0.4.880 → 0.4.882

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.
Files changed (49) hide show
  1. package/package.json +1 -1
  2. package/src/errors/ApiCallSite.d.ts +58 -0
  3. package/src/errors/ApiCallSite.js +96 -0
  4. package/src/errors/ApiCallSite.js.map +1 -0
  5. package/src/errors/ApiError.d.ts +9 -0
  6. package/src/errors/ApiError.js.map +1 -1
  7. package/src/errors/ReportableApiFailure.d.ts +43 -0
  8. package/src/errors/ReportableApiFailure.js +75 -0
  9. package/src/errors/ReportableApiFailure.js.map +1 -0
  10. package/src/errors/index.d.ts +3 -0
  11. package/src/errors/index.js +6 -1
  12. package/src/errors/index.js.map +1 -1
  13. package/src/http/WebpiecesDefaultErrorTranslator.d.ts +3 -0
  14. package/src/http/WebpiecesDefaultErrorTranslator.js +10 -0
  15. package/src/http/WebpiecesDefaultErrorTranslator.js.map +1 -1
  16. package/src/http/WebpiecesDefaultFailureClassifier.d.ts +3 -2
  17. package/src/http/WebpiecesDefaultFailureClassifier.js +6 -3
  18. package/src/http/WebpiecesDefaultFailureClassifier.js.map +1 -1
  19. package/src/http/api-type.js +2 -2
  20. package/src/http/api-type.js.map +1 -1
  21. package/src/index.d.ts +8 -2
  22. package/src/index.js +20 -1
  23. package/src/index.js.map +1 -1
  24. package/src/mcp/McpCompileAssertions.js +14 -4
  25. package/src/mcp/McpCompileAssertions.js.map +1 -1
  26. package/src/mcp/McpMetadata.d.ts +25 -13
  27. package/src/mcp/McpMetadata.js +31 -14
  28. package/src/mcp/McpMetadata.js.map +1 -1
  29. package/src/mcp/McpToolCatalogFile.d.ts +14 -2
  30. package/src/mcp/McpToolCatalogFile.js +24 -3
  31. package/src/mcp/McpToolCatalogFile.js.map +1 -1
  32. package/src/oauth/OAuthAuthorizationErrorRedirect.d.ts +37 -0
  33. package/src/oauth/OAuthAuthorizationErrorRedirect.js +61 -0
  34. package/src/oauth/OAuthAuthorizationErrorRedirect.js.map +1 -0
  35. package/src/oauth/OAuthBearerChallenge.d.ts +31 -0
  36. package/src/oauth/OAuthBearerChallenge.js +52 -0
  37. package/src/oauth/OAuthBearerChallenge.js.map +1 -0
  38. package/src/oauth/OAuthErrorCode.d.ts +69 -0
  39. package/src/oauth/OAuthErrorCode.js +89 -0
  40. package/src/oauth/OAuthErrorCode.js.map +1 -0
  41. package/src/oauth/OAuthErrorResponse.d.ts +34 -0
  42. package/src/oauth/OAuthErrorResponse.js +74 -0
  43. package/src/oauth/OAuthErrorResponse.js.map +1 -0
  44. package/src/oauth/OAuthProtocolError.d.ts +61 -0
  45. package/src/oauth/OAuthProtocolError.js +83 -0
  46. package/src/oauth/OAuthProtocolError.js.map +1 -0
  47. package/src/oauth/OAuthProtocolErrorCompileAssertions.d.ts +10 -0
  48. package/src/oauth/OAuthProtocolErrorCompileAssertions.js +39 -0
  49. package/src/oauth/OAuthProtocolErrorCompileAssertions.js.map +1 -0
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuthErrorResponse = exports.OAuthErrorBody = void 0;
4
+ const HttpResponseDto_1 = require("../http/HttpResponseDto");
5
+ const OAuthBearerChallenge_1 = require("./OAuthBearerChallenge");
6
+ const OAuthErrorCode_1 = require("./OAuthErrorCode");
7
+ /**
8
+ * The RFC 6749 §5.2 / RFC 6750 §3 error body: `{ "error": "...", "error_description"?: "...",
9
+ * "error_uri"?: "..." }`. The property names ARE the wire names, snake_case, because an OAuth client
10
+ * parses exactly these keys. An absent optional is left off the JSON entirely.
11
+ */
12
+ class OAuthErrorBody {
13
+ error;
14
+ error_description;
15
+ error_uri;
16
+ constructor(error, description, errorUri) {
17
+ this.error = error;
18
+ if (description !== undefined)
19
+ this.error_description = OAuthErrorCode_1.OAuthText.sanitize(description);
20
+ if (errorUri !== undefined)
21
+ this.error_uri = errorUri;
22
+ }
23
+ }
24
+ exports.OAuthErrorBody = OAuthErrorBody;
25
+ /**
26
+ * {@link OAuthProtocolError} -> the exact HTTP response. ONE renderer, used by every boundary that
27
+ * can meet one (`WebpiecesDefaultErrorTranslator`, `OAuthExpressErrorHandler`, the MCP bearer
28
+ * boundary), so an OAuth client gets the same bytes whichever path the refusal took.
29
+ *
30
+ * Headers: the error's `WWW-Authenticate` when it has one; otherwise `defaultChallenge` for a 401
31
+ * (the MCP boundary passes its own challenge, which carries `resource_metadata`); otherwise, for
32
+ * `invalid_token` / `insufficient_scope`, the minimal Bearer challenge RFC 6750 §3 requires. Every
33
+ * response also carries `Cache-Control: no-store` and `Pragma: no-cache` (RFC 6749 §5.1), so no
34
+ * intermediary caches an answer about a credential.
35
+ */
36
+ class OAuthErrorResponse {
37
+ static REASONS = new Map([
38
+ [400, 'Bad Request'],
39
+ [401, 'Unauthorized'],
40
+ [403, 'Forbidden'],
41
+ [500, 'Internal Server Error'],
42
+ [503, 'Service Unavailable'],
43
+ ]);
44
+ /**
45
+ * @param defaultChallenge - the boundary's own `WWW-Authenticate` for a 401 whose error carries
46
+ * none, or `undefined` when the boundary has no challenge of its own.
47
+ */
48
+ // webpieces-disable no-function-outside-class -- pure error -> response mapping shared by every boundary
49
+ static toResponse(error, defaultChallenge) {
50
+ const headers = [
51
+ new HttpResponseDto_1.HttpHeader('Cache-Control', 'no-store'),
52
+ new HttpResponseDto_1.HttpHeader('Pragma', 'no-cache'),
53
+ ];
54
+ const challenge = OAuthErrorResponse.challengeFor(error, defaultChallenge);
55
+ if (challenge !== undefined) {
56
+ headers.push(new HttpResponseDto_1.HttpHeader('WWW-Authenticate', challenge));
57
+ }
58
+ return new HttpResponseDto_1.HttpResponseDto(new HttpResponseDto_1.HttpResponseStatus(error.status, OAuthErrorResponse.REASONS.get(error.status) ?? ''), headers, new OAuthErrorBody(error.error, error.description, error.errorUri));
59
+ }
60
+ // webpieces-disable no-function-outside-class -- pure header choice owned by this renderer
61
+ static challengeFor(error, defaultChallenge) {
62
+ if (error.wwwAuthenticate !== undefined)
63
+ return error.wwwAuthenticate;
64
+ if (error.status === 401 && defaultChallenge !== undefined)
65
+ return defaultChallenge;
66
+ if (error.error === OAuthErrorCode_1.OAuthErrorCode.INVALID_TOKEN ||
67
+ error.error === OAuthErrorCode_1.OAuthErrorCode.INSUFFICIENT_SCOPE) {
68
+ return new OAuthBearerChallenge_1.OAuthBearerChallenge(error.error, error.description).toHeaderValue();
69
+ }
70
+ return undefined;
71
+ }
72
+ }
73
+ exports.OAuthErrorResponse = OAuthErrorResponse;
74
+ //# sourceMappingURL=OAuthErrorResponse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthErrorResponse.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/oauth/OAuthErrorResponse.ts"],"names":[],"mappings":";;;AAAA,6DAA0F;AAC1F,iEAA8D;AAC9D,qDAA8E;AAG9E;;;;GAIG;AACH,MAAa,cAAc;IACd,KAAK,CAAiB;IACtB,iBAAiB,CAAU;IAC3B,SAAS,CAAU;IAE5B,YAAY,KAAqB,EAAE,WAAoB,EAAE,QAAiB;QACtE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,WAAW,KAAK,SAAS;YAAE,IAAI,CAAC,iBAAiB,GAAG,0BAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QACxF,IAAI,QAAQ,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC1D,CAAC;CACJ;AAVD,wCAUC;AAED;;;;;;;;;;GAUG;AACH,MAAa,kBAAkB;IACnB,MAAM,CAAU,OAAO,GAAyC,IAAI,GAAG,CAG7E;QACE,CAAC,GAAG,EAAE,aAAa,CAAC;QACpB,CAAC,GAAG,EAAE,cAAc,CAAC;QACrB,CAAC,GAAG,EAAE,WAAW,CAAC;QAClB,CAAC,GAAG,EAAE,uBAAuB,CAAC;QAC9B,CAAC,GAAG,EAAE,qBAAqB,CAAC;KAC/B,CAAC,CAAC;IAEH;;;OAGG;IACH,yGAAyG;IACzG,MAAM,CAAC,UAAU,CACb,KAAyB,EACzB,gBAAoC;QAEpC,MAAM,OAAO,GAAG;YACZ,IAAI,4BAAU,CAAC,eAAe,EAAE,UAAU,CAAC;YAC3C,IAAI,4BAAU,CAAC,QAAQ,EAAE,UAAU,CAAC;SACvC,CAAC;QACF,MAAM,SAAS,GAAG,kBAAkB,CAAC,YAAY,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;QAC3E,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC,IAAI,4BAAU,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,iCAAe,CACtB,IAAI,oCAAkB,CAClB,KAAK,CAAC,MAAM,EACZ,kBAAkB,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CACrD,EACD,OAAO,EACP,IAAI,cAAc,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CACrE,CAAC;IACN,CAAC;IAED,2FAA2F;IACnF,MAAM,CAAC,YAAY,CACvB,KAAyB,EACzB,gBAAoC;QAEpC,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC,eAAe,CAAC;QACtE,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,IAAI,gBAAgB,KAAK,SAAS;YAAE,OAAO,gBAAgB,CAAC;QACpF,IACI,KAAK,CAAC,KAAK,KAAK,+BAAc,CAAC,aAAa;YAC5C,KAAK,CAAC,KAAK,KAAK,+BAAc,CAAC,kBAAkB,EACnD,CAAC;YACC,OAAO,IAAI,2CAAoB,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,aAAa,EAAE,CAAC;QACpF,CAAC;QACD,OAAO,SAAS,CAAC;IACrB,CAAC;;AArDL,gDAsDC","sourcesContent":["import { HttpHeader, HttpResponseDto, HttpResponseStatus } from '../http/HttpResponseDto';\nimport { OAuthBearerChallenge } from './OAuthBearerChallenge';\nimport { OAuthErrorCode, OAuthHttpStatus, OAuthText } from './OAuthErrorCode';\nimport { OAuthProtocolError } from './OAuthProtocolError';\n\n/**\n * The RFC 6749 §5.2 / RFC 6750 §3 error body: `{ \"error\": \"...\", \"error_description\"?: \"...\",\n * \"error_uri\"?: \"...\" }`. The property names ARE the wire names, snake_case, because an OAuth client\n * parses exactly these keys. An absent optional is left off the JSON entirely.\n */\nexport class OAuthErrorBody {\n readonly error: OAuthErrorCode;\n readonly error_description?: string;\n readonly error_uri?: string;\n\n constructor(error: OAuthErrorCode, description?: string, errorUri?: string) {\n this.error = error;\n if (description !== undefined) this.error_description = OAuthText.sanitize(description);\n if (errorUri !== undefined) this.error_uri = errorUri;\n }\n}\n\n/**\n * {@link OAuthProtocolError} -> the exact HTTP response. ONE renderer, used by every boundary that\n * can meet one (`WebpiecesDefaultErrorTranslator`, `OAuthExpressErrorHandler`, the MCP bearer\n * boundary), so an OAuth client gets the same bytes whichever path the refusal took.\n *\n * Headers: the error's `WWW-Authenticate` when it has one; otherwise `defaultChallenge` for a 401\n * (the MCP boundary passes its own challenge, which carries `resource_metadata`); otherwise, for\n * `invalid_token` / `insufficient_scope`, the minimal Bearer challenge RFC 6750 §3 requires. Every\n * response also carries `Cache-Control: no-store` and `Pragma: no-cache` (RFC 6749 §5.1), so no\n * intermediary caches an answer about a credential.\n */\nexport class OAuthErrorResponse {\n private static readonly REASONS: ReadonlyMap<OAuthHttpStatus, string> = new Map<\n OAuthHttpStatus,\n string\n >([\n [400, 'Bad Request'],\n [401, 'Unauthorized'],\n [403, 'Forbidden'],\n [500, 'Internal Server Error'],\n [503, 'Service Unavailable'],\n ]);\n\n /**\n * @param defaultChallenge - the boundary's own `WWW-Authenticate` for a 401 whose error carries\n * none, or `undefined` when the boundary has no challenge of its own.\n */\n // webpieces-disable no-function-outside-class -- pure error -> response mapping shared by every boundary\n static toResponse(\n error: OAuthProtocolError,\n defaultChallenge: string | undefined,\n ): HttpResponseDto<OAuthErrorBody> {\n const headers = [\n new HttpHeader('Cache-Control', 'no-store'),\n new HttpHeader('Pragma', 'no-cache'),\n ];\n const challenge = OAuthErrorResponse.challengeFor(error, defaultChallenge);\n if (challenge !== undefined) {\n headers.push(new HttpHeader('WWW-Authenticate', challenge));\n }\n return new HttpResponseDto(\n new HttpResponseStatus(\n error.status,\n OAuthErrorResponse.REASONS.get(error.status) ?? '',\n ),\n headers,\n new OAuthErrorBody(error.error, error.description, error.errorUri),\n );\n }\n\n // webpieces-disable no-function-outside-class -- pure header choice owned by this renderer\n private static challengeFor(\n error: OAuthProtocolError,\n defaultChallenge: string | undefined,\n ): string | undefined {\n if (error.wwwAuthenticate !== undefined) return error.wwwAuthenticate;\n if (error.status === 401 && defaultChallenge !== undefined) return defaultChallenge;\n if (\n error.error === OAuthErrorCode.INVALID_TOKEN ||\n error.error === OAuthErrorCode.INSUFFICIENT_SCOPE\n ) {\n return new OAuthBearerChallenge(error.error, error.description).toHeaderValue();\n }\n return undefined;\n }\n}\n"]}
@@ -0,0 +1,61 @@
1
+ import { InvalidClientStatus, OAuthErrorCode, OAuthFixedStatusErrorCode, OAuthHttpStatus } from './OAuthErrorCode';
2
+ /**
3
+ * The optional RFC extras of an {@link OAuthProtocolError}. Data only, so a class.
4
+ *
5
+ * - `errorUri` — RFC 6749 `error_uri`: a page a client DEVELOPER can read about the error.
6
+ * - `wwwAuthenticate` — the `WWW-Authenticate` value to send. Build the Bearer form with
7
+ * `OAuthBearerChallenge`. When it is omitted for `invalid_token` / `insufficient_scope`, the
8
+ * renderer derives the minimal `Bearer error="..."` challenge RFC 6750 requires.
9
+ * - `cause` — the underlying failure, for the operator log only; it never reaches the caller.
10
+ */
11
+ export declare class OAuthErrorExtras {
12
+ readonly errorUri?: string | undefined;
13
+ readonly wwwAuthenticate?: string | undefined;
14
+ readonly cause?: Error | undefined;
15
+ constructor(errorUri?: string | undefined, wwwAuthenticate?: string | undefined, cause?: Error | undefined);
16
+ }
17
+ /**
18
+ * A refusal from an app-hosted OAuth endpoint (token, registration, a bearer-protected resource),
19
+ * answered in the RFC shape `{ error, error_description?, error_uri? }` and NOT in the webpieces
20
+ * `ApiError` envelope (#1176).
21
+ *
22
+ * WHY A SEPARATE TYPE: the caller of an OAuth endpoint is an OAuth CLIENT (an MCP client, Claude,
23
+ * any RFC 6749 library), which parses the RFC body and nothing else. And a refused grant is the
24
+ * protocol working as designed, so it is logged as an expected refusal, not as a server ERROR. It is
25
+ * deliberately not an `ApiError`: its wire shape, status rules and audience are all different.
26
+ *
27
+ * THE STATUS IS DERIVED FROM THE CODE ({@link OAuthErrorStatus}); a throw site never types one. The
28
+ * single exception is the single one the RFCs make: `invalid_client` may be 400 or 401 (default 401).
29
+ * That is enforced by the OVERLOADS, not by a runtime check — a status argument only compiles for
30
+ * `invalid_client`, and only 400 or 401 (`OAuthProtocolErrorCompileAssertions` pins both):
31
+ *
32
+ * ```ts
33
+ * throw new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'authorization code expired');
34
+ * throw new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'unknown client', undefined, 400);
35
+ * throw new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'x', undefined, 401); // does not compile
36
+ * ```
37
+ *
38
+ * `description` is the RFC `error_description`: SENT to the caller, and meant for the client
39
+ * developer — never put a secret or an end-user sentence in it. It also forms `message`, which is
40
+ * what the operator log shows.
41
+ *
42
+ * Rendered by `OAuthErrorResponse` — which `WebpiecesDefaultErrorTranslator` (webpieces routes),
43
+ * `OAuthExpressErrorHandler` (raw express routes, @webpieces/http-server) and the MCP server's bearer
44
+ * boundary all use, so every path answers the same bytes.
45
+ */
46
+ export declare class OAuthProtocolError extends Error {
47
+ /** The RFC `error` code. */
48
+ readonly error: OAuthErrorCode;
49
+ /** The HTTP status, derived from `error` (see the class doc). */
50
+ readonly status: OAuthHttpStatus;
51
+ /** The RFC `error_description`, sent to the caller. */
52
+ readonly description?: string;
53
+ /** The RFC `error_uri`, sent to the caller. */
54
+ readonly errorUri?: string;
55
+ /** The `WWW-Authenticate` header value, when the caller must be challenged. */
56
+ readonly wwwAuthenticate?: string;
57
+ constructor(error: OAuthFixedStatusErrorCode, description?: string, extras?: OAuthErrorExtras);
58
+ constructor(error: OAuthErrorCode.INVALID_CLIENT, description?: string, extras?: OAuthErrorExtras, status?: InvalidClientStatus);
59
+ /** True for a refusal of the CALLER (every 4xx); false for `server_error` / `temporarily_unavailable`. */
60
+ isCallerError(): boolean;
61
+ }
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuthProtocolError = exports.OAuthErrorExtras = void 0;
4
+ const OAuthErrorCode_1 = require("./OAuthErrorCode");
5
+ /**
6
+ * The optional RFC extras of an {@link OAuthProtocolError}. Data only, so a class.
7
+ *
8
+ * - `errorUri` — RFC 6749 `error_uri`: a page a client DEVELOPER can read about the error.
9
+ * - `wwwAuthenticate` — the `WWW-Authenticate` value to send. Build the Bearer form with
10
+ * `OAuthBearerChallenge`. When it is omitted for `invalid_token` / `insufficient_scope`, the
11
+ * renderer derives the minimal `Bearer error="..."` challenge RFC 6750 requires.
12
+ * - `cause` — the underlying failure, for the operator log only; it never reaches the caller.
13
+ */
14
+ class OAuthErrorExtras {
15
+ errorUri;
16
+ wwwAuthenticate;
17
+ cause;
18
+ constructor(errorUri, wwwAuthenticate, cause) {
19
+ this.errorUri = errorUri;
20
+ this.wwwAuthenticate = wwwAuthenticate;
21
+ this.cause = cause;
22
+ }
23
+ }
24
+ exports.OAuthErrorExtras = OAuthErrorExtras;
25
+ /**
26
+ * A refusal from an app-hosted OAuth endpoint (token, registration, a bearer-protected resource),
27
+ * answered in the RFC shape `{ error, error_description?, error_uri? }` and NOT in the webpieces
28
+ * `ApiError` envelope (#1176).
29
+ *
30
+ * WHY A SEPARATE TYPE: the caller of an OAuth endpoint is an OAuth CLIENT (an MCP client, Claude,
31
+ * any RFC 6749 library), which parses the RFC body and nothing else. And a refused grant is the
32
+ * protocol working as designed, so it is logged as an expected refusal, not as a server ERROR. It is
33
+ * deliberately not an `ApiError`: its wire shape, status rules and audience are all different.
34
+ *
35
+ * THE STATUS IS DERIVED FROM THE CODE ({@link OAuthErrorStatus}); a throw site never types one. The
36
+ * single exception is the single one the RFCs make: `invalid_client` may be 400 or 401 (default 401).
37
+ * That is enforced by the OVERLOADS, not by a runtime check — a status argument only compiles for
38
+ * `invalid_client`, and only 400 or 401 (`OAuthProtocolErrorCompileAssertions` pins both):
39
+ *
40
+ * ```ts
41
+ * throw new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'authorization code expired');
42
+ * throw new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'unknown client', undefined, 400);
43
+ * throw new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'x', undefined, 401); // does not compile
44
+ * ```
45
+ *
46
+ * `description` is the RFC `error_description`: SENT to the caller, and meant for the client
47
+ * developer — never put a secret or an end-user sentence in it. It also forms `message`, which is
48
+ * what the operator log shows.
49
+ *
50
+ * Rendered by `OAuthErrorResponse` — which `WebpiecesDefaultErrorTranslator` (webpieces routes),
51
+ * `OAuthExpressErrorHandler` (raw express routes, @webpieces/http-server) and the MCP server's bearer
52
+ * boundary all use, so every path answers the same bytes.
53
+ */
54
+ class OAuthProtocolError extends Error {
55
+ /** The RFC `error` code. */
56
+ error;
57
+ /** The HTTP status, derived from `error` (see the class doc). */
58
+ status;
59
+ /** The RFC `error_description`, sent to the caller. */
60
+ description;
61
+ /** The RFC `error_uri`, sent to the caller. */
62
+ errorUri;
63
+ /** The `WWW-Authenticate` header value, when the caller must be challenged. */
64
+ wwwAuthenticate;
65
+ constructor(error, description, extras = new OAuthErrorExtras(), status) {
66
+ super(description === undefined ? `OAuth ${error}` : `OAuth ${error}: ${description}`, {
67
+ cause: extras.cause,
68
+ });
69
+ this.name = 'OAuthProtocolError';
70
+ this.error = error;
71
+ this.status =
72
+ error === OAuthErrorCode_1.OAuthErrorCode.INVALID_CLIENT ? (status ?? 401) : OAuthErrorCode_1.OAuthErrorStatus.of(error);
73
+ this.description = description;
74
+ this.errorUri = extras.errorUri;
75
+ this.wwwAuthenticate = extras.wwwAuthenticate;
76
+ }
77
+ /** True for a refusal of the CALLER (every 4xx); false for `server_error` / `temporarily_unavailable`. */
78
+ isCallerError() {
79
+ return this.status < 500;
80
+ }
81
+ }
82
+ exports.OAuthProtocolError = OAuthProtocolError;
83
+ //# sourceMappingURL=OAuthProtocolError.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthProtocolError.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/oauth/OAuthProtocolError.ts"],"names":[],"mappings":";;;AAAA,qDAM0B;AAE1B;;;;;;;;GAQG;AACH,MAAa,gBAAgB;IAEL;IACA;IACA;IAHpB,YACoB,QAAiB,EACjB,eAAwB,EACxB,KAAa;QAFb,aAAQ,GAAR,QAAQ,CAAS;QACjB,oBAAe,GAAf,eAAe,CAAS;QACxB,UAAK,GAAL,KAAK,CAAQ;IAC9B,CAAC;CACP;AAND,4CAMC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAa,kBAAmB,SAAQ,KAAK;IACzC,4BAA4B;IACnB,KAAK,CAAiB;IAC/B,iEAAiE;IACxD,MAAM,CAAkB;IACjC,uDAAuD;IAC9C,WAAW,CAAU;IAC9B,+CAA+C;IACtC,QAAQ,CAAU;IAC3B,+EAA+E;IACtE,eAAe,CAAU;IASlC,YACI,KAAqB,EACrB,WAAoB,EACpB,SAA2B,IAAI,gBAAgB,EAAE,EACjD,MAA4B;QAE5B,KAAK,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,KAAK,WAAW,EAAE,EAAE;YACnF,KAAK,EAAE,MAAM,CAAC,KAAK;SACtB,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM;YACP,KAAK,KAAK,+BAAc,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,iCAAgB,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;QAC3F,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,eAAe,GAAG,MAAM,CAAC,eAAe,CAAC;IAClD,CAAC;IAED,0GAA0G;IAC1G,aAAa;QACT,OAAO,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC;IAC7B,CAAC;CACJ;AAzCD,gDAyCC","sourcesContent":["import {\n InvalidClientStatus,\n OAuthErrorCode,\n OAuthErrorStatus,\n OAuthFixedStatusErrorCode,\n OAuthHttpStatus,\n} from './OAuthErrorCode';\n\n/**\n * The optional RFC extras of an {@link OAuthProtocolError}. Data only, so a class.\n *\n * - `errorUri` — RFC 6749 `error_uri`: a page a client DEVELOPER can read about the error.\n * - `wwwAuthenticate` — the `WWW-Authenticate` value to send. Build the Bearer form with\n * `OAuthBearerChallenge`. When it is omitted for `invalid_token` / `insufficient_scope`, the\n * renderer derives the minimal `Bearer error=\"...\"` challenge RFC 6750 requires.\n * - `cause` — the underlying failure, for the operator log only; it never reaches the caller.\n */\nexport class OAuthErrorExtras {\n constructor(\n public readonly errorUri?: string,\n public readonly wwwAuthenticate?: string,\n public readonly cause?: Error,\n ) {}\n}\n\n/**\n * A refusal from an app-hosted OAuth endpoint (token, registration, a bearer-protected resource),\n * answered in the RFC shape `{ error, error_description?, error_uri? }` and NOT in the webpieces\n * `ApiError` envelope (#1176).\n *\n * WHY A SEPARATE TYPE: the caller of an OAuth endpoint is an OAuth CLIENT (an MCP client, Claude,\n * any RFC 6749 library), which parses the RFC body and nothing else. And a refused grant is the\n * protocol working as designed, so it is logged as an expected refusal, not as a server ERROR. It is\n * deliberately not an `ApiError`: its wire shape, status rules and audience are all different.\n *\n * THE STATUS IS DERIVED FROM THE CODE ({@link OAuthErrorStatus}); a throw site never types one. The\n * single exception is the single one the RFCs make: `invalid_client` may be 400 or 401 (default 401).\n * That is enforced by the OVERLOADS, not by a runtime check — a status argument only compiles for\n * `invalid_client`, and only 400 or 401 (`OAuthProtocolErrorCompileAssertions` pins both):\n *\n * ```ts\n * throw new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'authorization code expired');\n * throw new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'unknown client', undefined, 400);\n * throw new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'x', undefined, 401); // does not compile\n * ```\n *\n * `description` is the RFC `error_description`: SENT to the caller, and meant for the client\n * developer — never put a secret or an end-user sentence in it. It also forms `message`, which is\n * what the operator log shows.\n *\n * Rendered by `OAuthErrorResponse` — which `WebpiecesDefaultErrorTranslator` (webpieces routes),\n * `OAuthExpressErrorHandler` (raw express routes, @webpieces/http-server) and the MCP server's bearer\n * boundary all use, so every path answers the same bytes.\n */\nexport class OAuthProtocolError extends Error {\n /** The RFC `error` code. */\n readonly error: OAuthErrorCode;\n /** The HTTP status, derived from `error` (see the class doc). */\n readonly status: OAuthHttpStatus;\n /** The RFC `error_description`, sent to the caller. */\n readonly description?: string;\n /** The RFC `error_uri`, sent to the caller. */\n readonly errorUri?: string;\n /** The `WWW-Authenticate` header value, when the caller must be challenged. */\n readonly wwwAuthenticate?: string;\n\n constructor(error: OAuthFixedStatusErrorCode, description?: string, extras?: OAuthErrorExtras);\n constructor(\n error: OAuthErrorCode.INVALID_CLIENT,\n description?: string,\n extras?: OAuthErrorExtras,\n status?: InvalidClientStatus,\n );\n constructor(\n error: OAuthErrorCode,\n description?: string,\n extras: OAuthErrorExtras = new OAuthErrorExtras(),\n status?: InvalidClientStatus,\n ) {\n super(description === undefined ? `OAuth ${error}` : `OAuth ${error}: ${description}`, {\n cause: extras.cause,\n });\n this.name = 'OAuthProtocolError';\n this.error = error;\n this.status =\n error === OAuthErrorCode.INVALID_CLIENT ? (status ?? 401) : OAuthErrorStatus.of(error);\n this.description = description;\n this.errorUri = extras.errorUri;\n this.wwwAuthenticate = extras.wwwAuthenticate;\n }\n\n /** True for a refusal of the CALLER (every 4xx); false for `server_error` / `temporarily_unavailable`. */\n isCallerError(): boolean {\n return this.status < 500;\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * COMPILE-TIME assertions for {@link OAuthProtocolError}'s status rule: only `invalid_client` takes
3
+ * a status, and only 400 or 401. Each `@ts-expect-error` fails the build (TS2578) if its line ever
4
+ * starts compiling. A non-spec file on purpose: vitest strips types and tsconfig.lib.json excludes
5
+ * specs, so only a compiled file makes these assertions real.
6
+ */
7
+ export declare class OAuthProtocolErrorCompileAssertions {
8
+ legitimate(): void;
9
+ illegal(): void;
10
+ }
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OAuthProtocolErrorCompileAssertions = void 0;
4
+ const OAuthAuthorizationErrorRedirect_1 = require("./OAuthAuthorizationErrorRedirect");
5
+ const OAuthBearerChallenge_1 = require("./OAuthBearerChallenge");
6
+ const OAuthErrorCode_1 = require("./OAuthErrorCode");
7
+ const OAuthProtocolError_1 = require("./OAuthProtocolError");
8
+ /**
9
+ * COMPILE-TIME assertions for {@link OAuthProtocolError}'s status rule: only `invalid_client` takes
10
+ * a status, and only 400 or 401. Each `@ts-expect-error` fails the build (TS2578) if its line ever
11
+ * starts compiling. A non-spec file on purpose: vitest strips types and tsconfig.lib.json excludes
12
+ * specs, so only a compiled file makes these assertions real.
13
+ */
14
+ class OAuthProtocolErrorCompileAssertions {
15
+ legitimate() {
16
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_GRANT);
17
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_GRANT, 'code expired');
18
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_TOKEN, 'expired', new OAuthProtocolError_1.OAuthErrorExtras());
19
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_CLIENT, 'unknown client');
20
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_CLIENT, 'x', undefined, 400);
21
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_CLIENT, 'x', undefined, 401);
22
+ void new OAuthBearerChallenge_1.OAuthBearerChallenge(OAuthErrorCode_1.OAuthErrorCode.INSUFFICIENT_SCOPE, 'x', 'tools:write');
23
+ void new OAuthAuthorizationErrorRedirect_1.OAuthAuthorizationErrorRedirect('https://c/cb', OAuthErrorCode_1.OAuthErrorCode.ACCESS_DENIED, 's');
24
+ }
25
+ illegal() {
26
+ // @ts-expect-error -- the RFCs fix invalid_grant's status; a status argument does not compile
27
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_GRANT, 'x', undefined, 401);
28
+ // @ts-expect-error -- invalid_client may only be 400 or 401
29
+ void new OAuthProtocolError_1.OAuthProtocolError(OAuthErrorCode_1.OAuthErrorCode.INVALID_CLIENT, 'x', undefined, 403);
30
+ // @ts-expect-error -- a code outside the RFC vocabulary does not compile
31
+ void new OAuthProtocolError_1.OAuthProtocolError('invalid_credential', 'x');
32
+ // @ts-expect-error -- invalid_grant is a token-endpoint code, not a Bearer challenge code
33
+ void new OAuthBearerChallenge_1.OAuthBearerChallenge(OAuthErrorCode_1.OAuthErrorCode.INVALID_GRANT);
34
+ // @ts-expect-error -- invalid_token is not an authorization-endpoint redirect code
35
+ void new OAuthAuthorizationErrorRedirect_1.OAuthAuthorizationErrorRedirect('https://c/cb', OAuthErrorCode_1.OAuthErrorCode.INVALID_TOKEN, 's');
36
+ }
37
+ }
38
+ exports.OAuthProtocolErrorCompileAssertions = OAuthProtocolErrorCompileAssertions;
39
+ //# sourceMappingURL=OAuthProtocolErrorCompileAssertions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OAuthProtocolErrorCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/oauth/OAuthProtocolErrorCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,uFAAoF;AACpF,iEAA8D;AAC9D,qDAAkD;AAClD,6DAA4E;AAE5E;;;;;GAKG;AACH,MAAa,mCAAmC;IAC5C,UAAU;QACN,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,aAAa,CAAC,CAAC;QAC1D,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC;QAC1E,KAAK,IAAI,uCAAkB,CACvB,+BAAc,CAAC,aAAa,EAC5B,SAAS,EACT,IAAI,qCAAgB,EAAE,CACzB,CAAC;QACF,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QAC7E,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAChF,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAChF,KAAK,IAAI,2CAAoB,CAAC,+BAAc,CAAC,kBAAkB,EAAE,GAAG,EAAE,aAAa,CAAC,CAAC;QACrF,KAAK,IAAI,iEAA+B,CAAC,cAAc,EAAE,+BAAc,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAChG,CAAC;IAED,OAAO;QACH,8FAA8F;QAC9F,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAC/E,4DAA4D;QAC5D,KAAK,IAAI,uCAAkB,CAAC,+BAAc,CAAC,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAChF,yEAAyE;QACzE,KAAK,IAAI,uCAAkB,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;QACvD,0FAA0F;QAC1F,KAAK,IAAI,2CAAoB,CAAC,+BAAc,CAAC,aAAa,CAAC,CAAC;QAC5D,mFAAmF;QACnF,KAAK,IAAI,iEAA+B,CAAC,cAAc,EAAE,+BAAc,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;IAChG,CAAC;CACJ;AA5BD,kFA4BC","sourcesContent":["import { OAuthAuthorizationErrorRedirect } from './OAuthAuthorizationErrorRedirect';\nimport { OAuthBearerChallenge } from './OAuthBearerChallenge';\nimport { OAuthErrorCode } from './OAuthErrorCode';\nimport { OAuthErrorExtras, OAuthProtocolError } from './OAuthProtocolError';\n\n/**\n * COMPILE-TIME assertions for {@link OAuthProtocolError}'s status rule: only `invalid_client` takes\n * a status, and only 400 or 401. Each `@ts-expect-error` fails the build (TS2578) if its line ever\n * starts compiling. A non-spec file on purpose: vitest strips types and tsconfig.lib.json excludes\n * specs, so only a compiled file makes these assertions real.\n */\nexport class OAuthProtocolErrorCompileAssertions {\n legitimate(): void {\n void new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT);\n void new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'code expired');\n void new OAuthProtocolError(\n OAuthErrorCode.INVALID_TOKEN,\n 'expired',\n new OAuthErrorExtras(),\n );\n void new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'unknown client');\n void new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'x', undefined, 400);\n void new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'x', undefined, 401);\n void new OAuthBearerChallenge(OAuthErrorCode.INSUFFICIENT_SCOPE, 'x', 'tools:write');\n void new OAuthAuthorizationErrorRedirect('https://c/cb', OAuthErrorCode.ACCESS_DENIED, 's');\n }\n\n illegal(): void {\n // @ts-expect-error -- the RFCs fix invalid_grant's status; a status argument does not compile\n void new OAuthProtocolError(OAuthErrorCode.INVALID_GRANT, 'x', undefined, 401);\n // @ts-expect-error -- invalid_client may only be 400 or 401\n void new OAuthProtocolError(OAuthErrorCode.INVALID_CLIENT, 'x', undefined, 403);\n // @ts-expect-error -- a code outside the RFC vocabulary does not compile\n void new OAuthProtocolError('invalid_credential', 'x');\n // @ts-expect-error -- invalid_grant is a token-endpoint code, not a Bearer challenge code\n void new OAuthBearerChallenge(OAuthErrorCode.INVALID_GRANT);\n // @ts-expect-error -- invalid_token is not an authorization-endpoint redirect code\n void new OAuthAuthorizationErrorRedirect('https://c/cb', OAuthErrorCode.INVALID_TOKEN, 's');\n }\n}\n"]}