@webpieces/core-context 0.4.923 → 0.4.925

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-context",
3
- "version": "0.4.923",
3
+ "version": "0.4.925",
4
4
  "description": "AsyncLocalStorage-based context management for request-scoped data",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,7 +22,7 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-util": "0.4.923",
25
+ "@webpieces/core-util": "0.4.925",
26
26
  "@inversifyjs/binding-decorators": "1.1.5",
27
27
  "inversify": "7.10.4",
28
28
  "reflect-metadata": "0.2.2"
@@ -32,7 +32,7 @@ export declare class PendingTrustedValue {
32
32
  * Inbound trusted values never enter the context. They are stashed HERE, and `AuthFilter` decides
33
33
  * what to do with them once it knows who the caller is:
34
34
  *
35
- * - `oidc(...)` / `sharedSecret(...)` — the CALLER's own identity was verified, so this is an
35
+ * - `oidcSameProject()` / `oidcSvcs(...)` / `sharedSecret(...)` — the CALLER's own identity was verified, so this is an
36
36
  * internal service passing along context it already holds. Admit the pending values as trusted.
37
37
  * This is what makes service-to-service propagation of a verified userId work, and it is the whole
38
38
  * reason trusted keys are allowed to have an `httpHeader` at all.
@@ -45,7 +45,7 @@ exports.PendingTrustedValue = PendingTrustedValue;
45
45
  * Inbound trusted values never enter the context. They are stashed HERE, and `AuthFilter` decides
46
46
  * what to do with them once it knows who the caller is:
47
47
  *
48
- * - `oidc(...)` / `sharedSecret(...)` — the CALLER's own identity was verified, so this is an
48
+ * - `oidcSameProject()` / `oidcSvcs(...)` / `sharedSecret(...)` — the CALLER's own identity was verified, so this is an
49
49
  * internal service passing along context it already holds. Admit the pending values as trusted.
50
50
  * This is what makes service-to-service propagation of a verified userId work, and it is the whole
51
51
  * reason trusted keys are allowed to have an `httpHeader` at all.
@@ -1 +1 @@
1
- {"version":3,"file":"PendingWireTrust.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/PendingWireTrust.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAElD;;;;GAIG;AACH,MAAM,sBAAsB,GAAG,kCAAkC,CAAC;AAElE;;;;GAIG;AACH,MAAa,mBAAmB;IAER;IACA;IAFpB,YACoB,GAAyB,EACzB,KAAa;QADb,QAAG,GAAH,GAAG,CAAsB;QACzB,UAAK,GAAL,KAAK,CAAQ;IAC9B,CAAC;CACP;AALD,kDAKC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,oBAAoB;IACtB;;;OAGG;IACH,KAAK,CAAC,GAAyB,EAAE,KAAa;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,GAAG,EAA+B,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3D,+BAAc,CAAC,GAAG,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED;;;OAGG;IACH,OAAO;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,EAAE,CAAC;QACd,CAAC;QACD,+BAAc,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;IAEO,OAAO;QACX,OAAO,+BAAc,CAAC,GAAG,CAAmC,sBAAsB,CAAC,CAAC;IACxF,CAAC;CACJ;AAED,sEAAsE;AACzD,QAAA,gBAAgB,GAAG,IAAI,oBAAoB,EAAE,CAAC","sourcesContent":["import { AnyTrustedContextKey } from '@webpieces/core-util';\nimport { RequestContext } from './RequestContext';\n\n/**\n * Reserved (deliberately UNREGISTERED) slot holding the pending map. Not a ContextKey: it is\n * framework plumbing that exists only between the inbound fill and the AuthFilter, and giving it a\n * ContextKey would make it transferrable/loggable, neither of which it should ever be.\n */\nconst PENDING_WIRE_TRUST_KEY = '__webpieces_pending_wire_trust__';\n\n/**\n * One trusted key that ARRIVED ON THE WIRE and has not yet been vouched for. Data-only (a class,\n * per the guidelines). Carries the key as well as the value so a rejection message can name the\n * HTTP header the caller actually sent, not just the context name.\n */\nexport class PendingTrustedValue {\n constructor(\n public readonly key: AnyTrustedContextKey,\n public readonly value: string,\n ) {}\n}\n\n/**\n * PendingWireTrust - the holding pen between \"a trusted key arrived on the wire\" and \"we know\n * whether we may believe it\".\n *\n * ## The hole this closes\n *\n * `RequestContextHeaders.fillFromRequest` runs at the TRANSPORT level (`ExpressWrapper`), and\n * `AuthFilter` runs later, as a filter. So the wire always gets to write first and the authenticator\n * second. If the inbound loop wrote trusted keys straight into the context, then for the entire\n * window before AuthFilter ran — and forever after, on any route where the authenticator does not\n * happen to stamp that particular key — a value typed by whoever sent the request would be sitting\n * in the slot that `getTrusted` reads. `curl -H 'x-user-id: victim'` would be an authenticated\n * identity.\n *\n * That is not hypothetical on this codebase: the framework's own {@link DefaultJwtHook} returns an\n * EMPTY `AuthenticatedCaller.entries`, so a fully verified `jwt()` request stamps no context entries at\n * all and would leave a forged `x-user-id` completely unopposed.\n *\n * ## The fix\n *\n * Inbound trusted values never enter the context. They are stashed HERE, and `AuthFilter` decides\n * what to do with them once it knows who the caller is:\n *\n * - `oidc(...)` / `sharedSecret(...)` — the CALLER's own identity was verified, so this is an\n * internal service passing along context it already holds. Admit the pending values as trusted.\n * This is what makes service-to-service propagation of a verified userId work, and it is the whole\n * reason trusted keys are allowed to have an `httpHeader` at all.\n * - `jwt()` / public — the caller may be a browser or anyone with curl. A pending value is\n * admitted ONLY if the authenticator independently derived the SAME value. Anything else — a\n * different value, or a value nothing vouched for — rejects the request.\n *\n * Note the deliberate asymmetry with a \"strip it and carry on\" design: a mismatch is not merely\n * neutralized, it FAILS. Rate limiters commonly bucket on the inbound header rather than on the JWT,\n * so a request whose header says `alice` and whose JWT says `bob` was rate-limited as the wrong\n * principal. Letting the JWT quietly win would turn every forged header into a free rate-limit\n * bypass. There is no legitimate caller that sends a header contradicting its own credential.\n *\n * ## If nothing reconciles\n *\n * `AuthFilter` is auto-installed on every route, so reconciliation always happens on a webpieces\n * server. Should a non-webpieces transport ever call `fillFromRequest` without one, the pending\n * values simply never arrive — the trusted key reads as absent. That is the fail-SAFE direction, and\n * it is intentional: silence beats an unvouched value.\n *\n * Module-global instance (like {@link RequestContext} itself) rather than a DI singleton — it is\n * stateless plumbing over the ambient context, used by one class on each side of the boundary.\n */\nclass PendingWireTrustImpl {\n /**\n * Hold an inbound wire value for a trusted key. Called by the inbound fill INSTEAD of writing it\n * into the context.\n */\n stash(key: AnyTrustedContextKey, value: string): void {\n const pending = this.current() ?? new Map<string, PendingTrustedValue>();\n pending.set(key.name, new PendingTrustedValue(key, value));\n RequestContext.put(PENDING_WIRE_TRUST_KEY, pending);\n }\n\n /**\n * Everything stashed for this request, and CLEAR the pen in the same step — so reconciliation\n * cannot run twice and a value cannot be re-admitted later by anything else.\n */\n takeAll(): PendingTrustedValue[] {\n const pending = this.current();\n if (!pending) {\n return [];\n }\n RequestContext.remove(PENDING_WIRE_TRUST_KEY);\n return Array.from(pending.values());\n }\n\n private current(): Map<string, PendingTrustedValue> | undefined {\n return RequestContext.get<Map<string, PendingTrustedValue>>(PENDING_WIRE_TRUST_KEY);\n }\n}\n\n/** The process-wide holding pen. See {@link PendingWireTrustImpl}. */\nexport const PendingWireTrust = new PendingWireTrustImpl();\n"]}
1
+ {"version":3,"file":"PendingWireTrust.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/PendingWireTrust.ts"],"names":[],"mappings":";;;AACA,qDAAkD;AAElD;;;;GAIG;AACH,MAAM,sBAAsB,GAAG,kCAAkC,CAAC;AAElE;;;;GAIG;AACH,MAAa,mBAAmB;IAER;IACA;IAFpB,YACoB,GAAyB,EACzB,KAAa;QADb,QAAG,GAAH,GAAG,CAAsB;QACzB,UAAK,GAAL,KAAK,CAAQ;IAC9B,CAAC;CACP;AALD,kDAKC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,oBAAoB;IACtB;;;OAGG;IACH,KAAK,CAAC,GAAyB,EAAE,KAAa;QAC1C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,IAAI,GAAG,EAA+B,CAAC;QACzE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,mBAAmB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;QAC3D,+BAAc,CAAC,GAAG,CAAC,sBAAsB,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED;;;OAGG;IACH,OAAO;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,OAAO,EAAE,CAAC;QACd,CAAC;QACD,+BAAc,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC;QAC9C,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;IAEO,OAAO;QACX,OAAO,+BAAc,CAAC,GAAG,CAAmC,sBAAsB,CAAC,CAAC;IACxF,CAAC;CACJ;AAED,sEAAsE;AACzD,QAAA,gBAAgB,GAAG,IAAI,oBAAoB,EAAE,CAAC","sourcesContent":["import { AnyTrustedContextKey } from '@webpieces/core-util';\nimport { RequestContext } from './RequestContext';\n\n/**\n * Reserved (deliberately UNREGISTERED) slot holding the pending map. Not a ContextKey: it is\n * framework plumbing that exists only between the inbound fill and the AuthFilter, and giving it a\n * ContextKey would make it transferrable/loggable, neither of which it should ever be.\n */\nconst PENDING_WIRE_TRUST_KEY = '__webpieces_pending_wire_trust__';\n\n/**\n * One trusted key that ARRIVED ON THE WIRE and has not yet been vouched for. Data-only (a class,\n * per the guidelines). Carries the key as well as the value so a rejection message can name the\n * HTTP header the caller actually sent, not just the context name.\n */\nexport class PendingTrustedValue {\n constructor(\n public readonly key: AnyTrustedContextKey,\n public readonly value: string,\n ) {}\n}\n\n/**\n * PendingWireTrust - the holding pen between \"a trusted key arrived on the wire\" and \"we know\n * whether we may believe it\".\n *\n * ## The hole this closes\n *\n * `RequestContextHeaders.fillFromRequest` runs at the TRANSPORT level (`ExpressWrapper`), and\n * `AuthFilter` runs later, as a filter. So the wire always gets to write first and the authenticator\n * second. If the inbound loop wrote trusted keys straight into the context, then for the entire\n * window before AuthFilter ran — and forever after, on any route where the authenticator does not\n * happen to stamp that particular key — a value typed by whoever sent the request would be sitting\n * in the slot that `getTrusted` reads. `curl -H 'x-user-id: victim'` would be an authenticated\n * identity.\n *\n * That is not hypothetical on this codebase: the framework's own {@link DefaultJwtHook} returns an\n * EMPTY `AuthenticatedCaller.entries`, so a fully verified `jwt()` request stamps no context entries at\n * all and would leave a forged `x-user-id` completely unopposed.\n *\n * ## The fix\n *\n * Inbound trusted values never enter the context. They are stashed HERE, and `AuthFilter` decides\n * what to do with them once it knows who the caller is:\n *\n * - `oidcSameProject()` / `oidcSvcs(...)` / `sharedSecret(...)` — the CALLER's own identity was verified, so this is an\n * internal service passing along context it already holds. Admit the pending values as trusted.\n * This is what makes service-to-service propagation of a verified userId work, and it is the whole\n * reason trusted keys are allowed to have an `httpHeader` at all.\n * - `jwt()` / public — the caller may be a browser or anyone with curl. A pending value is\n * admitted ONLY if the authenticator independently derived the SAME value. Anything else — a\n * different value, or a value nothing vouched for — rejects the request.\n *\n * Note the deliberate asymmetry with a \"strip it and carry on\" design: a mismatch is not merely\n * neutralized, it FAILS. Rate limiters commonly bucket on the inbound header rather than on the JWT,\n * so a request whose header says `alice` and whose JWT says `bob` was rate-limited as the wrong\n * principal. Letting the JWT quietly win would turn every forged header into a free rate-limit\n * bypass. There is no legitimate caller that sends a header contradicting its own credential.\n *\n * ## If nothing reconciles\n *\n * `AuthFilter` is auto-installed on every route, so reconciliation always happens on a webpieces\n * server. Should a non-webpieces transport ever call `fillFromRequest` without one, the pending\n * values simply never arrive — the trusted key reads as absent. That is the fail-SAFE direction, and\n * it is intentional: silence beats an unvouched value.\n *\n * Module-global instance (like {@link RequestContext} itself) rather than a DI singleton — it is\n * stateless plumbing over the ambient context, used by one class on each side of the boundary.\n */\nclass PendingWireTrustImpl {\n /**\n * Hold an inbound wire value for a trusted key. Called by the inbound fill INSTEAD of writing it\n * into the context.\n */\n stash(key: AnyTrustedContextKey, value: string): void {\n const pending = this.current() ?? new Map<string, PendingTrustedValue>();\n pending.set(key.name, new PendingTrustedValue(key, value));\n RequestContext.put(PENDING_WIRE_TRUST_KEY, pending);\n }\n\n /**\n * Everything stashed for this request, and CLEAR the pen in the same step — so reconciliation\n * cannot run twice and a value cannot be re-admitted later by anything else.\n */\n takeAll(): PendingTrustedValue[] {\n const pending = this.current();\n if (!pending) {\n return [];\n }\n RequestContext.remove(PENDING_WIRE_TRUST_KEY);\n return Array.from(pending.values());\n }\n\n private current(): Map<string, PendingTrustedValue> | undefined {\n return RequestContext.get<Map<string, PendingTrustedValue>>(PENDING_WIRE_TRUST_KEY);\n }\n}\n\n/** The process-wide holding pen. See {@link PendingWireTrustImpl}. */\nexport const PendingWireTrust = new PendingWireTrustImpl();\n"]}