@webpieces/core-util 0.4.598 → 0.4.600
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 +141 -27
- package/src/ContextKey.js +143 -7
- package/src/ContextKey.js.map +1 -1
- package/src/ContextKeyTrustCompileAssertions.d.ts +34 -0
- package/src/ContextKeyTrustCompileAssertions.js +61 -0
- package/src/ContextKeyTrustCompileAssertions.js.map +1 -0
- package/src/ContextTuple.d.ts +9 -4
- package/src/ContextTuple.js +6 -1
- package/src/ContextTuple.js.map +1 -1
- package/src/http/ApiCallContext.d.ts +8 -4
- package/src/http/ApiCallContext.js.map +1 -1
- package/src/http/HeaderRegistry.d.ts +15 -4
- package/src/http/HeaderRegistry.js +28 -7
- package/src/http/HeaderRegistry.js.map +1 -1
- package/src/http/WebpiecesCoreHeaders.d.ts +32 -13
- package/src/http/WebpiecesCoreHeaders.js +32 -13
- package/src/http/WebpiecesCoreHeaders.js.map +1 -1
- package/src/http/recorder/TestCaseRecorder.d.ts +1 -1
- package/src/http/recorder/TestCaseRecorder.js +1 -1
- package/src/http/recorder/TestCaseRecorder.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js.map +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AnyUntrustedContextKey } from '../ContextKey';
|
|
2
2
|
/**
|
|
3
3
|
* ApiCallContext - the tiny seam that lets {@link LogApiCall} (browser-safe, core-util) stamp a
|
|
4
4
|
* ContextKey (the `api` tag) into the ambient request context WITHOUT importing it.
|
|
@@ -24,16 +24,20 @@ export interface ApiCallContext {
|
|
|
24
24
|
*/
|
|
25
25
|
isActive(): boolean;
|
|
26
26
|
/**
|
|
27
|
-
* Stamp one ContextKey → value into the ambient context.
|
|
27
|
+
* Stamp one UNTRUSTED ContextKey → value into the ambient context. Untrusted by type on purpose:
|
|
28
|
+
* this seam is reachable from browser-side client code, so if it accepted a trusted key it would
|
|
29
|
+
* be a side door for forging a proven identity. The one key it actually stamps
|
|
30
|
+
* ({@link WebpiecesCoreHeaders.API_CALL_INFO}) is a log tag, which is untrusted by nature.
|
|
31
|
+
* The logger reads it back off the context
|
|
28
32
|
* (server: RequestContext.buildStructuredLogFields; browser: its own store) during the log emit.
|
|
29
33
|
*/
|
|
30
|
-
set(contextKey:
|
|
34
|
+
set(contextKey: AnyUntrustedContextKey, value: unknown): void;
|
|
31
35
|
/**
|
|
32
36
|
* Clear one ContextKey. {@link LogApiCall} calls set → log → remove as one SYNCHRONOUS span, so the
|
|
33
37
|
* tag is never held across `await`. That is what makes a single browser global safe: single-threaded,
|
|
34
38
|
* nothing can interleave between set and remove, so a concurrent call can never clobber the slot.
|
|
35
39
|
*/
|
|
36
|
-
remove(contextKey:
|
|
40
|
+
remove(contextKey: AnyUntrustedContextKey): void;
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* ApiCallContextHolder - the process-wide holder for the active {@link ApiCallContext}.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCallContext.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallContext.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"ApiCallContext.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/ApiCallContext.ts"],"names":[],"mappings":";;;AA8CA;;;;;;GAMG;AACH,MAAa,oBAAoB;IACrB,MAAM,CAAC,OAAO,CAA6B;IAEnD,sGAAsG;IACtG,6HAA6H;IAC7H,MAAM,CAAC,OAAO,CAAC,GAAmB;QAC9B,oBAAoB,CAAC,OAAO,GAAG,GAAG,CAAC;IACvC,CAAC;IAED,+FAA+F;IAC/F,gHAAgH;IAChH,MAAM,CAAC,WAAW;QACd,OAAO,oBAAoB,CAAC,OAAO,KAAK,SAAS,CAAC;IACtD,CAAC;IAED;;;OAGG;IACH,mIAAmI;IACnI,MAAM,CAAC,GAAG;QACN,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACX,wFAAwF;gBACxF,4FAA4F;gBAC5F,gFAAgF;gBAChF,oDAAoD,CACvD,CAAC;QACN,CAAC;QACD,OAAO,oBAAoB,CAAC,OAAO,CAAC;IACxC,CAAC;CACJ;AA/BD,oDA+BC","sourcesContent":["import { AnyUntrustedContextKey } from '../ContextKey';\n\n/**\n * ApiCallContext - the tiny seam that lets {@link LogApiCall} (browser-safe, core-util) stamp a\n * ContextKey (the `api` tag) into the ambient request context WITHOUT importing it.\n *\n * WHY a seam instead of a direct call: the ambient context is `RequestContext` in\n * `@webpieces/core-context`, which is built on Node `async_hooks` (AsyncLocalStorage). core-util —\n * and `ProxyClient`, which runs in a BROWSER bundle — must never import that (it would be a circular\n * dependency, and it would drag Node vocabulary into a browser build). So core-util owns only this\n * interface + a global holder; each environment installs its own impl at startup:\n * - Node server: `setupRuntime` installs a RequestContext-backed impl.\n * - Browser: `ClientHttpBrowserFactory` installs a module-global impl.\n *\n * If NEITHER installs one, {@link ApiCallContextHolder.get} THROWS (loud misconfiguration, in the\n * webpieces spirit) — see its message. There is deliberately no silent no-op default.\n *\n * This mirrors the existing global-singleton-configured-at-startup pattern (LogManager,\n * HeaderRegistry): behavior is an interface (per CLAUDE.md), the holder is the global seam.\n */\nexport interface ApiCallContext {\n /**\n * True when there is a context to stamp into (a live Node RequestContext scope; a browser is always\n * active). {@link LogApiCall} throws if this is false — an api call with nowhere to tag is a bug.\n */\n isActive(): boolean;\n\n /**\n * Stamp one UNTRUSTED ContextKey → value into the ambient context. Untrusted by type on purpose:\n * this seam is reachable from browser-side client code, so if it accepted a trusted key it would\n * be a side door for forging a proven identity. The one key it actually stamps\n * ({@link WebpiecesCoreHeaders.API_CALL_INFO}) is a log tag, which is untrusted by nature.\n * The logger reads it back off the context\n * (server: RequestContext.buildStructuredLogFields; browser: its own store) during the log emit.\n */\n // webpieces-disable no-any-unknown -- a context value is heterogeneous (the api struct here; strings elsewhere)\n set(contextKey: AnyUntrustedContextKey, value: unknown): void;\n\n /**\n * Clear one ContextKey. {@link LogApiCall} calls set → log → remove as one SYNCHRONOUS span, so the\n * tag is never held across `await`. That is what makes a single browser global safe: single-threaded,\n * nothing can interleave between set and remove, so a concurrent call can never clobber the slot.\n */\n remove(contextKey: AnyUntrustedContextKey): void;\n}\n\n/**\n * ApiCallContextHolder - the process-wide holder for the active {@link ApiCallContext}.\n *\n * Configured exactly like {@link LogManager}: the environment calls {@link ApiCallContextHolder.install}\n * at startup. Until then {@link get} THROWS, so a forgotten setup fails loudly rather than silently\n * dropping the `api` tag off every log line.\n */\nexport class ApiCallContextHolder {\n private static current: ApiCallContext | undefined;\n\n /** Install the environment's ApiCallContext (Node: RequestContext-backed; browser: module-global). */\n // webpieces-disable no-function-outside-class -- static global seam, configured once at startup (like LogManager.setFactory)\n static install(ctx: ApiCallContext): void {\n ApiCallContextHolder.current = ctx;\n }\n\n /** True once an ApiCallContext has been installed (used by tests to probe the unset state). */\n // webpieces-disable no-function-outside-class -- static global seam accessor (like HeaderRegistry.isConfigured)\n static isInstalled(): boolean {\n return ApiCallContextHolder.current !== undefined;\n }\n\n /**\n * The active ApiCallContext. Throws if nothing was installed — a one-time setup call is required:\n * `setupRuntime()` does it on a Node server; building `ClientHttpBrowserFactory` does it in a browser.\n */\n // webpieces-disable no-function-outside-class -- static global seam accessor (like LogManager/HeaderRegistry.get), not DI-injected\n static get(): ApiCallContext {\n if (!ApiCallContextHolder.current) {\n throw new Error(\n 'ApiCallContext is not installed — LogApiCall cannot tag API-call logs. Set it up ONCE ' +\n 'at startup: on a Node server, setupRuntime() installs it for you; in a browser, construct ' +\n 'ClientHttpBrowserFactory once at startup. (This is the same one-time setup as ' +\n 'HeaderRegistry.configure / LogManager.setFactory.)',\n );\n }\n return ApiCallContextHolder.current;\n }\n}\n"]}
|
|
@@ -19,7 +19,7 @@ import { AnyContextKey } from '../ContextKey';
|
|
|
19
19
|
*
|
|
20
20
|
* Duplicate validation (port of Java checkForDuplicates) runs at configure() time,
|
|
21
21
|
* so conflicting definitions fail fast at startup:
|
|
22
|
-
* - Two keys with the same `name` must agree on httpHeader/
|
|
22
|
+
* - Two keys with the same `name` must agree on httpHeader/trust/maskInLogs/isLogged.
|
|
23
23
|
* - Two keys with the same `httpHeader` must agree on `name`.
|
|
24
24
|
* - Exact duplicates collapse to one entry.
|
|
25
25
|
*/
|
|
@@ -49,8 +49,8 @@ export declare class HeaderRegistry {
|
|
|
49
49
|
* outbound request): those with an httpHeader set.
|
|
50
50
|
*/
|
|
51
51
|
getTransferredKeys(): AnyContextKey[];
|
|
52
|
-
/** Names (log keys) whose values must be masked in logs.
|
|
53
|
-
|
|
52
|
+
/** Names (log keys) whose values must be masked in logs. maskInLogs=true. */
|
|
53
|
+
getMaskedNames(): string[];
|
|
54
54
|
/**
|
|
55
55
|
* Keys that appear in logs. isLogged=true. The node logging backends read THESE and build the log
|
|
56
56
|
* field map directly from the active context — see `RequestContext.buildLogFields()` /
|
|
@@ -58,6 +58,17 @@ export declare class HeaderRegistry {
|
|
|
58
58
|
* callback, but only the server ever called them, so the seam was inlined away.)
|
|
59
59
|
*/
|
|
60
60
|
getLoggedKeys(): AnyContextKey[];
|
|
61
|
+
/**
|
|
62
|
+
* Look up a key by its CONTEXT name — the guard behind `RequestContext.get/put(name: string)`.
|
|
63
|
+
* Those raw string accessors would otherwise be a side door around the typed trust verbs
|
|
64
|
+
* (`RequestContext.get('userId')` reads a trusted value without ever saying `getTrusted`, and
|
|
65
|
+
* `put('userId', ...)` forges one without saying `putTrusted`), so they reject any name that
|
|
66
|
+
* belongs to a registered key and point the caller at the verb to use instead.
|
|
67
|
+
*
|
|
68
|
+
* Linear over the key list, which is fine: this runs only on the raw string path, and the
|
|
69
|
+
* reserved framework keys that legitimately use it ('__webpieces_*__') are a handful.
|
|
70
|
+
*/
|
|
71
|
+
findByName(name: string): AnyContextKey | undefined;
|
|
61
72
|
/** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */
|
|
62
73
|
findByHttpHeader(httpHeader: string): AnyContextKey | undefined;
|
|
63
74
|
/**
|
|
@@ -66,7 +77,7 @@ export declare class HeaderRegistry {
|
|
|
66
77
|
*/
|
|
67
78
|
private checkForDuplicates;
|
|
68
79
|
/**
|
|
69
|
-
* Two keys sharing a `name` must agree on httpHeader/
|
|
80
|
+
* Two keys sharing a `name` must agree on httpHeader/trust/maskInLogs/isLogged,
|
|
70
81
|
* otherwise the platform would behave differently depending on which module's
|
|
71
82
|
* definition happened to load first.
|
|
72
83
|
*/
|
|
@@ -22,7 +22,7 @@ const WebpiecesCoreHeaders_1 = require("./WebpiecesCoreHeaders");
|
|
|
22
22
|
*
|
|
23
23
|
* Duplicate validation (port of Java checkForDuplicates) runs at configure() time,
|
|
24
24
|
* so conflicting definitions fail fast at startup:
|
|
25
|
-
* - Two keys with the same `name` must agree on httpHeader/
|
|
25
|
+
* - Two keys with the same `name` must agree on httpHeader/trust/maskInLogs/isLogged.
|
|
26
26
|
* - Two keys with the same `httpHeader` must agree on `name`.
|
|
27
27
|
* - Exact duplicates collapse to one entry.
|
|
28
28
|
*/
|
|
@@ -44,7 +44,7 @@ class HeaderRegistry {
|
|
|
44
44
|
this.keys = this.checkForDuplicates(keys);
|
|
45
45
|
this.transferredKeys = this.keys.filter((k) => k.httpHeader !== undefined);
|
|
46
46
|
this.securedNames = this.keys
|
|
47
|
-
.filter((k) => k.
|
|
47
|
+
.filter((k) => k.maskInLogs)
|
|
48
48
|
.map((k) => k.name);
|
|
49
49
|
this.loggedKeys = this.keys.filter((k) => k.isLogged);
|
|
50
50
|
this.byHttpHeader = new Map(this.transferredKeys.map((k) => [k.httpHeader.toLowerCase(), k]));
|
|
@@ -84,8 +84,8 @@ class HeaderRegistry {
|
|
|
84
84
|
getTransferredKeys() {
|
|
85
85
|
return this.transferredKeys;
|
|
86
86
|
}
|
|
87
|
-
/** Names (log keys) whose values must be masked in logs.
|
|
88
|
-
|
|
87
|
+
/** Names (log keys) whose values must be masked in logs. maskInLogs=true. */
|
|
88
|
+
getMaskedNames() {
|
|
89
89
|
return this.securedNames;
|
|
90
90
|
}
|
|
91
91
|
/**
|
|
@@ -97,6 +97,20 @@ class HeaderRegistry {
|
|
|
97
97
|
getLoggedKeys() {
|
|
98
98
|
return this.loggedKeys;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Look up a key by its CONTEXT name — the guard behind `RequestContext.get/put(name: string)`.
|
|
102
|
+
* Those raw string accessors would otherwise be a side door around the typed trust verbs
|
|
103
|
+
* (`RequestContext.get('userId')` reads a trusted value without ever saying `getTrusted`, and
|
|
104
|
+
* `put('userId', ...)` forges one without saying `putTrusted`), so they reject any name that
|
|
105
|
+
* belongs to a registered key and point the caller at the verb to use instead.
|
|
106
|
+
*
|
|
107
|
+
* Linear over the key list, which is fine: this runs only on the raw string path, and the
|
|
108
|
+
* reserved framework keys that legitimately use it ('__webpieces_*__') are a handful.
|
|
109
|
+
*/
|
|
110
|
+
findByName(name) {
|
|
111
|
+
const lower = name.toLowerCase();
|
|
112
|
+
return this.keys.find((k) => k.name.toLowerCase() === lower);
|
|
113
|
+
}
|
|
100
114
|
/** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */
|
|
101
115
|
findByHttpHeader(httpHeader) {
|
|
102
116
|
return this.byHttpHeader.get(httpHeader.toLowerCase());
|
|
@@ -130,7 +144,7 @@ class HeaderRegistry {
|
|
|
130
144
|
return Array.from(byName.values());
|
|
131
145
|
}
|
|
132
146
|
/**
|
|
133
|
-
* Two keys sharing a `name` must agree on httpHeader/
|
|
147
|
+
* Two keys sharing a `name` must agree on httpHeader/trust/maskInLogs/isLogged,
|
|
134
148
|
* otherwise the platform would behave differently depending on which module's
|
|
135
149
|
* definition happened to load first.
|
|
136
150
|
*/
|
|
@@ -139,8 +153,15 @@ class HeaderRegistry {
|
|
|
139
153
|
if (existing.httpHeader !== duplicate.httpHeader) {
|
|
140
154
|
conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);
|
|
141
155
|
}
|
|
142
|
-
if (existing.
|
|
143
|
-
|
|
156
|
+
if (existing.trust !== duplicate.trust) {
|
|
157
|
+
// The most dangerous disagreement of the three: one module says this key is a proven
|
|
158
|
+
// fact and another says it is caller-asserted. Whichever loaded first would silently
|
|
159
|
+
// decide whether every reader of that key is doing an authorization check on a
|
|
160
|
+
// spoofable value, so it fails the whole startup instead.
|
|
161
|
+
conflicts.push(`trust ('${existing.trust}' vs '${duplicate.trust}')`);
|
|
162
|
+
}
|
|
163
|
+
if (existing.maskInLogs !== duplicate.maskInLogs) {
|
|
164
|
+
conflicts.push(`maskInLogs (${existing.maskInLogs} vs ${duplicate.maskInLogs})`);
|
|
144
165
|
}
|
|
145
166
|
if (existing.isLogged !== duplicate.isLogged) {
|
|
146
167
|
conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the\n * platform knows about. Port of Java webpieces' HeaderTranslation.\n *\n * Configured exactly like {@link LogManager} — once, at process startup — and then\n * globally accessible. There is NO DI wiring: filters/clients call\n * `HeaderRegistry.get()` instead of injecting it.\n *\n * ```ts\n * // startup (server AND browser), BEFORE LogManager.setFactory(...):\n * HeaderRegistry.configure(CompanyHeaders.ALL_HEADERS, true);\n * ```\n *\n * - `svrHeaders` the context keys this process registers — by convention the whole\n * company-wide set (all keys across all servers), e.g. CompanyHeaders.\n * - `platformHeaders` when true, also include {@link HeaderRegistry.DEFAULT_HEADERS}\n * (the webpieces common keys: request-id, correlation-id, ...).\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at configure() time,\n * so conflicting definitions fail fast at startup:\n * - Two keys with the same `name` must agree on httpHeader/isSecured/isLogged.\n * - Two keys with the same `httpHeader` must agree on `name`.\n * - Exact duplicates collapse to one entry.\n */\nexport class HeaderRegistry {\n /** The webpieces-supplied common keys; included when platformHeaders=true. */\n static readonly DEFAULT_HEADERS: AnyContextKey[] = WebpiecesCoreHeaders.ALL_HEADERS;\n\n private static instance: HeaderRegistry | undefined;\n\n private readonly keys: AnyContextKey[];\n\n // Derived collections precomputed ONCE, here in the constructor (i.e. at\n // configure() time). The hot path — every log line calls getLoggedKeys(), every\n // outbound request calls getTransferredKeys() — then returns the cached array\n // instead of re-filtering the full key list on each call. These are reachable\n // only through HeaderRegistry.get(), which throws until configure() has run.\n private readonly transferredKeys: AnyContextKey[];\n private readonly securedNames: string[];\n private readonly loggedKeys: AnyContextKey[];\n private readonly byHttpHeader: Map<string, AnyContextKey>;\n\n private constructor(keys: AnyContextKey[]) {\n this.keys = this.checkForDuplicates(keys);\n this.transferredKeys = this.keys.filter((k: AnyContextKey) => k.httpHeader !== undefined);\n this.securedNames = this.keys\n .filter((k: AnyContextKey) => k.isSecured)\n .map((k: AnyContextKey) => k.name);\n this.loggedKeys = this.keys.filter((k: AnyContextKey) => k.isLogged);\n this.byHttpHeader = new Map(\n this.transferredKeys.map((k: AnyContextKey): [string, AnyContextKey] => [k.httpHeader!.toLowerCase(), k]),\n );\n }\n\n /**\n * Install the process-wide registry. Call once at startup, BEFORE\n * LogManager.setFactory(...) (logging masks/keys off this registry).\n */\n // webpieces-disable no-function-outside-class -- HeaderRegistry is a deliberately static global singleton (like LogManager); configured once at startup, never DI-injected\n static configure(svrHeaders: AnyContextKey[], platformHeaders: boolean): void {\n const all: AnyContextKey[] = [\n ...(platformHeaders ? HeaderRegistry.DEFAULT_HEADERS : []),\n ...svrHeaders,\n ];\n HeaderRegistry.instance = new HeaderRegistry(all);\n }\n\n /** The configured registry. Throws if configure() has not been called. */\n static get(): HeaderRegistry {\n if (!HeaderRegistry.instance) {\n throw new Error(\n 'HeaderRegistry.configure(...) has not been called. Configure the registry ' +\n 'at startup (before LogManager.setFactory) so filters/logging know the context keys.',\n );\n }\n return HeaderRegistry.instance;\n }\n\n /** True once configure() has run. Used by LogManager.setFactory to fail fast. */\n static isConfigured(): boolean {\n return HeaderRegistry.instance !== undefined;\n }\n\n /** All registered keys (deduplicated). */\n getKeys(): AnyContextKey[] {\n return this.keys;\n }\n\n /**\n * Keys that transfer over the wire (inbound request -> context, and context ->\n * outbound request): those with an httpHeader set.\n */\n getTransferredKeys(): AnyContextKey[] {\n return this.transferredKeys;\n }\n\n /** Names (log keys) whose values must be masked in logs. isSecured=true. */\n getSecuredNames(): string[] {\n return this.securedNames;\n }\n\n /**\n * Keys that appear in logs. isLogged=true. The node logging backends read THESE and build the log\n * field map directly from the active context — see `RequestContext.buildLogFields()` /\n * `buildStructuredLogFields()`. (The registry used to own those two builders behind a read\n * callback, but only the server ever called them, so the seam was inlined away.)\n */\n getLoggedKeys(): AnyContextKey[] {\n return this.loggedKeys;\n }\n\n /** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */\n findByHttpHeader(httpHeader: string): AnyContextKey | undefined {\n return this.byHttpHeader.get(httpHeader.toLowerCase());\n }\n\n /**\n * Collapse exact duplicates, throw on conflicting definitions sharing a `name`\n * or an `httpHeader`.\n */\n private checkForDuplicates(allKeys: AnyContextKey[]): AnyContextKey[] {\n const byName = new Map<string, AnyContextKey>();\n const byHttpHeader = new Map<string, AnyContextKey>();\n\n for (const key of allKeys) {\n const nameKey = key.name.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, key);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, key);\n\n if (key.httpHeader !== undefined) {\n const headerKey = key.httpHeader.toLowerCase();\n const clash = byHttpHeader.get(headerKey);\n if (clash) {\n throw new Error(\n `Duplicate ContextKey httpHeader '${key.httpHeader}': ` +\n `defined by key '${clash.name}' AND key '${key.name}'. ` +\n `Each HTTP header must map to exactly one context key.`,\n );\n }\n byHttpHeader.set(headerKey, key);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two keys sharing a `name` must agree on httpHeader/isSecured/isLogged,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: AnyContextKey, duplicate: AnyContextKey): void {\n const conflicts: string[] = [];\n if (existing.httpHeader !== duplicate.httpHeader) {\n conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);\n }\n if (existing.isSecured !== duplicate.isSecured) {\n conflicts.push(`isSecured (${existing.isSecured} vs ${duplicate.isSecured})`);\n }\n if (existing.isLogged !== duplicate.isLogged) {\n conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting ContextKey definitions for '${existing.name}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Keys sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"HeaderRegistry.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/HeaderRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { ContextKey, AnyContextKey } from '../ContextKey';\nimport { WebpiecesCoreHeaders } from './WebpiecesCoreHeaders';\n\n/**\n * HeaderRegistry - the single, GLOBAL source of truth for every ContextKey the\n * platform knows about. Port of Java webpieces' HeaderTranslation.\n *\n * Configured exactly like {@link LogManager} — once, at process startup — and then\n * globally accessible. There is NO DI wiring: filters/clients call\n * `HeaderRegistry.get()` instead of injecting it.\n *\n * ```ts\n * // startup (server AND browser), BEFORE LogManager.setFactory(...):\n * HeaderRegistry.configure(CompanyHeaders.ALL_HEADERS, true);\n * ```\n *\n * - `svrHeaders` the context keys this process registers — by convention the whole\n * company-wide set (all keys across all servers), e.g. CompanyHeaders.\n * - `platformHeaders` when true, also include {@link HeaderRegistry.DEFAULT_HEADERS}\n * (the webpieces common keys: request-id, correlation-id, ...).\n *\n * Duplicate validation (port of Java checkForDuplicates) runs at configure() time,\n * so conflicting definitions fail fast at startup:\n * - Two keys with the same `name` must agree on httpHeader/trust/maskInLogs/isLogged.\n * - Two keys with the same `httpHeader` must agree on `name`.\n * - Exact duplicates collapse to one entry.\n */\nexport class HeaderRegistry {\n /** The webpieces-supplied common keys; included when platformHeaders=true. */\n static readonly DEFAULT_HEADERS: AnyContextKey[] = WebpiecesCoreHeaders.ALL_HEADERS;\n\n private static instance: HeaderRegistry | undefined;\n\n private readonly keys: AnyContextKey[];\n\n // Derived collections precomputed ONCE, here in the constructor (i.e. at\n // configure() time). The hot path — every log line calls getLoggedKeys(), every\n // outbound request calls getTransferredKeys() — then returns the cached array\n // instead of re-filtering the full key list on each call. These are reachable\n // only through HeaderRegistry.get(), which throws until configure() has run.\n private readonly transferredKeys: AnyContextKey[];\n private readonly securedNames: string[];\n private readonly loggedKeys: AnyContextKey[];\n private readonly byHttpHeader: Map<string, AnyContextKey>;\n\n private constructor(keys: AnyContextKey[]) {\n this.keys = this.checkForDuplicates(keys);\n this.transferredKeys = this.keys.filter((k: AnyContextKey) => k.httpHeader !== undefined);\n this.securedNames = this.keys\n .filter((k: AnyContextKey) => k.maskInLogs)\n .map((k: AnyContextKey) => k.name);\n this.loggedKeys = this.keys.filter((k: AnyContextKey) => k.isLogged);\n this.byHttpHeader = new Map(\n this.transferredKeys.map((k: AnyContextKey): [string, AnyContextKey] => [k.httpHeader!.toLowerCase(), k]),\n );\n }\n\n /**\n * Install the process-wide registry. Call once at startup, BEFORE\n * LogManager.setFactory(...) (logging masks/keys off this registry).\n */\n // webpieces-disable no-function-outside-class -- HeaderRegistry is a deliberately static global singleton (like LogManager); configured once at startup, never DI-injected\n static configure(svrHeaders: AnyContextKey[], platformHeaders: boolean): void {\n const all: AnyContextKey[] = [\n ...(platformHeaders ? HeaderRegistry.DEFAULT_HEADERS : []),\n ...svrHeaders,\n ];\n HeaderRegistry.instance = new HeaderRegistry(all);\n }\n\n /** The configured registry. Throws if configure() has not been called. */\n static get(): HeaderRegistry {\n if (!HeaderRegistry.instance) {\n throw new Error(\n 'HeaderRegistry.configure(...) has not been called. Configure the registry ' +\n 'at startup (before LogManager.setFactory) so filters/logging know the context keys.',\n );\n }\n return HeaderRegistry.instance;\n }\n\n /** True once configure() has run. Used by LogManager.setFactory to fail fast. */\n static isConfigured(): boolean {\n return HeaderRegistry.instance !== undefined;\n }\n\n /** All registered keys (deduplicated). */\n getKeys(): AnyContextKey[] {\n return this.keys;\n }\n\n /**\n * Keys that transfer over the wire (inbound request -> context, and context ->\n * outbound request): those with an httpHeader set.\n */\n getTransferredKeys(): AnyContextKey[] {\n return this.transferredKeys;\n }\n\n /** Names (log keys) whose values must be masked in logs. maskInLogs=true. */\n getMaskedNames(): string[] {\n return this.securedNames;\n }\n\n /**\n * Keys that appear in logs. isLogged=true. The node logging backends read THESE and build the log\n * field map directly from the active context — see `RequestContext.buildLogFields()` /\n * `buildStructuredLogFields()`. (The registry used to own those two builders behind a read\n * callback, but only the server ever called them, so the seam was inlined away.)\n */\n getLoggedKeys(): AnyContextKey[] {\n return this.loggedKeys;\n }\n\n /**\n * Look up a key by its CONTEXT name — the guard behind `RequestContext.get/put(name: string)`.\n * Those raw string accessors would otherwise be a side door around the typed trust verbs\n * (`RequestContext.get('userId')` reads a trusted value without ever saying `getTrusted`, and\n * `put('userId', ...)` forges one without saying `putTrusted`), so they reject any name that\n * belongs to a registered key and point the caller at the verb to use instead.\n *\n * Linear over the key list, which is fine: this runs only on the raw string path, and the\n * reserved framework keys that legitimately use it ('__webpieces_*__') are a handful.\n */\n findByName(name: string): AnyContextKey | undefined {\n const lower = name.toLowerCase();\n return this.keys.find((k: AnyContextKey) => k.name.toLowerCase() === lower);\n }\n\n /** Look up a key by its HTTP header name (case-insensitive). O(1) via the precomputed map. */\n findByHttpHeader(httpHeader: string): AnyContextKey | undefined {\n return this.byHttpHeader.get(httpHeader.toLowerCase());\n }\n\n /**\n * Collapse exact duplicates, throw on conflicting definitions sharing a `name`\n * or an `httpHeader`.\n */\n private checkForDuplicates(allKeys: AnyContextKey[]): AnyContextKey[] {\n const byName = new Map<string, AnyContextKey>();\n const byHttpHeader = new Map<string, AnyContextKey>();\n\n for (const key of allKeys) {\n const nameKey = key.name.toLowerCase();\n const existing = byName.get(nameKey);\n if (existing) {\n this.assertSameDefinition(existing, key);\n continue; // exact duplicate - collapse\n }\n byName.set(nameKey, key);\n\n if (key.httpHeader !== undefined) {\n const headerKey = key.httpHeader.toLowerCase();\n const clash = byHttpHeader.get(headerKey);\n if (clash) {\n throw new Error(\n `Duplicate ContextKey httpHeader '${key.httpHeader}': ` +\n `defined by key '${clash.name}' AND key '${key.name}'. ` +\n `Each HTTP header must map to exactly one context key.`,\n );\n }\n byHttpHeader.set(headerKey, key);\n }\n }\n\n return Array.from(byName.values());\n }\n\n /**\n * Two keys sharing a `name` must agree on httpHeader/trust/maskInLogs/isLogged,\n * otherwise the platform would behave differently depending on which module's\n * definition happened to load first.\n */\n private assertSameDefinition(existing: AnyContextKey, duplicate: AnyContextKey): void {\n const conflicts: string[] = [];\n if (existing.httpHeader !== duplicate.httpHeader) {\n conflicts.push(`httpHeader ('${existing.httpHeader}' vs '${duplicate.httpHeader}')`);\n }\n if (existing.trust !== duplicate.trust) {\n // The most dangerous disagreement of the three: one module says this key is a proven\n // fact and another says it is caller-asserted. Whichever loaded first would silently\n // decide whether every reader of that key is doing an authorization check on a\n // spoofable value, so it fails the whole startup instead.\n conflicts.push(`trust ('${existing.trust}' vs '${duplicate.trust}')`);\n }\n if (existing.maskInLogs !== duplicate.maskInLogs) {\n conflicts.push(`maskInLogs (${existing.maskInLogs} vs ${duplicate.maskInLogs})`);\n }\n if (existing.isLogged !== duplicate.isLogged) {\n conflicts.push(`isLogged (${existing.isLogged} vs ${duplicate.isLogged})`);\n }\n if (conflicts.length > 0) {\n throw new Error(\n `Conflicting ContextKey definitions for '${existing.name}': ` +\n `two modules registered it with different ${conflicts.join(', ')}. ` +\n `Keys sharing a name must agree on all flags.`,\n );\n }\n }\n}\n"]}
|
|
@@ -21,7 +21,7 @@ export declare class WebpiecesCoreHeaders {
|
|
|
21
21
|
* The id that correlates every hop of one request, and every log line of every hop.
|
|
22
22
|
* Generated by the first service to see a request without one; propagated unchanged after that.
|
|
23
23
|
*/
|
|
24
|
-
static readonly REQUEST_ID: ContextKey<string>;
|
|
24
|
+
static readonly REQUEST_ID: ContextKey<string, "untrusted">;
|
|
25
25
|
/**
|
|
26
26
|
* WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by
|
|
27
27
|
* `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when
|
|
@@ -36,7 +36,7 @@ export declare class WebpiecesCoreHeaders {
|
|
|
36
36
|
* the signal: present == I am the origin.
|
|
37
37
|
* - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.
|
|
38
38
|
*/
|
|
39
|
-
static readonly REQUEST_ID_SOURCE: ContextKey<string>;
|
|
39
|
+
static readonly REQUEST_ID_SOURCE: ContextKey<string, "untrusted">;
|
|
40
40
|
/**
|
|
41
41
|
* The CALLER's build version — so a downstream server's logs record which build of the client
|
|
42
42
|
* called it (surfaces as `jsonPayload.clientVersion`). Distinct from the log line's own `version`
|
|
@@ -50,7 +50,7 @@ export declare class WebpiecesCoreHeaders {
|
|
|
50
50
|
* - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log
|
|
51
51
|
* field map; no backend change needed.
|
|
52
52
|
*/
|
|
53
|
-
static readonly CLIENT_VERSION: ContextKey<string>;
|
|
53
|
+
static readonly CLIENT_VERSION: ContextKey<string, "untrusted">;
|
|
54
54
|
/**
|
|
55
55
|
* A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.
|
|
56
56
|
*
|
|
@@ -74,15 +74,34 @@ export declare class WebpiecesCoreHeaders {
|
|
|
74
74
|
* outbound hops, so the id follows the action across services.
|
|
75
75
|
* - `isLogged` TRUE → emitted as a plain string on every log line of the request.
|
|
76
76
|
*/
|
|
77
|
-
static readonly ACTION_ID: ContextKey<string>;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
77
|
+
static readonly ACTION_ID: ContextKey<string, "untrusted">;
|
|
78
|
+
/**
|
|
79
|
+
* WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the
|
|
80
|
+
* inputs to authorization decisions, so a reader must be able to tell "the framework proved
|
|
81
|
+
* this" from "the caller typed this" — see the trust section of the {@link ContextKey} doc.
|
|
82
|
+
*
|
|
83
|
+
* They keep their `httpHeader`, because propagating a verified identity to the next internal
|
|
84
|
+
* service is the point. What makes that safe is not the header being absent, it is WHO is
|
|
85
|
+
* allowed to have set it: an inbound value is held PENDING by
|
|
86
|
+
* `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that
|
|
87
|
+
* verified its CALLER (`@AuthOidc` / `@AuthSharedSecret`). On a browser-reachable route
|
|
88
|
+
* (`@AuthJwt` / public) the value must match what the authenticator itself derived, or the
|
|
89
|
+
* request is rejected.
|
|
90
|
+
*
|
|
91
|
+
* `provenance` says "an app-bound JwtHook" rather than naming one hook, because the framework
|
|
92
|
+
* default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that
|
|
93
|
+
* returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's
|
|
94
|
+
* hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud
|
|
95
|
+
* outcome: nothing is vouching for it.
|
|
96
|
+
*/
|
|
97
|
+
static readonly ORG_ID: ContextKey<string, "trusted">;
|
|
98
|
+
static readonly USER_ID: ContextKey<string, "trusted">;
|
|
99
|
+
static readonly USER_ROLES: ContextKey<string, "trusted">;
|
|
81
100
|
/**
|
|
82
101
|
* Turns on test-case recording for this request (Java: x-webpieces-recording).
|
|
83
102
|
* Transferred so recording follows the request across service hops.
|
|
84
103
|
*/
|
|
85
|
-
static readonly RECORDING: ContextKey<string>;
|
|
104
|
+
static readonly RECORDING: ContextKey<string, "untrusted">;
|
|
86
105
|
/**
|
|
87
106
|
* The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCall} around every
|
|
88
107
|
* outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted
|
|
@@ -95,7 +114,7 @@ export declare class WebpiecesCoreHeaders {
|
|
|
95
114
|
* read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat
|
|
96
115
|
* `buildLogFields()` string map deliberately skips it (typeof-string guard).
|
|
97
116
|
*/
|
|
98
|
-
static readonly API_CALL_INFO: ContextKey<ApiCallInfo>;
|
|
117
|
+
static readonly API_CALL_INFO: ContextKey<ApiCallInfo, "untrusted">;
|
|
99
118
|
/**
|
|
100
119
|
* The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by
|
|
101
120
|
* `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels
|
|
@@ -107,8 +126,8 @@ export declare class WebpiecesCoreHeaders {
|
|
|
107
126
|
* method/path, never the caller's. Outbound client calls never set these (no inbound path).
|
|
108
127
|
* - `isLogged` TRUE → emitted by the logging backends as plain strings.
|
|
109
128
|
*/
|
|
110
|
-
static readonly HTTP_METHOD: ContextKey<string>;
|
|
111
|
-
static readonly REQUEST_PATH: ContextKey<string>;
|
|
129
|
+
static readonly HTTP_METHOD: ContextKey<string, "untrusted">;
|
|
130
|
+
static readonly REQUEST_PATH: ContextKey<string, "untrusted">;
|
|
112
131
|
/**
|
|
113
132
|
* The routed endpoint's IMPLEMENTATION identity: the concrete controller class name
|
|
114
133
|
* ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME
|
|
@@ -127,8 +146,8 @@ export declare class WebpiecesCoreHeaders {
|
|
|
127
146
|
* - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.
|
|
128
147
|
* - `isLogged` TRUE → emitted by the logging backends as plain strings.
|
|
129
148
|
*/
|
|
130
|
-
static readonly CONTROLLER: ContextKey<string>;
|
|
131
|
-
static readonly METHOD: ContextKey<string>;
|
|
149
|
+
static readonly CONTROLLER: ContextKey<string, "untrusted">;
|
|
150
|
+
static readonly METHOD: ContextKey<string, "untrusted">;
|
|
132
151
|
/**
|
|
133
152
|
* NO CREDENTIAL KEYS LIVE HERE.
|
|
134
153
|
*
|
|
@@ -23,7 +23,7 @@ class WebpiecesCoreHeaders {
|
|
|
23
23
|
* The id that correlates every hop of one request, and every log line of every hop.
|
|
24
24
|
* Generated by the first service to see a request without one; propagated unchanged after that.
|
|
25
25
|
*/
|
|
26
|
-
static REQUEST_ID =
|
|
26
|
+
static REQUEST_ID = ContextKey_1.ContextKey.untrusted('requestId', 'x-request-id');
|
|
27
27
|
/**
|
|
28
28
|
* WHICH SERVICE MINTED {@link REQUEST_ID} — the name from {@link ServiceInfo}, stamped by
|
|
29
29
|
* `RequestContextHeaders.fillFromRequest` ONLY on the branch that generates a new id (i.e. when
|
|
@@ -38,7 +38,7 @@ class WebpiecesCoreHeaders {
|
|
|
38
38
|
* the signal: present == I am the origin.
|
|
39
39
|
* - `isLogged` TRUE → emitted as a plain string at `jsonPayload.requestIdSource`.
|
|
40
40
|
*/
|
|
41
|
-
static REQUEST_ID_SOURCE =
|
|
41
|
+
static REQUEST_ID_SOURCE = ContextKey_1.ContextKey.untrusted('requestIdSource',
|
|
42
42
|
/*httpHeader*/ undefined);
|
|
43
43
|
/**
|
|
44
44
|
* The CALLER's build version — so a downstream server's logs record which build of the client
|
|
@@ -53,7 +53,7 @@ class WebpiecesCoreHeaders {
|
|
|
53
53
|
* - `isLogged` TRUE → the inbound value lands in the context and flows through the normal log
|
|
54
54
|
* field map; no backend change needed.
|
|
55
55
|
*/
|
|
56
|
-
static CLIENT_VERSION =
|
|
56
|
+
static CLIENT_VERSION = ContextKey_1.ContextKey.untrusted('clientVersion', 'x-webpieces-client-version');
|
|
57
57
|
/**
|
|
58
58
|
* A frontend/app-minted correlation id that groups every request triggered by ONE user ACTION.
|
|
59
59
|
*
|
|
@@ -77,15 +77,34 @@ class WebpiecesCoreHeaders {
|
|
|
77
77
|
* outbound hops, so the id follows the action across services.
|
|
78
78
|
* - `isLogged` TRUE → emitted as a plain string on every log line of the request.
|
|
79
79
|
*/
|
|
80
|
-
static ACTION_ID =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
static ACTION_ID = ContextKey_1.ContextKey.untrusted('actionId', 'x-webpieces-actionid');
|
|
81
|
+
/**
|
|
82
|
+
* WHO the request is acting as, and WHAT they may do. All three are TRUSTED keys: they are the
|
|
83
|
+
* inputs to authorization decisions, so a reader must be able to tell "the framework proved
|
|
84
|
+
* this" from "the caller typed this" — see the trust section of the {@link ContextKey} doc.
|
|
85
|
+
*
|
|
86
|
+
* They keep their `httpHeader`, because propagating a verified identity to the next internal
|
|
87
|
+
* service is the point. What makes that safe is not the header being absent, it is WHO is
|
|
88
|
+
* allowed to have set it: an inbound value is held PENDING by
|
|
89
|
+
* `RequestContextHeaders.fillFromRequest` and admitted by `AuthFilter` only on a route that
|
|
90
|
+
* verified its CALLER (`@AuthOidc` / `@AuthSharedSecret`). On a browser-reachable route
|
|
91
|
+
* (`@AuthJwt` / public) the value must match what the authenticator itself derived, or the
|
|
92
|
+
* request is rejected.
|
|
93
|
+
*
|
|
94
|
+
* `provenance` says "an app-bound JwtHook" rather than naming one hook, because the framework
|
|
95
|
+
* default ({@link DefaultJwtHook}) stamps NO context entries at all — an app supplies a hook that
|
|
96
|
+
* returns {@link ContextTuple}s for the keys it can vouch for. Any of these three that an app's
|
|
97
|
+
* hook does NOT stamp will be rejected when a caller supplies it, which is the correct and loud
|
|
98
|
+
* outcome: nothing is vouching for it.
|
|
99
|
+
*/
|
|
100
|
+
static ORG_ID = ContextKey_1.ContextKey.trusted('orgId', 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthValues)', 'x-org-id');
|
|
101
|
+
static USER_ID = ContextKey_1.ContextKey.trusted('userId', 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthValues)', 'x-user-id');
|
|
102
|
+
static USER_ROLES = ContextKey_1.ContextKey.trusted('roles', 'derived from a verified credential by an app-bound JwtHook (a ContextTuple in AuthValues)', 'x-webpieces-roles');
|
|
84
103
|
/**
|
|
85
104
|
* Turns on test-case recording for this request (Java: x-webpieces-recording).
|
|
86
105
|
* Transferred so recording follows the request across service hops.
|
|
87
106
|
*/
|
|
88
|
-
static RECORDING =
|
|
107
|
+
static RECORDING = ContextKey_1.ContextKey.untrusted('recording', 'x-webpieces-recording');
|
|
89
108
|
/**
|
|
90
109
|
* The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCall} around every
|
|
91
110
|
* outbound (client) / inbound (server) call. It rides the magic context so EVERY log line emitted
|
|
@@ -98,7 +117,7 @@ class WebpiecesCoreHeaders {
|
|
|
98
117
|
* read it via {@link HeaderRegistry.buildStructuredLogFields} (object-aware); the flat
|
|
99
118
|
* `buildLogFields()` string map deliberately skips it (typeof-string guard).
|
|
100
119
|
*/
|
|
101
|
-
static API_CALL_INFO =
|
|
120
|
+
static API_CALL_INFO = ContextKey_1.ContextKey.untrusted('api', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);
|
|
102
121
|
/**
|
|
103
122
|
* The inbound request's HTTP method and path, stamped ONCE from the {@link HttpRequest} by
|
|
104
123
|
* `RequestContextHeaders.fillFromRequest` (the atomic inbound choke point every transport funnels
|
|
@@ -110,8 +129,8 @@ class WebpiecesCoreHeaders {
|
|
|
110
129
|
* method/path, never the caller's. Outbound client calls never set these (no inbound path).
|
|
111
130
|
* - `isLogged` TRUE → emitted by the logging backends as plain strings.
|
|
112
131
|
*/
|
|
113
|
-
static HTTP_METHOD =
|
|
114
|
-
static REQUEST_PATH =
|
|
132
|
+
static HTTP_METHOD = ContextKey_1.ContextKey.untrusted('httpMethod', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);
|
|
133
|
+
static REQUEST_PATH = ContextKey_1.ContextKey.untrusted('requestPath', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);
|
|
115
134
|
/**
|
|
116
135
|
* The routed endpoint's IMPLEMENTATION identity: the concrete controller class name
|
|
117
136
|
* ({@link RouteMetadata.controllerClassName}, e.g. `LoginController`) and the handler method NAME
|
|
@@ -130,8 +149,8 @@ class WebpiecesCoreHeaders {
|
|
|
130
149
|
* - `httpHeader` UNDEFINED → NOT transferred: each hop stamps its OWN routed controller/method.
|
|
131
150
|
* - `isLogged` TRUE → emitted by the logging backends as plain strings.
|
|
132
151
|
*/
|
|
133
|
-
static CONTROLLER =
|
|
134
|
-
static METHOD =
|
|
152
|
+
static CONTROLLER = ContextKey_1.ContextKey.untrusted('controller', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);
|
|
153
|
+
static METHOD = ContextKey_1.ContextKey.untrusted('method', /*httpHeader*/ undefined, /*maskInLogs*/ false, /*isLogged*/ true);
|
|
135
154
|
/**
|
|
136
155
|
* NO CREDENTIAL KEYS LIVE HERE.
|
|
137
156
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA0D;AAG1D;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;OAGG;IACH,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAS,WAAW,EAAE,cAAc,CAAC,CAAC;IAEjF;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAU,iBAAiB,GAAG,IAAI,uBAAU,CAC9C,iBAAiB;IACjB,cAAc,CAAC,SAAS,CAC3B,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,CAAU,cAAc,GAAG,IAAI,uBAAU,CAAS,eAAe,EAAE,4BAA4B,CAAC,CAAC;IAEvG;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAU,SAAS,GAAG,IAAI,uBAAU,CAAS,UAAU,EAAE,sBAAsB,CAAC,CAAC;IAEvF,MAAM,CAAU,MAAM,GAAG,IAAI,uBAAU,CAAS,OAAO,EAAE,UAAU,CAAC,CAAC;IAErE,MAAM,CAAU,OAAO,GAAG,IAAI,uBAAU,CAAS,QAAQ,EAAE,WAAW,CAAC,CAAC;IAExE,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAS,OAAO,EAAE,mBAAmB,CAAC,CAAC;IAElF;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,IAAI,uBAAU,CAAS,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAEzF;;;;;;;;;;;OAWG;IACH,MAAM,CAAU,aAAa,GAAG,IAAI,uBAAU,CAAc,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAErI;;;;;;;;;;OAUG;IACH,MAAM,CAAU,WAAW,GAAG,IAAI,uBAAU,CAAS,YAAY,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAErI,MAAM,CAAU,YAAY,GAAG,IAAI,uBAAU,CAAS,aAAa,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAEvI;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAU,UAAU,GAAG,IAAI,uBAAU,CAAS,YAAY,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAEpI,MAAM,CAAU,MAAM,GAAG,IAAI,uBAAU,CAAS,QAAQ,EAAE,cAAc,CAAC,SAAS,EAAE,aAAa,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE5H;;;;;;;;;;;;;;;OAeG;IAEH;;;;;OAKG;IACH,MAAM,CAAU,WAAW,GAAoB;QAC3C,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,iBAAiB;QACtC,oBAAoB,CAAC,cAAc;QACnC,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,OAAO;QAC5B,oBAAoB,CAAC,MAAM;QAC3B,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,aAAa;QAClC,oBAAoB,CAAC,WAAW;QAChC,oBAAoB,CAAC,YAAY;QACjC,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,MAAM;KAC9B,CAAC;;AAtKN,oDAuKC","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 = new ContextKey<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 = new ContextKey<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 = new ContextKey<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 = new ContextKey<string>('actionId', 'x-webpieces-actionid');\n\n static readonly ORG_ID = new ContextKey<string>('orgId', 'x-org-id');\n\n static readonly USER_ID = new ContextKey<string>('userId', 'x-user-id');\n\n static readonly USER_ROLES = new ContextKey<string>('roles', 'x-webpieces-roles');\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 = new ContextKey<string>('recording', 'x-webpieces-recording');\n\n /**\n * The structured API-call tag ({@link ApiCallInfo}) stamped by {@link LogApiCall} 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 = new ContextKey<ApiCallInfo>('api', /*httpHeader*/ undefined, /*isSecured*/ 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 = new ContextKey<string>('httpMethod', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);\n\n static readonly REQUEST_PATH = new ContextKey<string>('requestPath', /*httpHeader*/ undefined, /*isSecured*/ 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 = new ContextKey<string>('controller', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);\n\n static readonly METHOD = new ContextKey<string>('method', /*httpHeader*/ undefined, /*isSecured*/ false, /*isLogged*/ true);\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 ];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"WebpiecesCoreHeaders.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/WebpiecesCoreHeaders.ts"],"names":[],"mappings":";;;AAAA,8CAA0D;AAG1D;;;;;;;;;;;;;;;GAeG;AACH,MAAa,oBAAoB;IAC7B;;;OAGG;IACH,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,SAAS,CAAS,WAAW,EAAE,cAAc,CAAC,CAAC;IAEvF;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAU,iBAAiB,GAAG,uBAAU,CAAC,SAAS,CACpD,iBAAiB;IACjB,cAAc,CAAC,SAAS,CAC3B,CAAC;IAEF;;;;;;;;;;;;OAYG;IACH,MAAM,CAAU,cAAc,GAAG,uBAAU,CAAC,SAAS,CAAS,eAAe,EAAE,4BAA4B,CAAC,CAAC;IAE7G;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAU,SAAS,GAAG,uBAAU,CAAC,SAAS,CAAS,UAAU,EAAE,sBAAsB,CAAC,CAAC;IAE7F;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,OAAO,CACvC,OAAO,EACP,2FAA2F,EAC3F,UAAU,CACb,CAAC;IAEF,MAAM,CAAU,OAAO,GAAG,uBAAU,CAAC,OAAO,CACxC,QAAQ,EACR,2FAA2F,EAC3F,WAAW,CACd,CAAC;IAEF,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,OAAO,CAC3C,OAAO,EACP,2FAA2F,EAC3F,mBAAmB,CACtB,CAAC;IAEF;;;OAGG;IACH,MAAM,CAAU,SAAS,GAAG,uBAAU,CAAC,SAAS,CAAS,WAAW,EAAE,uBAAuB,CAAC,CAAC;IAE/F;;;;;;;;;;;OAWG;IACH,MAAM,CAAU,aAAa,GAAG,uBAAU,CAAC,SAAS,CAAc,KAAK,EAAE,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE5I;;;;;;;;;;OAUG;IACH,MAAM,CAAU,WAAW,GAAG,uBAAU,CAAC,SAAS,CAAS,YAAY,EAAE,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE5I,MAAM,CAAU,YAAY,GAAG,uBAAU,CAAC,SAAS,CAAS,aAAa,EAAE,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE9I;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAU,UAAU,GAAG,uBAAU,CAAC,SAAS,CAAS,YAAY,EAAE,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAE3I,MAAM,CAAU,MAAM,GAAG,uBAAU,CAAC,SAAS,CAAS,QAAQ,EAAE,cAAc,CAAC,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IAEnI;;;;;;;;;;;;;;;OAeG;IAEH;;;;;OAKG;IACH,MAAM,CAAU,WAAW,GAAoB;QAC3C,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,iBAAiB;QACtC,oBAAoB,CAAC,cAAc;QACnC,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,OAAO;QAC5B,oBAAoB,CAAC,MAAM;QAC3B,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,SAAS;QAC9B,oBAAoB,CAAC,aAAa;QAClC,oBAAoB,CAAC,WAAW;QAChC,oBAAoB,CAAC,YAAY;QACjC,oBAAoB,CAAC,UAAU;QAC/B,oBAAoB,CAAC,MAAM;KAC9B,CAAC;;AArMN,oDAsMC","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 AuthValues)',\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 AuthValues)',\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 AuthValues)',\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 LogApiCall} 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 * 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 ];\n}\n"]}
|
|
@@ -32,5 +32,5 @@ export declare class RecorderKeys {
|
|
|
32
32
|
* Key under which the active TestCaseRecorder travels in the request
|
|
33
33
|
* context. Absent = not recording.
|
|
34
34
|
*/
|
|
35
|
-
static readonly RECORDER: ContextKey<TestCaseRecorder>;
|
|
35
|
+
static readonly RECORDER: ContextKey<TestCaseRecorder, "untrusted">;
|
|
36
36
|
}
|
|
@@ -10,7 +10,7 @@ class RecorderKeys {
|
|
|
10
10
|
* Key under which the active TestCaseRecorder travels in the request
|
|
11
11
|
* context. Absent = not recording.
|
|
12
12
|
*/
|
|
13
|
-
static RECORDER =
|
|
13
|
+
static RECORDER = ContextKey_1.ContextKey.untrusted('webpieces-recorder', undefined, false, /*isLogged*/ false);
|
|
14
14
|
}
|
|
15
15
|
exports.RecorderKeys = RecorderKeys;
|
|
16
16
|
//# sourceMappingURL=TestCaseRecorder.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TestCaseRecorder.js","sourceRoot":"","sources":["../../../../../../../packages/core/core-util/src/http/recorder/TestCaseRecorder.ts"],"names":[],"mappings":";;;AAAA,iDAA8C;AA6B9C;;GAEG;AACH,MAAa,YAAY;IACrB;;;OAGG;IACH,MAAM,CAAU,QAAQ,GAAG,
|
|
1
|
+
{"version":3,"file":"TestCaseRecorder.js","sourceRoot":"","sources":["../../../../../../../packages/core/core-util/src/http/recorder/TestCaseRecorder.ts"],"names":[],"mappings":";;;AAAA,iDAA8C;AA6B9C;;GAEG;AACH,MAAa,YAAY;IACrB;;;OAGG;IACH,MAAM,CAAU,QAAQ,GAAG,uBAAU,CAAC,SAAS,CAAmB,oBAAoB,EAAE,SAAS,EAAE,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;;AALlI,oCAMC","sourcesContent":["import { ContextKey } from '../../ContextKey';\nimport { RecordedEndpoint } from './RecordedEndpoint';\n\n/**\n * TestCaseRecorder - Records every api call made while serving one inbound\n * request (port of Java TestCaseRecorder).\n *\n * The recorder travels in the request's magic context under\n * RecorderKeys.RECORDER (Java: Context RECORDER_KEY). Downstream hooks -\n * the HTTP client proxy and recordable() in-process wrappers - check the\n * context and record into it when present.\n *\n * The contract lives in http-api (browser-safe, no Node imports) so the\n * http-client can reference it; the implementation (TestCaseRecorderImpl)\n * lives in http-server.\n */\nexport interface TestCaseRecorder {\n /**\n * Record one downstream api call (outbound HTTP or in-process recordable).\n */\n addEndpointInfo(info: RecordedEndpoint): void;\n\n /**\n * The most recently recorded downstream call (for hooks that fill in the\n * response after the call completes).\n */\n getLastEndpointInfo(): RecordedEndpoint | undefined;\n}\n\n/**\n * Context keys for the recording subsystem.\n */\nexport class RecorderKeys {\n /**\n * Key under which the active TestCaseRecorder travels in the request\n * context. Absent = not recording.\n */\n static readonly RECORDER = ContextKey.untrusted<TestCaseRecorder>('webpieces-recorder', undefined, false, /*isLogged*/ false);\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export { toError } from './lib/errorUtils';
|
|
10
10
|
export { ContextKey } from './ContextKey';
|
|
11
|
-
export type { AnyContextKey } from './ContextKey';
|
|
11
|
+
export type { AnyContextKey, AnyTrustedContextKey, AnyUntrustedContextKey, Trust } from './ContextKey';
|
|
12
12
|
export { ContextTuple } from './ContextTuple';
|
|
13
13
|
export { DocumentDesign, isDocumentDesign, DESIGN_METADATA_KEYS } from './DocumentDesign';
|
|
14
14
|
export type { Logger, LogLevel } from './logging/Logger';
|