@webpieces/core-util 0.4.760 → 0.4.761
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/package.json +1 -1
- package/src/ContextKey.d.ts +3 -3
- package/src/ContextKey.js +3 -3
- package/src/ContextKey.js.map +1 -1
- package/src/http/ContextMgr.d.ts +2 -2
- package/src/http/ContextMgr.js +2 -2
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/DestinationTrust.d.ts +5 -5
- package/src/http/DestinationTrust.js +8 -8
- package/src/http/DestinationTrust.js.map +1 -1
- package/src/http/RouteMetadata.d.ts +1 -1
- package/src/http/RouteMetadata.js +1 -1
- package/src/http/RouteMetadata.js.map +1 -1
- package/src/http/RuntimeLocality.d.ts +3 -3
- package/src/http/RuntimeLocality.js +1 -1
- package/src/http/RuntimeLocality.js.map +1 -1
- package/src/http/Secrets.d.ts +2 -2
- package/src/http/Secrets.js +2 -2
- package/src/http/Secrets.js.map +1 -1
- package/src/http/WebpiecesCoreHeaders.d.ts +2 -2
- package/src/http/WebpiecesCoreHeaders.js +22 -7
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/http/{AuthApiKeyCompileAssertions.d.ts → WpAuthApiKeyCompileAssertions.d.ts} +4 -4
- package/src/http/{AuthApiKeyCompileAssertions.js → WpAuthApiKeyCompileAssertions.js} +25 -11
- package/src/http/WpAuthApiKeyCompileAssertions.js.map +1 -0
- package/src/http/{AuthJwtCompileAssertions.d.ts → WpAuthJwtCompileAssertions.d.ts} +2 -0
- package/src/http/{AuthJwtCompileAssertions.js → WpAuthJwtCompileAssertions.js} +19 -12
- package/src/http/WpAuthJwtCompileAssertions.js.map +1 -0
- package/src/http/auth-mode.d.ts +8 -8
- package/src/http/auth-mode.js +1 -1
- package/src/http/auth-mode.js.map +1 -1
- package/src/http/decorators.d.ts +47 -44
- package/src/http/decorators.js +95 -81
- package/src/http/decorators.js.map +1 -1
- package/src/index.d.ts +3 -1
- package/src/index.js +23 -12
- package/src/index.js.map +1 -1
- package/src/ipc/IpcDecorators.d.ts +23 -0
- package/src/ipc/IpcDecorators.js +104 -0
- package/src/ipc/IpcDecorators.js.map +1 -0
- package/src/ipc/IpcIdentity.d.ts +3 -0
- package/src/ipc/IpcIdentity.js +14 -0
- package/src/ipc/IpcIdentity.js.map +1 -0
- package/src/ipc/IpcProtocol.js +6 -6
- package/src/ipc/IpcProtocol.js.map +1 -1
- package/src/ipc/index.d.ts +4 -2
- package/src/ipc/index.js +16 -5
- package/src/ipc/index.js.map +1 -1
- package/src/http/AuthApiKeyCompileAssertions.js.map +0 -1
- package/src/http/AuthJwtCompileAssertions.js.map +0 -1
- package/src/ipc/IpcCompileAssertions.d.ts +0 -4
- package/src/ipc/IpcCompileAssertions.js +0 -39
- package/src/ipc/IpcCompileAssertions.js.map +0 -1
- package/src/ipc/IpcContract.d.ts +0 -32
- package/src/ipc/IpcContract.js +0 -65
- package/src/ipc/IpcContract.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { ApiCallInfo } from './ApiCallInfo';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n */\n static readonly REQUEST_ID = ContextKey.untrusted<string>('requestId', 'x-request-id');\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = ContextKey.untrusted<string>(\n 'requestIdSource',\n /*httpHeader*/ undefined\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = ContextKey.untrusted<string>('clientVersion', 'x-webpieces-client-version');\n\n /**\n * A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.\n *\n * An \"action\" is a single thing the user did in the GUI — a CLICK on a button/link, or TYPING in a\n * field — or a background poller tick: anything that may fan out into MULTIPLE remote calls. That one\n * action fires 1..N browser HTTP calls, each of which gets its own framework-minted {@link REQUEST_ID}\n * (one per HTTP call, shared within that call's server→server subtree). `actionId` sits ABOVE\n * `requestId` and is what stitches those N requests back to the single action that caused them:\n *\n * actionId (app-minted, ONE per user action, rides EVERY call of that action)\n * └── 1..N requestId (framework-minted, ONE per HTTP call)\n *\n * Grep one `actionId` in the logs → every `requestId` it spawned, and every log line of the whole\n * action. Minted and refreshed by the app (a UI concern), carried under `x-webpieces-actionid`.\n *\n * Browser/app-minted ONLY: unlike {@link REQUEST_ID}, the framework transfers and logs it but must\n * NOT auto-mint one server-side. Absent `actionId` ⇒ a non-action flow (system / cron / task), which\n * is the correct signal.\n *\n * - `httpHeader` SET → transferred: copied off the inbound request into context and re-emitted on\n * outbound hops, so the id follows the action across services.\n * - `isLogged` TRUE → emitted as a plain string on every log line of the request.\n */\n static readonly ACTION_ID = ContextKey.untrusted<string>('actionId', 'x-webpieces-actionid');\n\n /**\n * WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the\n * inputs to authorization decisions, so a reader must be able to tell \"the framework proved\n * this\" from \"the caller typed this\" — see the trust section of the {@link ContextKey} doc.\n *\n * They keep their `httpHeader`, because propagating a verified identity to the next internal\n * service is the point. What makes that safe is not the header being absent, it is WHO is\n * allowed to have set it: an inbound value is held PENDING by\n * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that\n * verified its CALLER (`@AuthOidc` / `@AuthSharedSecret`). On a browser-reachable route\n * (`@AuthJwt` / public) the value must match what the authenticator itself derived, or the\n * request is rejected.\n *\n * `provenance` says \"an app-bound JwtHook\" rather than naming one hook, because the framework\n * default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that\n * returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's\n * hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud\n * outcome: nothing is vouching for it.\n */\n static readonly ORG_ID = ContextKey.trusted<string>(\n 'orgId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-org-id',\n );\n\n static readonly USER_ID = ContextKey.trusted<string>(\n 'userId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-user-id',\n );\n\n static readonly USER_ROLES = ContextKey.trusted<string>(\n 'roles',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-webpieces-roles',\n );\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = ContextKey.untrusted<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCallImpl} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = ContextKey.untrusted<ApiCallInfo>('api', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = ContextKey.untrusted<string>('httpMethod', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);\n\n static readonly REQUEST_PATH = ContextKey.untrusted<string>('requestPath', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);\n\n /**\n * The routed endpoint's IMPLEMENTATION identity: the concrete controller class name\n * ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME\n * ({@link RouteMetadata.methodName}, e.g. `login`), stamped once per request by {@link LogApiFilter}\n * after route matching so every subsequent log line of the request carries them.\n *\n * These say WHICH CODE ran, which is what you actually grep for — far more useful than the raw\n * `requestPath`. They are the top-level, filterable twin of what previously only lived nested in\n * {@link ApiCallInfo} (`api.method.controllerName` / `api.method.methodName`). The local console\n * formatters render them together as a compact `[Controller.method]` bracket; GCP keeps them as two\n * separate `jsonPayload.controller` / `jsonPayload.method` fields.\n *\n * NOTE: `method` here is the CODE method name (e.g. `login`), NOT the HTTP verb — that is\n * {@link HTTP_METHOD} (`httpMethod`).\n *\n * - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly CONTROLLER = ContextKey.untrusted<string>('controller', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);\n\n static readonly METHOD = ContextKey.untrusted<string>('method', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);\n\n /**\n * The base URL ONE outbound call should go to, overriding whatever the client's `ClientConfig`\n * bound at construction — the answer to \"POST our published contract to a URL the PARTNER\n * registered at runtime\" (an `OrganizationWebhook.url` column, an OAuth callback, a per-tenant\n * or self-hosted host). The destination is DATA, not deployment, so it cannot be a svcName and\n * there is nothing to register in {@link ClientRegistry}.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * ONLY a client carrying a `ContextBaseUrlFilter` reads it. Every other client IGNORES this key\n * entirely, which is what stops an ambient value re-pointing every other client in the same\n * fan-out loop at a partner's server. Installing that ONE filter IS the opt-in, so\n * `grep -rn ContextBaseUrlFilter` enumerates every client that can be re-pointed at all.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is load-bearing. This value\n * names where THIS hop goes. If it travelled, the callee would inherit it and re-point ITS\n * own outbound calls at the same host — one partner-supplied URL turning into an SSRF pivot\n * across the whole call tree. It is per-hop, always.\n * - `isLogged` TRUE → the destination of a partner delivery is exactly what you want in the log\n * line when one fails.\n *\n * UNTRUSTED, necessarily: it comes from a database column a partner edited. That is precisely\n * why re-pointing a request ARMS the framework's SSRF guard, automatically, rather than trusting\n * it — the guard sits beneath every app filter and reads the fact that the request was moved.\n */\n static readonly OVERRIDE_BASE_URL = ContextKey.untrusted<string>(\n 'overrideBaseUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * All core context keys (the platform DEFAULT_HEADERS set). A `static readonly` CONSTANT, not a\n * method: it is compile-time data — a list of the key definitions above — read once at the startup\n * composition root (`HeaderRegistry.configure` / `HeaderRegistry.DEFAULT_HEADERS`). A method here\n * would be un-injectable behavior the DI design graph can't reach; a constant is honest data.\n */\n static readonly ALL_HEADERS: AnyContextKey[] = [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.ACTION_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n WebpiecesCoreHeaders.CONTROLLER,\n WebpiecesCoreHeaders.METHOD,\n WebpiecesCoreHeaders.OVERRIDE_BASE_URL,\n ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { ApiCallInfo } from './ApiCallInfo';\n\n/**\n * Core framework context keys — the minimum the WebPieces framework needs to correlate one\n * request across every service it touches, and across every log line each of them writes.\n *\n * ONE id, propagated unchanged. The first service to see a request without an `x-request-id`\n * generates one (RequestContextHeaders.fillFromRequest); every hop copies it onward verbatim. Grep that id and you\n * have the whole call tree. There is no per-hop id and no parent pointer: a chain of ids you must\n * stitch back together buys nothing a single shared id does not already give you.\n *\n * Lives in core-util (browser-safe) so both the http clients and http-server can reference it.\n *\n * Exposed as {@link HeaderRegistry.DEFAULT_HEADERS} — a service opts into these by\n * passing `platformHeaders=true` to `HeaderRegistry.configure(...)`.\n *\n * Each key's `name` is the logical/log name; `httpHeader` is the wire name.\n */\nexport class WebpiecesCoreHeaders {\n /**\n * The id that correlates every hop of one request, and every log line of every hop.\n * Generated by the first service to see a request without one; propagated unchanged after that.\n */\n static readonly REQUEST_ID = ContextKey.untrusted<string>('requestId', 'x-request-id');\n\n /**\n * WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by\n * `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when\n * the inbound request carried no `x-request-id`). It answers the question the id alone cannot:\n * \"this trace starts here — is that right?\" An id appearing with no source means it came from\n * outside; an id sourced by a service that should never be an entry point is a routing bug.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is the WHOLE POINT. If it\n * travelled, hop 2 would inherit it, hop 3 would inherit it, and \"who started this trace\"\n * would be indistinguishable from \"who passed it along\" — the origin, the one fact this key\n * carries, would be lost. It is absent on every hop that did NOT mint the id, which is exactly\n * the signal: present == I am the origin.\n * - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.\n */\n static readonly REQUEST_ID_SOURCE = ContextKey.untrusted<string>(\n 'requestIdSource',\n /*httpHeader*/ undefined,\n );\n\n /**\n * The CALLER's build version — so a downstream server's logs record which build of the client\n * called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`\n * (this service's build): `version` answers \"which build wrote this line?\", `clientVersion`\n * answers \"which build asked us to?\".\n *\n * - `httpHeader` SET → transferred over the wire, BUT unlike a normal transferred key it is NOT\n * copied from the context onward. Each hop OVERWRITES it with its OWN `ServiceInfo.getVersion()`\n * as it becomes the client to the next hop (see `buildOutboundHeaders`), so on any given server\n * `clientVersion` is always the IMMEDIATE caller's version, never a stale grand-caller's.\n * - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log\n * field map; no backend change needed.\n */\n static readonly CLIENT_VERSION = ContextKey.untrusted<string>(\n 'clientVersion',\n 'x-webpieces-client-version',\n );\n\n /**\n * A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.\n *\n * An \"action\" is a single thing the user did in the GUI — a CLICK on a button/link, or TYPING in a\n * field — or a background poller tick: anything that may fan out into MULTIPLE remote calls. That one\n * action fires 1..N browser HTTP calls, each of which gets its own framework-minted {@link REQUEST_ID}\n * (one per HTTP call, shared within that call's server→server subtree). `actionId` sits ABOVE\n * `requestId` and is what stitches those N requests back to the single action that caused them:\n *\n * actionId (app-minted, ONE per user action, rides EVERY call of that action)\n * └── 1..N requestId (framework-minted, ONE per HTTP call)\n *\n * Grep one `actionId` in the logs → every `requestId` it spawned, and every log line of the whole\n * action. Minted and refreshed by the app (a UI concern), carried under `x-webpieces-actionid`.\n *\n * Browser/app-minted ONLY: unlike {@link REQUEST_ID}, the framework transfers and logs it but must\n * NOT auto-mint one server-side. Absent `actionId` ⇒ a non-action flow (system / cron / task), which\n * is the correct signal.\n *\n * - `httpHeader` SET → transferred: copied off the inbound request into context and re-emitted on\n * outbound hops, so the id follows the action across services.\n * - `isLogged` TRUE → emitted as a plain string on every log line of the request.\n */\n static readonly ACTION_ID = ContextKey.untrusted<string>('actionId', 'x-webpieces-actionid');\n\n /**\n * WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the\n * inputs to authorization decisions, so a reader must be able to tell \"the framework proved\n * this\" from \"the caller typed this\" — see the trust section of the {@link ContextKey} doc.\n *\n * They keep their `httpHeader`, because propagating a verified identity to the next internal\n * service is the point. What makes that safe is not the header being absent, it is WHO is\n * allowed to have set it: an inbound value is held PENDING by\n * `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that\n * verified its CALLER (`@WpAuthOidc` / `@WpAuthSharedSecret`). On a browser-reachable route\n * (`@WpAuthJwt` / public) the value must match what the authenticator itself derived, or the\n * request is rejected.\n *\n * `provenance` says \"an app-bound JwtHook\" rather than naming one hook, because the framework\n * default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that\n * returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's\n * hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud\n * outcome: nothing is vouching for it.\n */\n static readonly ORG_ID = ContextKey.trusted<string>(\n 'orgId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-org-id',\n );\n\n static readonly USER_ID = ContextKey.trusted<string>(\n 'userId',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-user-id',\n );\n\n static readonly USER_ROLES = ContextKey.trusted<string>(\n 'roles',\n 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthenticatedCaller)',\n 'x-webpieces-roles',\n );\n\n /**\n * Turns on test-case recording for this request (Java: x-webpieces-recording).\n * Transferred so recording follows the request across service hops.\n */\n static readonly RECORDING = ContextKey.untrusted<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCallImpl} around every\n * outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted\n * during the call inherits a filterable `api` object, surfacing in GCP as nested\n * `jsonPayload.api.{side,type,result,path,method}`.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire. Per-hop only: each server/client hop\n * stamps its own tag, so a downstream server records `side:'server'`, never the caller's `side:'client'`.\n * - `isLogged` TRUE → emitted by the logging backends. It carries an OBJECT value, so the backends\n * read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat\n * `buildLogFields()` string map deliberately skips it (typeof-string guard).\n */\n static readonly API_CALL_INFO = ContextKey.untrusted<ApiCallInfo>(\n 'api',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by\n * `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels\n * through). They surface as top-level `jsonPayload.httpMethod` / `jsonPayload.requestPath` so every\n * log line of the request carries them — they used to ride inside {@link ApiCallInfo} (`api.path` /\n * `api.method`) but that coupled a per-CALL logger to the per-REQUEST transport shape.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire: a downstream hop stamps its OWN inbound\n * method/path, never the caller's. Outbound client calls never set these (no inbound path).\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly HTTP_METHOD = ContextKey.untrusted<string>(\n 'httpMethod',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly REQUEST_PATH = ContextKey.untrusted<string>(\n 'requestPath',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The routed endpoint's IMPLEMENTATION identity: the concrete controller class name\n * ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME\n * ({@link RouteMetadata.methodName}, e.g. `login`), stamped once per request by {@link LogApiFilter}\n * after route matching so every subsequent log line of the request carries them.\n *\n * These say WHICH CODE ran, which is what you actually grep for — far more useful than the raw\n * `requestPath`. They are the top-level, filterable twin of what previously only lived nested in\n * {@link ApiCallInfo} (`api.method.controllerName` / `api.method.methodName`). The local console\n * formatters render them together as a compact `[Controller.method]` bracket; GCP keeps them as two\n * separate `jsonPayload.controller` / `jsonPayload.method` fields.\n *\n * NOTE: `method` here is the CODE method name (e.g. `login`), NOT the HTTP verb — that is\n * {@link HTTP_METHOD} (`httpMethod`).\n *\n * - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.\n * - `isLogged` TRUE → emitted by the logging backends as plain strings.\n */\n static readonly CONTROLLER = ContextKey.untrusted<string>(\n 'controller',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n static readonly METHOD = ContextKey.untrusted<string>(\n 'method',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * The base URL ONE outbound call should go to, overriding whatever the client's `ClientConfig`\n * bound at construction — the answer to \"POST our published contract to a URL the PARTNER\n * registered at runtime\" (an `OrganizationWebhook.url` column, an OAuth callback, a per-tenant\n * or self-hosted host). The destination is DATA, not deployment, so it cannot be a svcName and\n * there is nothing to register in {@link ClientRegistry}.\n *\n * ```ts\n * RequestContext.run(() => {\n * RequestContext.putUntrusted(WebpiecesCoreHeaders.OVERRIDE_BASE_URL, webhook.url);\n * return partnerWebhookClient.deliver(envelope);\n * });\n * ```\n *\n * ONLY a client carrying a `ContextBaseUrlFilter` reads it. Every other client IGNORES this key\n * entirely, which is what stops an ambient value re-pointing every other client in the same\n * fan-out loop at a partner's server. Installing that ONE filter IS the opt-in, so\n * `grep -rn ContextBaseUrlFilter` enumerates every client that can be re-pointed at all.\n *\n * - `httpHeader` UNDEFINED → NOT transferred over the wire, and that is load-bearing. This value\n * names where THIS hop goes. If it travelled, the callee would inherit it and re-point ITS\n * own outbound calls at the same host — one partner-supplied URL turning into an SSRF pivot\n * across the whole call tree. It is per-hop, always.\n * - `isLogged` TRUE → the destination of a partner delivery is exactly what you want in the log\n * line when one fails.\n *\n * UNTRUSTED, necessarily: it comes from a database column a partner edited. That is precisely\n * why re-pointing a request ARMS the framework's SSRF guard, automatically, rather than trusting\n * it — the guard sits beneath every app filter and reads the fact that the request was moved.\n */\n static readonly OVERRIDE_BASE_URL = ContextKey.untrusted<string>(\n 'overrideBaseUrl',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ true,\n );\n\n /**\n * NO CREDENTIAL KEYS LIVE HERE.\n *\n * `authorization` and `x-webpieces-shared-secret` used to be ContextKeys. That made them\n * TRANSFERRED keys, so the inbound transfer copied them off the request into the\n * RequestContext, and every outbound RPC call and enqueued Cloud Task then carried the\n * caller's credential onward — to services that had no business seeing it.\n *\n * A credential belongs to ONE request hop. It is read straight off the {@link HttpRequest}\n * by the framework AuthFilter, and written straight onto the outbound request by the client\n * that mints it (NodeProxyClient, GcpTaskInvoker, InMemoryTaskInvoker). It never enters the\n * magic context, so nothing can propagate it by accident.\n *\n * An app that genuinely wants a credential to travel can still register its own ContextKey for\n * it — but that is now an explicit, visible decision rather than the default.\n */\n\n /**\n * All core context keys (the platform DEFAULT_HEADERS set). A `static readonly` CONSTANT, not a\n * method: it is compile-time data — a list of the key definitions above — read once at the startup\n * composition root (`HeaderRegistry.configure` / `HeaderRegistry.DEFAULT_HEADERS`). A method here\n * would be un-injectable behavior the DI design graph can't reach; a constant is honest data.\n */\n static readonly ALL_HEADERS: AnyContextKey[] = [\n WebpiecesCoreHeaders.REQUEST_ID,\n WebpiecesCoreHeaders.REQUEST_ID_SOURCE,\n WebpiecesCoreHeaders.CLIENT_VERSION,\n WebpiecesCoreHeaders.ACTION_ID,\n WebpiecesCoreHeaders.USER_ID,\n WebpiecesCoreHeaders.ORG_ID,\n WebpiecesCoreHeaders.USER_ROLES,\n WebpiecesCoreHeaders.RECORDING,\n WebpiecesCoreHeaders.API_CALL_INFO,\n WebpiecesCoreHeaders.HTTP_METHOD,\n WebpiecesCoreHeaders.REQUEST_PATH,\n WebpiecesCoreHeaders.CONTROLLER,\n WebpiecesCoreHeaders.METHOD,\n WebpiecesCoreHeaders.OVERRIDE_BASE_URL,\n ];\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ApiKeyCredential, AuthMode } from './auth-mode';
|
|
2
2
|
/**
|
|
3
3
|
* COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the
|
|
4
|
-
* {@link
|
|
4
|
+
* {@link WpAuthApiKey} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, "unused
|
|
5
5
|
* '@ts-expect-error' directive") if the line it guards ever starts compiling.
|
|
6
6
|
*
|
|
7
7
|
* WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:
|
|
@@ -20,7 +20,7 @@ import { ApiKeyCredential, AuthMode } from './auth-mode';
|
|
|
20
20
|
* 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential
|
|
21
21
|
* must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,
|
|
22
22
|
* so a `name` beside it would be a lie a generator has to guess about.
|
|
23
|
-
* 4. The ONE-ARGUMENT `@
|
|
23
|
+
* 4. The ONE-ARGUMENT `@WpAuthApiKey('regime')` form is GONE. It could declare a key regime while
|
|
24
24
|
* saying nothing about where the credential rides, which is the whole defect; per the
|
|
25
25
|
* no-backwards-compatibility rule there is no overload and no optional second parameter left
|
|
26
26
|
* behind, and this directive is what proves it.
|
|
@@ -36,11 +36,11 @@ export declare class AuthApiKeyCompileAssertions {
|
|
|
36
36
|
/** The bearer branch, likewise asserted by the ABSENCE of an error. */
|
|
37
37
|
legitimateBearer(): ApiKeyCredential;
|
|
38
38
|
/** The decorator's TWO-argument form is the only one; asserted by the ABSENCE of an error. */
|
|
39
|
-
legitimateDecorator():
|
|
39
|
+
legitimateDecorator(): MethodDecorator;
|
|
40
40
|
/** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */
|
|
41
41
|
rejected(): void;
|
|
42
42
|
/**
|
|
43
|
-
* The DELETED one-argument form. `@
|
|
43
|
+
* The DELETED one-argument form. `@WpAuthApiKey('onetablet-partner')` used to compile and is now a
|
|
44
44
|
* compile error naming the missing `credentials` argument — the delivery mechanism for the
|
|
45
45
|
* migration, and the reason no `@deprecated` overload survives.
|
|
46
46
|
*/
|
|
@@ -4,7 +4,7 @@ exports.AuthApiKeyCompileAssertions = void 0;
|
|
|
4
4
|
const decorators_1 = require("./decorators");
|
|
5
5
|
/**
|
|
6
6
|
* COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the
|
|
7
|
-
* {@link
|
|
7
|
+
* {@link WpAuthApiKey} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, "unused
|
|
8
8
|
* '@ts-expect-error' directive") if the line it guards ever starts compiling.
|
|
9
9
|
*
|
|
10
10
|
* WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:
|
|
@@ -23,7 +23,7 @@ const decorators_1 = require("./decorators");
|
|
|
23
23
|
* 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential
|
|
24
24
|
* must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,
|
|
25
25
|
* so a `name` beside it would be a lie a generator has to guess about.
|
|
26
|
-
* 4. The ONE-ARGUMENT `@
|
|
26
|
+
* 4. The ONE-ARGUMENT `@WpAuthApiKey('regime')` form is GONE. It could declare a key regime while
|
|
27
27
|
* saying nothing about where the credential rides, which is the whole defect; per the
|
|
28
28
|
* no-backwards-compatibility rule there is no overload and no optional second parameter left
|
|
29
29
|
* behind, and this directive is what proves it.
|
|
@@ -40,7 +40,11 @@ class AuthApiKeyCompileAssertions {
|
|
|
40
40
|
kind: 'apikey',
|
|
41
41
|
regime: 'onetablet-partner',
|
|
42
42
|
credentials: [
|
|
43
|
-
{
|
|
43
|
+
{
|
|
44
|
+
in: 'header',
|
|
45
|
+
name: 'x-api-key',
|
|
46
|
+
description: 'The key issued to your integration.',
|
|
47
|
+
},
|
|
44
48
|
{ in: 'header', name: 'x-organization-id' },
|
|
45
49
|
],
|
|
46
50
|
};
|
|
@@ -48,17 +52,23 @@ class AuthApiKeyCompileAssertions {
|
|
|
48
52
|
}
|
|
49
53
|
/** The bearer branch, likewise asserted by the ABSENCE of an error. */
|
|
50
54
|
legitimateBearer() {
|
|
51
|
-
const credential = {
|
|
55
|
+
const credential = {
|
|
56
|
+
in: 'bearer',
|
|
57
|
+
description: 'Send the key as a bearer token.',
|
|
58
|
+
};
|
|
52
59
|
return credential;
|
|
53
60
|
}
|
|
54
61
|
/** The decorator's TWO-argument form is the only one; asserted by the ABSENCE of an error. */
|
|
55
62
|
legitimateDecorator() {
|
|
56
|
-
return (0, decorators_1.
|
|
63
|
+
return (0, decorators_1.WpAuthApiKey)('onetablet-partner', [{ in: 'header', name: 'x-api-key' }]);
|
|
57
64
|
}
|
|
58
65
|
/** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */
|
|
59
66
|
rejected() {
|
|
60
67
|
// @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted
|
|
61
|
-
const noRegime = {
|
|
68
|
+
const noRegime = {
|
|
69
|
+
kind: 'apikey',
|
|
70
|
+
credentials: [{ in: 'header', name: 'x-api-key' }],
|
|
71
|
+
};
|
|
62
72
|
void noRegime;
|
|
63
73
|
// @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect
|
|
64
74
|
const noCredentials = { kind: 'apikey', regime: 'onetablet-partner' };
|
|
@@ -66,8 +76,12 @@ class AuthApiKeyCompileAssertions {
|
|
|
66
76
|
// @ts-expect-error EMPTY is not a widening — it would emit a document with no security block
|
|
67
77
|
const empty = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };
|
|
68
78
|
void empty;
|
|
69
|
-
|
|
70
|
-
|
|
79
|
+
const misspelled = {
|
|
80
|
+
// @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union
|
|
81
|
+
kind: 'api-key',
|
|
82
|
+
regime: 'onetablet-partner',
|
|
83
|
+
credentials: [{ in: 'header', name: 'x-api-key' }],
|
|
84
|
+
};
|
|
71
85
|
void misspelled;
|
|
72
86
|
// @ts-expect-error bearer's location IS `Authorization`; a header name beside it is a lie
|
|
73
87
|
const bearerWithName = { in: 'bearer', name: 'x-api-key' };
|
|
@@ -80,13 +94,13 @@ class AuthApiKeyCompileAssertions {
|
|
|
80
94
|
void unknownLocation;
|
|
81
95
|
}
|
|
82
96
|
/**
|
|
83
|
-
* The DELETED one-argument form. `@
|
|
97
|
+
* The DELETED one-argument form. `@WpAuthApiKey('onetablet-partner')` used to compile and is now a
|
|
84
98
|
* compile error naming the missing `credentials` argument — the delivery mechanism for the
|
|
85
99
|
* migration, and the reason no `@deprecated` overload survives.
|
|
86
100
|
*/
|
|
87
101
|
oneArgumentFormIsGone() {
|
|
88
102
|
// @ts-expect-error the one-argument form is DELETED; pass the credential list as well
|
|
89
|
-
const legacy = (0, decorators_1.
|
|
103
|
+
const legacy = (0, decorators_1.WpAuthApiKey)('onetablet-partner');
|
|
90
104
|
void legacy;
|
|
91
105
|
}
|
|
92
106
|
/**
|
|
@@ -114,4 +128,4 @@ class AuthApiKeyCompileAssertions {
|
|
|
114
128
|
}
|
|
115
129
|
}
|
|
116
130
|
exports.AuthApiKeyCompileAssertions = AuthApiKeyCompileAssertions;
|
|
117
|
-
//# sourceMappingURL=
|
|
131
|
+
//# sourceMappingURL=WpAuthApiKeyCompileAssertions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WpAuthApiKeyCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthApiKeyCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { ApiKeyCredential, AuthMode } from './auth-mode';\nimport { WpAuthApiKey } from './decorators';\n\n/**\n * COMPILE-TIME assertions for the `apikey` member of the {@link AuthMode} union and for the\n * {@link WpAuthApiKey} signature. Each `@ts-expect-error` below FAILS THE BUILD (TS2578, \"unused\n * '@ts-expect-error' directive\") if the line it guards ever starts compiling.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — same reason as its sibling `AuthJwtCompileAssertions.ts`:\n * tsconfig.lib.json EXCLUDES specs and vitest strips types with esbuild, so a `@ts-expect-error` in a\n * spec is inert and the suite passes whether or not the guarded line really errors.\n *\n * WHAT IT PINS.\n *\n * 1. The union member is `{ kind: 'apikey'; regime: string; credentials: [...] }` and nothing looser.\n * `regime` is the lookup key selecting WHICH key regime a route belongs to, so a mode with no\n * regime — or spelled `api-key`, which is what a reader guesses from the decorator — must not\n * type-check its way into a switch that would then silently miss it.\n * 2. `credentials` is NON-EMPTY. A regime that names no credential generates a published document\n * with no security block, which is precisely the silent failure the argument exists to remove, so\n * `[]` is a compile error rather than a runtime throw (shim shape #4).\n * 3. {@link ApiKeyCredential} makes the two OpenAPI schemes MUTUALLY exclusive: a header credential\n * must carry its header name, and a bearer credential must NOT — its location IS `Authorization`,\n * so a `name` beside it would be a lie a generator has to guess about.\n * 4. The ONE-ARGUMENT `@WpAuthApiKey('regime')` form is GONE. It could declare a key regime while\n * saying nothing about where the credential rides, which is the whole defect; per the\n * no-backwards-compatibility rule there is no overload and no optional second parameter left\n * behind, and this directive is what proves it.\n *\n * The EXHAUSTIVENESS half needs no directive: `apiKeyIsCoveredExhaustively` below returns on every\n * branch with no `default`, so dropping the `apikey` case makes tsc fail with TS7030 (not all code\n * paths return a value). That is the same property `AuthFilter.verifiesCaller` and\n * `DestinationTrust.forAuthMode` rely on, asserted here where it cannot be edited away by accident.\n */\nexport class AuthApiKeyCompileAssertions {\n /** The one legitimate spelling must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): AuthMode {\n const mode: AuthMode = {\n kind: 'apikey',\n regime: 'onetablet-partner',\n credentials: [\n {\n in: 'header',\n name: 'x-api-key',\n description: 'The key issued to your integration.',\n },\n { in: 'header', name: 'x-organization-id' },\n ],\n };\n return mode;\n }\n\n /** The bearer branch, likewise asserted by the ABSENCE of an error. */\n legitimateBearer(): ApiKeyCredential {\n const credential: ApiKeyCredential = {\n in: 'bearer',\n description: 'Send the key as a bearer token.',\n };\n return credential;\n }\n\n /** The decorator's TWO-argument form is the only one; asserted by the ABSENCE of an error. */\n legitimateDecorator(): MethodDecorator {\n return WpAuthApiKey('onetablet-partner', [{ in: 'header', name: 'x-api-key' }]);\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error `regime` is REQUIRED — it selects which key regime, so it cannot be omitted\n const noRegime: AuthMode = {\n kind: 'apikey',\n credentials: [{ in: 'header', name: 'x-api-key' }],\n };\n void noRegime;\n // @ts-expect-error `credentials` is REQUIRED — a regime that declares no location is the defect\n const noCredentials: AuthMode = { kind: 'apikey', regime: 'onetablet-partner' };\n void noCredentials;\n // @ts-expect-error EMPTY is not a widening — it would emit a document with no security block\n const empty: AuthMode = { kind: 'apikey', regime: 'onetablet-partner', credentials: [] };\n void empty;\n const misspelled: AuthMode = {\n // @ts-expect-error the discriminant is 'apikey'; 'api-key' is not a member of the union\n kind: 'api-key',\n regime: 'onetablet-partner',\n credentials: [{ in: 'header', name: 'x-api-key' }],\n };\n void misspelled;\n // @ts-expect-error bearer's location IS `Authorization`; a header name beside it is a lie\n const bearerWithName: ApiKeyCredential = { in: 'bearer', name: 'x-api-key' };\n void bearerWithName;\n // @ts-expect-error a header credential with no header name cannot become a securityScheme\n const headerWithNoName: ApiKeyCredential = { in: 'header' };\n void headerWithNoName;\n // @ts-expect-error 'query' is not reachable — the framework declares only these two locations\n const unknownLocation: ApiKeyCredential = { in: 'query', name: 'api_key' };\n void unknownLocation;\n }\n\n /**\n * The DELETED one-argument form. `@WpAuthApiKey('onetablet-partner')` used to compile and is now a\n * compile error naming the missing `credentials` argument — the delivery mechanism for the\n * migration, and the reason no `@deprecated` overload survives.\n */\n oneArgumentFormIsGone(): void {\n // @ts-expect-error the one-argument form is DELETED; pass the credential list as well\n const legacy = WpAuthApiKey('onetablet-partner');\n void legacy;\n }\n\n /**\n * Exhaustiveness, stated as code: NO `default`, a return on every branch. Deleting the `apikey`\n * case turns this into TS7030 — the compile error that forces a DECISION about a new mode's trust\n * posture rather than letting it land on one by accident.\n */\n apiKeyIsCoveredExhaustively(mode: AuthMode): string {\n switch (mode.kind) {\n case 'public':\n return 'public';\n case 'jwt':\n return 'jwt';\n case 'oidc':\n return 'oidc';\n case 'shared-secret':\n return 'shared-secret';\n case 'webhook':\n return 'webhook';\n case 'apikey':\n return 'apikey';\n case 'local-only':\n return 'local-only';\n }\n }\n}\n"]}
|
|
@@ -26,4 +26,6 @@ export declare class AuthJwtCompileAssertions {
|
|
|
26
26
|
legitimate(): void;
|
|
27
27
|
/** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */
|
|
28
28
|
rejected(): void;
|
|
29
|
+
/** Class-level auth readers were deleted; every metadata lookup must name the audited method. */
|
|
30
|
+
readersRequireMethodName(): void;
|
|
29
31
|
}
|
|
@@ -28,27 +28,34 @@ const decorators_1 = require("./decorators");
|
|
|
28
28
|
class AuthJwtCompileAssertions {
|
|
29
29
|
/** The two legitimate spellings must keep compiling; this half is asserted by the ABSENCE of error. */
|
|
30
30
|
legitimate() {
|
|
31
|
-
void (0, decorators_1.
|
|
32
|
-
void (0, decorators_1.
|
|
33
|
-
void (0, decorators_1.
|
|
34
|
-
void (0, decorators_1.
|
|
35
|
-
void (0, decorators_1.
|
|
31
|
+
void (0, decorators_1.WpAuthJwt)({ roles: ['admin'] });
|
|
32
|
+
void (0, decorators_1.WpAuthJwt)({ roles: ['admin', 'editor'] });
|
|
33
|
+
void (0, decorators_1.WpAuthJwt)({ allRolesAllowed: true });
|
|
34
|
+
void (0, decorators_1.WpAuthJwt)({ allRolesAllowed: true, inOrg: true });
|
|
35
|
+
void (0, decorators_1.WpAuthJwt)({ roles: ['admin'], tenantScoped: true });
|
|
36
36
|
}
|
|
37
37
|
/** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */
|
|
38
38
|
rejected() {
|
|
39
39
|
// @ts-expect-error pick a branch — {} may not mean "any authenticated user"
|
|
40
|
-
void (0, decorators_1.
|
|
40
|
+
void (0, decorators_1.WpAuthJwt)({});
|
|
41
41
|
// @ts-expect-error roles needs AT LEAST ONE entry; [] was the old silent widest grant
|
|
42
|
-
void (0, decorators_1.
|
|
42
|
+
void (0, decorators_1.WpAuthJwt)({ roles: [] });
|
|
43
43
|
// @ts-expect-error contradictory: wide AND role-gated at the same time
|
|
44
|
-
void (0, decorators_1.
|
|
44
|
+
void (0, decorators_1.WpAuthJwt)({ allRolesAllowed: true, roles: ['admin'] });
|
|
45
45
|
// @ts-expect-error allRolesAllowed:false is a redundant SECOND spelling of role-gating
|
|
46
|
-
void (0, decorators_1.
|
|
46
|
+
void (0, decorators_1.WpAuthJwt)({ roles: ['admin'], allRolesAllowed: false });
|
|
47
47
|
// @ts-expect-error `false` is never how you say "wide" — the other branch is for that
|
|
48
|
-
void (0, decorators_1.
|
|
48
|
+
void (0, decorators_1.WpAuthJwt)({ allRolesAllowed: false });
|
|
49
49
|
// @ts-expect-error app-defined fields alone authorize nothing; a role decision is mandatory
|
|
50
|
-
void (0, decorators_1.
|
|
50
|
+
void (0, decorators_1.WpAuthJwt)({ inOrg: true });
|
|
51
|
+
}
|
|
52
|
+
/** Class-level auth readers were deleted; every metadata lookup must name the audited method. */
|
|
53
|
+
readersRequireMethodName() {
|
|
54
|
+
// @ts-expect-error methodName is mandatory so removed class-level auth cannot silently compile
|
|
55
|
+
void (0, decorators_1.getAuthMeta)(AuthJwtCompileAssertions);
|
|
56
|
+
// @ts-expect-error methodName is mandatory so removed class-level auth cannot silently compile
|
|
57
|
+
void (0, decorators_1.getAuthMode)(AuthJwtCompileAssertions);
|
|
51
58
|
}
|
|
52
59
|
}
|
|
53
60
|
exports.AuthJwtCompileAssertions = AuthJwtCompileAssertions;
|
|
54
|
-
//# sourceMappingURL=
|
|
61
|
+
//# sourceMappingURL=WpAuthJwtCompileAssertions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WpAuthJwtCompileAssertions.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WpAuthJwtCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,6CAAmE;AAEnE;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAa,wBAAwB;IACjC,uGAAuG;IACvG,UAAU;QACN,KAAK,IAAA,sBAAS,EAAC,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrC,KAAK,IAAA,sBAAS,EAAC,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC/C,KAAK,IAAA,sBAAS,EAAC,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,KAAK,IAAA,sBAAS,EAAC,EAAE,eAAe,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,KAAK,IAAA,sBAAS,EAAC,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,4FAA4F;IAC5F,QAAQ;QACJ,4EAA4E;QAC5E,KAAK,IAAA,sBAAS,EAAC,EAAE,CAAC,CAAC;QACnB,sFAAsF;QACtF,KAAK,IAAA,sBAAS,EAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QAC9B,uEAAuE;QACvE,KAAK,IAAA,sBAAS,EAAC,EAAE,eAAe,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC5D,uFAAuF;QACvF,KAAK,IAAA,sBAAS,EAAC,EAAE,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;QAC7D,sFAAsF;QACtF,KAAK,IAAA,sBAAS,EAAC,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3C,4FAA4F;QAC5F,KAAK,IAAA,sBAAS,EAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,CAAC;IAED,iGAAiG;IACjG,wBAAwB;QACpB,+FAA+F;QAC/F,KAAK,IAAA,wBAAW,EAAC,wBAAwB,CAAC,CAAC;QAC3C,+FAA+F;QAC/F,KAAK,IAAA,wBAAW,EAAC,wBAAwB,CAAC,CAAC;IAC/C,CAAC;CACJ;AAjCD,4DAiCC","sourcesContent":["import { getAuthMeta, getAuthMode, WpAuthJwt } from './decorators';\n\n/**\n * COMPILE-TIME assertions for `JwtRoles` — the guarantee that every broken role decision is a compile\n * error rather than a silent over-permissioning. Each `@ts-expect-error` below FAILS THE BUILD (TS2578,\n * \"unused '@ts-expect-error' directive\") if the line it guards ever starts compiling. Verified by\n * deliberately making a guarded line valid and watching `tsc -p tsconfig.lib.json` go red.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE. It was, and it proved nothing: tsconfig.lib.json EXCLUDES spec\n * files, and vitest transpiles with esbuild, which strips types without checking them. So a\n * `@ts-expect-error` in a spec is inert — the suite passes whether or not the guarded line really\n * errors. Verified the hard way: with one guarded line deliberately made VALID, both `vitest run` and\n * `nx run core-util:ci` stayed green. A type-level guarantee must be asserted in a file the\n * type-checker actually compiles, which is any non-spec file under `src/`.\n *\n * WHY IT CALLS THE FACTORIES INSTEAD OF USING DECORATOR SYNTAX. A decorator factory is a plain\n * function, so a direct call type-checks the argument identically — and the first cut of this file,\n * which used `@ApiPath` + `@Endpoint` on an abstract class, made the architecture scanner classify\n * core-util as exporting an API contract and demand a `role:api-lib` tag. A test fixture must not\n * change what the repo believes about its own architecture.\n *\n * The counterpart RUNTIME behaviour (that `allRolesAllowed: true` reads back as `[]` through\n * `rolesRequired`) is asserted in `__tests__/pubsub-and-auth-decorators.spec.ts`, where runtime\n * assertions belong.\n */\nexport class AuthJwtCompileAssertions {\n /** The two legitimate spellings must keep compiling; this half is asserted by the ABSENCE of error. */\n legitimate(): void {\n void WpAuthJwt({ roles: ['admin'] });\n void WpAuthJwt({ roles: ['admin', 'editor'] });\n void WpAuthJwt({ allRolesAllowed: true });\n void WpAuthJwt({ allRolesAllowed: true, inOrg: true });\n void WpAuthJwt({ roles: ['admin'], tenantScoped: true });\n }\n\n /** Every one of these must be UNWRITABLE. A directive going unused here fails the build. */\n rejected(): void {\n // @ts-expect-error pick a branch — {} may not mean \"any authenticated user\"\n void WpAuthJwt({});\n // @ts-expect-error roles needs AT LEAST ONE entry; [] was the old silent widest grant\n void WpAuthJwt({ roles: [] });\n // @ts-expect-error contradictory: wide AND role-gated at the same time\n void WpAuthJwt({ allRolesAllowed: true, roles: ['admin'] });\n // @ts-expect-error allRolesAllowed:false is a redundant SECOND spelling of role-gating\n void WpAuthJwt({ roles: ['admin'], allRolesAllowed: false });\n // @ts-expect-error `false` is never how you say \"wide\" — the other branch is for that\n void WpAuthJwt({ allRolesAllowed: false });\n // @ts-expect-error app-defined fields alone authorize nothing; a role decision is mandatory\n void WpAuthJwt({ inOrg: true });\n }\n\n /** Class-level auth readers were deleted; every metadata lookup must name the audited method. */\n readersRequireMethodName(): void {\n // @ts-expect-error methodName is mandatory so removed class-level auth cannot silently compile\n void getAuthMeta(AuthJwtCompileAssertions);\n // @ts-expect-error methodName is mandatory so removed class-level auth cannot silently compile\n void getAuthMode(AuthJwtCompileAssertions);\n }\n}\n"]}
|
package/src/http/auth-mode.d.ts
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
* every broken combination a compile error:
|
|
14
14
|
*
|
|
15
15
|
* ```typescript
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
-
* @
|
|
19
|
-
* @
|
|
16
|
+
* @WpAuthJwt({ roles: ['admin'] }) // ✅ role-gated (any-of)
|
|
17
|
+
* @WpAuthJwt({ allRolesAllowed: true }) // ✅ every authenticated user, said out loud
|
|
18
|
+
* @WpAuthJwt({}) // ❌ pick a branch
|
|
19
|
+
* @WpAuthJwt({ roles: [] }) // ❌ needs at least one role
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
22
|
* `allRolesAllowed` exists ONLY on the wide branch (the dangerous half must be a greppable token, and
|
|
@@ -37,7 +37,7 @@ export type JwtRoles = {
|
|
|
37
37
|
};
|
|
38
38
|
/**
|
|
39
39
|
* JwtRequirement - the {@link JwtRoles} decision PLUS any app-defined authorization fields, e.g.
|
|
40
|
-
* `@
|
|
40
|
+
* `@WpAuthJwt({ allRolesAllowed: true, inOrg: true })`. The framework authenticates (JwtHook.parseJwt)
|
|
41
41
|
* and enforces the roles any-of; the app overrides JwtHook.authorizeJwt to enforce its own fields.
|
|
42
42
|
* Both hook methods are ASYNC, so an app field like `inOrg` may be answered from a datastore.
|
|
43
43
|
*
|
|
@@ -106,13 +106,13 @@ export type ApiKeyCredentials = readonly [ApiKeyCredential, ...ApiKeyCredential[
|
|
|
106
106
|
* - `shared-secret` → constant-time compare of a header against the secret bound for `secretKey`
|
|
107
107
|
* - `webhook` → an OUTSIDE vendor signed this request its own way; the app's bound `WebhookAuthCallback`
|
|
108
108
|
* verifies it, selected by `name`. The framework ships NO vendor crypto (see
|
|
109
|
-
* {@link
|
|
109
|
+
* {@link WpAuthWebhook}).
|
|
110
110
|
* - `apikey` → a CUSTOMER holds the credential; the app's bound `ApiKeyHook` looks it up
|
|
111
111
|
* (async, over the whole header set) and returns the context to seed, selected
|
|
112
112
|
* by `regime`. `credentials` DECLARES where the credential rides — an ORDERED,
|
|
113
113
|
* non-empty list, because a real regime authenticates a PAIR (a key AND the
|
|
114
114
|
* organization id it acts for) that must be presented TOGETHER. NOT a peer
|
|
115
|
-
* service — see {@link
|
|
115
|
+
* service — see {@link WpAuthApiKey}.
|
|
116
116
|
* - `local-only` → exists ONLY on a developer's machine; not registered and never served when
|
|
117
117
|
* {@link RuntimeLocality} says this process is deployed. Authenticates NOBODY —
|
|
118
118
|
* it is a deployment gate, not a credential.
|
|
@@ -140,7 +140,7 @@ export type AuthMode = {
|
|
|
140
140
|
};
|
|
141
141
|
/**
|
|
142
142
|
* Auth metadata attached to a class or method via one of the auth decorators
|
|
143
|
-
* (@
|
|
143
|
+
* (@WpAuthPublic / @WpAuthJwt / @WpAuthOidc / @WpAuthSharedSecret / @WpAuthWebhook / @WpAuthApiKey / @WpAuthLocalOnly) —
|
|
144
144
|
* one per credential kind.
|
|
145
145
|
*
|
|
146
146
|
* Carries a discriminated {@link AuthMode} and nothing else. It USED to also expose
|
package/src/http/auth-mode.js
CHANGED
|
@@ -13,7 +13,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
13
13
|
exports.AuthMeta = void 0;
|
|
14
14
|
/**
|
|
15
15
|
* Auth metadata attached to a class or method via one of the auth decorators
|
|
16
|
-
* (@
|
|
16
|
+
* (@WpAuthPublic / @WpAuthJwt / @WpAuthOidc / @WpAuthSharedSecret / @WpAuthWebhook / @WpAuthApiKey / @WpAuthLocalOnly) —
|
|
17
17
|
* one per credential kind.
|
|
18
18
|
*
|
|
19
19
|
* Carries a discriminated {@link AuthMode} and nothing else. It USED to also expose
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-mode.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/auth-mode.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAiHH;;;;;;;;;;;GAWG;AACH,MAAa,QAAQ;IACjB,IAAI,CAAW;IAEf,YAAY,IAAc;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AAND,4BAMC","sourcesContent":["/**\n * The TYPE layer of the auth surface: what an endpoint's credential posture IS, with no decorator and\n * no reflect-metadata. `decorators.ts` (which attaches these) imports FROM here, never the other way,\n * so a reader that only needs to switch on a mode — `DestinationTrust`, `RouteMetadata`, a spec\n * generator — does not drag the whole decorator surface in with it.\n *\n * Split out of `decorators.ts` purely for file size, exactly as `api-kind.ts`, `external-caller.ts` and\n * `RouteMetadata.ts` were before it. Nothing about these types' role changed in the move, and the\n * barrel keeps the package surface identical.\n */\n\n/**\n * The role decision for a JWT endpoint, as a union the COMPILER enforces — one spelling per decision,\n * every broken combination a compile error:\n *\n * ```typescript\n * @
|
|
1
|
+
{"version":3,"file":"auth-mode.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/auth-mode.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAiHH;;;;;;;;;;;GAWG;AACH,MAAa,QAAQ;IACjB,IAAI,CAAW;IAEf,YAAY,IAAc;QACtB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACrB,CAAC;CACJ;AAND,4BAMC","sourcesContent":["/**\n * The TYPE layer of the auth surface: what an endpoint's credential posture IS, with no decorator and\n * no reflect-metadata. `decorators.ts` (which attaches these) imports FROM here, never the other way,\n * so a reader that only needs to switch on a mode — `DestinationTrust`, `RouteMetadata`, a spec\n * generator — does not drag the whole decorator surface in with it.\n *\n * Split out of `decorators.ts` purely for file size, exactly as `api-kind.ts`, `external-caller.ts` and\n * `RouteMetadata.ts` were before it. Nothing about these types' role changed in the move, and the\n * barrel keeps the package surface identical.\n */\n\n/**\n * The role decision for a JWT endpoint, as a union the COMPILER enforces — one spelling per decision,\n * every broken combination a compile error:\n *\n * ```typescript\n * @WpAuthJwt({ roles: ['admin'] }) // ✅ role-gated (any-of)\n * @WpAuthJwt({ allRolesAllowed: true }) // ✅ every authenticated user, said out loud\n * @WpAuthJwt({}) // ❌ pick a branch\n * @WpAuthJwt({ roles: [] }) // ❌ needs at least one role\n * ```\n *\n * `allRolesAllowed` exists ONLY on the wide branch (the dangerous half must be a greppable token, and\n * the narrow branch rejects it as a redundant second spelling); `roles` is a NON-EMPTY tuple so\n * \"declared roles, passed none\" — the old optional `string[]`'s silent widest grant — cannot be written.\n * All six bad cases are pinned in `AuthJwtCompileAssertions.ts` — a COMPILED file, not a spec: tsc\n * fails the build (TS2578) if any starts compiling. A spec cannot do this (see that file's header).\n *\n * WHY a type rather than the runtime `throw` this replaced: `.claude/review/backwards-compatibility.md`\n * shim shapes #4 and #5. Not restated here — three copies of one rationale is three things to drift.\n */\nexport type JwtRoles =\n | { allRolesAllowed: true; roles?: never }\n | { roles: readonly [string, ...string[]]; allRolesAllowed?: never };\n\n/**\n * JwtRequirement - the {@link JwtRoles} decision PLUS any app-defined authorization fields, e.g.\n * `@WpAuthJwt({ allRolesAllowed: true, inOrg: true })`. The framework authenticates (JwtHook.parseJwt)\n * and enforces the roles any-of; the app overrides JwtHook.authorizeJwt to enforce its own fields.\n * Both hook methods are ASYNC, so an app field like `inOrg` may be answered from a datastore.\n *\n * This was a SECOND decorator (`@Auth`) whose `roles` was optional — so `@Auth({})` reached the exact\n * widest grant that {@link JwtRoles} exists to make un-typeable. Folding it in leaves one decorator per\n * credential kind and closes that route by construction.\n */\n// webpieces-disable no-any-unknown -- app-defined authorization fields (inOrg, tenant, ...)\nexport type JwtRequirement = JwtRoles & { [field: string]: unknown };\n\n/**\n * WHERE ONE api-key credential rides on the wire, as a union the COMPILER enforces — the DECLARATION\n * a spec generator turns into an OpenAPI `securityScheme`:\n *\n * ```typescript\n * { in: 'header', name: 'x-api-key' } // ✅ → {type: apiKey, in: header, name: x-api-key}\n * { in: 'bearer' } // ✅ → {type: http, scheme: bearer}\n * { in: 'bearer', name: 'x-api-key' } // ❌ bearer's location IS `Authorization`; a name is a lie\n * { in: 'header' } // ❌ a header credential with no header name is unusable\n * ```\n *\n * WHY A UNION AND NOT ONE OPTIONAL `headerName`. The two OpenAPI schemes reachable from \"an api key\"\n * are structurally DIFFERENT documents (`type: apiKey` + `in` + `name` vs `type: http` + `scheme`).\n * A single optional field makes the contradictory combination REPRESENTABLE, and a generator handed\n * `{in: 'bearer', name: 'x-api-key'}` has to either guess or emit a silently-wrong published contract.\n * `name?: never` on the bearer branch is what deletes that state — the same device {@link JwtRoles}\n * uses, pinned the same way in `AuthApiKeyCompileAssertions.ts` (a COMPILED file, not a spec).\n *\n * THIS IS DESCRIPTION, NOT HANDLING. The framework reads no header from this; `ApiKeyHook` still gets\n * the whole request and still owns the cross-check. Declaring the location cannot regress running auth\n * — it exists so the location stops living ONLY in a hand-written spec fragment that drifts silently\n * from the hook's own header constants.\n *\n * `description` is carried because it is what a docs site renders on its authorization card, and that\n * prose belongs beside the declaration rather than in a hand-maintained JSON file.\n */\nexport type ApiKeyCredential =\n | { in: 'header'; name: string; description?: string }\n | { in: 'bearer'; name?: never; description?: string };\n\n/**\n * EVERY credential one api-key regime requires, in the order they appear in the published document.\n *\n * A NON-EMPTY tuple, for the same reason {@link JwtRoles}'s `roles` is one: a regime that declares no\n * credential would generate a document with no security block — the exact silent failure the\n * declaration exists to remove — so `[]` is a COMPILE error rather than a runtime throw (shim shape\n * #4). More than one entry is the normal case, not an exotic one: a real regime authenticates a PAIR,\n * and every entry here must be presented TOGETHER (an AND, i.e. ONE OpenAPI security-requirement\n * object holding every scheme, never a LIST of one-key objects, which would mean \"either suffices\").\n */\nexport type ApiKeyCredentials = readonly [ApiKeyCredential, ...ApiKeyCredential[]];\n\n/**\n * The service-to-service / user auth mode of an endpoint. Discriminated union so a filter can\n * `switch (mode.kind)` and get the data it needs, exhaustively.\n *\n * - `public` → no auth check\n * - `jwt` → user JWT; `requirement` carries the compiler-enforced role decision\n * ({@link JwtRoles}) plus any app-defined authorization fields\n * - `oidc` → Google OIDC service-to-service (Cloud Tasks delivery / cross-service RPC);\n * `callers` is the allow-list of caller SAs ('self' = this service's SA)\n * - `shared-secret` → constant-time compare of a header against the secret bound for `secretKey`\n * - `webhook` → an OUTSIDE vendor signed this request its own way; the app's bound `WebhookAuthCallback`\n * verifies it, selected by `name`. The framework ships NO vendor crypto (see\n * {@link WpAuthWebhook}).\n * - `apikey` → a CUSTOMER holds the credential; the app's bound `ApiKeyHook` looks it up\n * (async, over the whole header set) and returns the context to seed, selected\n * by `regime`. `credentials` DECLARES where the credential rides — an ORDERED,\n * non-empty list, because a real regime authenticates a PAIR (a key AND the\n * organization id it acts for) that must be presented TOGETHER. NOT a peer\n * service — see {@link WpAuthApiKey}.\n * - `local-only` → exists ONLY on a developer's machine; not registered and never served when\n * {@link RuntimeLocality} says this process is deployed. Authenticates NOBODY —\n * it is a deployment gate, not a credential.\n */\nexport type AuthMode =\n | { kind: 'public' }\n | { kind: 'jwt'; requirement: JwtRequirement }\n | { kind: 'oidc'; callers: string[] }\n | { kind: 'shared-secret'; secretKey: string }\n | { kind: 'webhook'; name: string }\n | { kind: 'apikey'; regime: string; credentials: ApiKeyCredentials }\n | { kind: 'local-only' };\n\n/**\n * Auth metadata attached to a class or method via one of the auth decorators\n * (@WpAuthPublic / @WpAuthJwt / @WpAuthOidc / @WpAuthSharedSecret / @WpAuthWebhook / @WpAuthApiKey / @WpAuthLocalOnly) —\n * one per credential kind.\n *\n * Carries a discriminated {@link AuthMode} and nothing else. It USED to also expose\n * `authenticated`/`roles` getters \"for back-compat with readers that only understand the user-JWT\n * model\" — deleted, because nothing read them: every reader (AuthFilter, BrowserProxyClient,\n * ProxyClient) switches on `mode.kind`, which is the whole point of the discriminated union. A\n * flattened view of a union is a second spelling of it, and the flattened one silently answers\n * `authenticated: true` for oidc and shared-secret too.\n */\nexport class AuthMeta {\n mode: AuthMode;\n\n constructor(mode: AuthMode) {\n this.mode = mode;\n }\n}\n"]}
|