@webpieces/http-routing 0.4.966 → 0.4.968
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 +7 -7
- package/src/AuthConfig.d.ts +2 -1
- package/src/AuthConfig.js +2 -1
- package/src/AuthConfig.js.map +1 -1
- package/src/AuthHooks.d.ts +2 -2
- package/src/AuthHooks.js +2 -2
- package/src/AuthHooks.js.map +1 -1
- package/src/AuthHooksCompileAssertions.js +10 -0
- package/src/AuthHooksCompileAssertions.js.map +1 -1
- package/src/RouteBuilderImpl.d.ts +12 -0
- package/src/RouteBuilderImpl.js +20 -0
- package/src/RouteBuilderImpl.js.map +1 -1
- package/src/UnverifiableAuthModeError.d.ts +20 -0
- package/src/UnverifiableAuthModeError.js +28 -0
- package/src/UnverifiableAuthModeError.js.map +1 -0
- package/src/filters/AuthFilter.d.ts +48 -21
- package/src/filters/AuthFilter.js +123 -43
- package/src/filters/AuthFilter.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +6 -3
- package/src/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-routing",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.968",
|
|
4
4
|
"description": "Decorator-based routing with auto-wiring for WebPieces",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
25
|
-
"@webpieces/core-context": "0.4.
|
|
26
|
-
"@webpieces/core-util": "0.4.
|
|
27
|
-
"@webpieces/gcp-identity": "0.4.
|
|
25
|
+
"@webpieces/core-context": "0.4.968",
|
|
26
|
+
"@webpieces/core-util": "0.4.968",
|
|
27
|
+
"@webpieces/gcp-identity": "0.4.968",
|
|
28
28
|
"inversify": "7.10.4",
|
|
29
29
|
"jsonwebtoken": "9.0.2",
|
|
30
30
|
"minimatch": "10.0.1",
|
|
31
|
-
"@webpieces/http-client-core": "0.4.
|
|
32
|
-
"@webpieces/http-client-node": "0.4.
|
|
33
|
-
"@webpieces/cloudtasks-client": "0.4.
|
|
31
|
+
"@webpieces/http-client-core": "0.4.968",
|
|
32
|
+
"@webpieces/http-client-node": "0.4.968",
|
|
33
|
+
"@webpieces/cloudtasks-client": "0.4.968"
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/AuthConfig.d.ts
CHANGED
|
@@ -46,7 +46,8 @@ export declare const AUTHENTICATED_CALLER_KEY: ContextKey<AuthenticatedCaller, "
|
|
|
46
46
|
*
|
|
47
47
|
* So a zero-wiring server accepts service-to-service OIDC out of the box, and an app only binds the
|
|
48
48
|
* pieces it actually uses. This class is injected `@optional` into AuthFilter (rebindable in tests);
|
|
49
|
-
*
|
|
49
|
+
* a server SERVING a `sharedSecret(key)` endpoint with no AuthConfig, or one not holding `key`, refuses
|
|
50
|
+
* to boot (#1362, `AuthFilter.assertRouteVerifiable`).
|
|
50
51
|
*/
|
|
51
52
|
export declare class AuthConfig {
|
|
52
53
|
/** Accepted shared-secret values keyed by `sharedSecret(name)`. DEFAULT empty — pass to enable. */
|
package/src/AuthConfig.js
CHANGED
|
@@ -60,7 +60,8 @@ exports.AUTHENTICATED_CALLER_KEY = core_util_1.ContextKey.trusted('authenticated
|
|
|
60
60
|
*
|
|
61
61
|
* So a zero-wiring server accepts service-to-service OIDC out of the box, and an app only binds the
|
|
62
62
|
* pieces it actually uses. This class is injected `@optional` into AuthFilter (rebindable in tests);
|
|
63
|
-
*
|
|
63
|
+
* a server SERVING a `sharedSecret(key)` endpoint with no AuthConfig, or one not holding `key`, refuses
|
|
64
|
+
* to boot (#1362, `AuthFilter.assertRouteVerifiable`).
|
|
64
65
|
*/
|
|
65
66
|
class AuthConfig {
|
|
66
67
|
/** Accepted shared-secret values keyed by `sharedSecret(name)`. DEFAULT empty — pass to enable. */
|
package/src/AuthConfig.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthConfig.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthConfig.ts"],"names":[],"mappings":";;;AAAA,oDAAkD;AAGlD,sGAAsG;AACtG,qGAAqG;AACrG,0DAA0D;AAC1D,wDAA4F;AAAnF,mHAAA,mBAAmB,OAAA;AAAE,4HAAA,4BAA4B,OAAA;AAE1D;;;;;;;;;GASG;AACH,MAAa,aAAa;IAEF;IACA;IAFpB,YACoB,OAAe,EACf,OAAe;QADf,YAAO,GAAP,OAAO,CAAQ;QACf,YAAO,GAAP,OAAO,CAAQ;IAChC,CAAC;CACP;AALD,sCAKC;AAED;;;;;;;;;;;;;;;GAeG;AACU,QAAA,wBAAwB,GAAG,sBAAU,CAAC,OAAO,CACtD,qBAAqB,EACrB,wHAAwH;AACxH,cAAc,CAAC,SAAS;AACxB,cAAc,CAAC,KAAK;AACpB,YAAY,CAAC,KAAK,CACrB,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"AuthConfig.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthConfig.ts"],"names":[],"mappings":";;;AAAA,oDAAkD;AAGlD,sGAAsG;AACtG,qGAAqG;AACrG,0DAA0D;AAC1D,wDAA4F;AAAnF,mHAAA,mBAAmB,OAAA;AAAE,4HAAA,4BAA4B,OAAA;AAE1D;;;;;;;;;GASG;AACH,MAAa,aAAa;IAEF;IACA;IAFpB,YACoB,OAAe,EACf,OAAe;QADf,YAAO,GAAP,OAAO,CAAQ;QACf,YAAO,GAAP,OAAO,CAAQ;IAChC,CAAC;CACP;AALD,sCAKC;AAED;;;;;;;;;;;;;;;GAeG;AACU,QAAA,wBAAwB,GAAG,sBAAU,CAAC,OAAO,CACtD,qBAAqB,EACrB,wHAAwH;AACxH,cAAc,CAAC,SAAS;AACxB,cAAc,CAAC,KAAK;AACpB,YAAY,CAAC,KAAK,CACrB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,MAAa,UAAU;IACnB,mGAAmG;IAC1F,aAAa,CAAgC;IAEtD,YAAY,gBAA+C,EAAE;QACzD,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACvC,CAAC;CACJ;AAPD,gCAOC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { ContextKey } from '@webpieces/core-util';\nimport { AuthenticatedCaller } from '@webpieces/core-context';\n\n// Defined in @webpieces/core-context (beside HttpRequest, which http-routing re-exports the same way)\n// so VendorAuthVerifier (defined there beside its outbound twin VendorAuthMinter) can name it from a\n// package both the node client and this router depend on.\nexport { AuthenticatedCaller, AuthenticatedMachineIdentity } from '@webpieces/core-context';\n\n/**\n * SharedSecrets - the accepted values for ONE `sharedSecret(name)`. BOTH secret1 AND secret2\n * are accepted — this is what makes zero-downtime ROTATION possible:\n *\n * to rotate: shift secret2 → secret1, and put the NEW secret in secret2. Callers cut over from\n * the old value to the new during the window; once every caller sends the new one, the stale\n * value falls out on the next shift. At all times EITHER key works, so no request is dropped.\n *\n * Data-only structure (a class, per the guidelines). Leave secret2 empty for a single secret.\n */\nexport class SharedSecrets {\n constructor(\n public readonly secret1: string,\n public readonly secret2: string,\n ) {}\n}\n\n/**\n * The context slot holding the {@link AuthenticatedCaller} the framework {@link AuthFilter} resolved\n * for this request. A real TRUSTED {@link ContextKey}, written with `RequestContext.putTrusted` and\n * read with `RequestContext.getTrusted` — it replaces a raw `'__webpieces_principal__'` string that\n * was the one place in the codebase bypassing the typed context layer.\n *\n * `httpHeader` is deliberately UNDEFINED, so the key is context-only and NEVER travels. Two reasons,\n * and either alone is decisive: the value is an OBJECT, which no HTTP header can carry; and a\n * principal is proof THIS hop's authenticator produced, so forwarding it would hand the next service\n * a \"proven\" caller nothing on that hop verified. The individual facts a downstream service needs\n * (userId, orgId, roles) already propagate as their own transferred keys in\n * {@link WebpiecesCoreHeaders}, gated by the caller-verified rule.\n *\n * `isLogged` is FALSE: it is an object carrying the raw credential claims, which has no business\n * being serialized into a log line.\n */\nexport const AUTHENTICATED_CALLER_KEY = ContextKey.trusted<AuthenticatedCaller>(\n 'authenticatedCaller',\n 'resolved by the framework AuthFilter from a credential an app-bound JwtHook, ApiKeyHook or VendorAuthVerifier verified',\n /*httpHeader*/ undefined,\n /*maskInLogs*/ false,\n /*isLogged*/ false,\n);\n\n/**\n * AuthConfig - the app-provided SHARED-SECRET state the framework {@link AuthFilter} reads to\n * enforce `sharedSecret(name)` endpoints. It holds ONLY the accepted secret values (STATE) —\n * there is no verification code here. The verification MECHANISMS are separate optional hooks the\n * app binds when it needs them:\n *\n * - user JWT → bind a {@link JwtHook} (async parseJwt).\n * - api key → bind an {@link ApiKeyHook} (async verifyApiKey over the request's headers).\n * - OIDC → bind an {@link OidcHook} to override the framework's default verifier; a server that\n * binds nothing still verifies Google OIDC via the built-in {@link DefaultOidcVerifier}.\n *\n * So a zero-wiring server accepts service-to-service OIDC out of the box, and an app only binds the\n * pieces it actually uses. This class is injected `@optional` into AuthFilter (rebindable in tests);\n * a server SERVING a `sharedSecret(key)` endpoint with no AuthConfig, or one not holding `key`, refuses\n * to boot (#1362, `AuthFilter.assertRouteVerifiable`).\n */\nexport class AuthConfig {\n /** Accepted shared-secret values keyed by `sharedSecret(name)`. DEFAULT empty — pass to enable. */\n readonly sharedSecrets: Record<string, SharedSecrets>;\n\n constructor(sharedSecrets: Record<string, SharedSecrets> = {}) {\n this.sharedSecrets = sharedSecrets;\n }\n}\n\n/**\n * DI identifier for the optional {@link AuthConfig} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(AUTH_CONFIG)`\n * correct — undefined when unbound. The AuthConfig class stays the TYPE and the impl base.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const AUTH_CONFIG = Symbol.for('AuthConfig');\n"]}
|
package/src/AuthHooks.d.ts
CHANGED
|
@@ -52,8 +52,8 @@ export declare const OIDC_HOOK: unique symbol;
|
|
|
52
52
|
* options.bind(API_KEY_HOOK).to(OneTabletApiKeyHook);
|
|
53
53
|
* ```
|
|
54
54
|
*
|
|
55
|
-
* When NO ApiKeyHook is bound,
|
|
56
|
-
* exactly as it does for an unbound JwtHook. There is no framework default and there never will be
|
|
55
|
+
* When NO ApiKeyHook is bound, a server SERVING an `apiKey(...)` endpoint refuses to boot (#1362,
|
|
56
|
+
* `AuthFilter.assertRouteVerifiable`), exactly as it does for an unbound JwtHook. There is no framework default and there never will be
|
|
57
57
|
* one: the key regime lives in the app's datastore, under the app's hashing scheme, behind the app's
|
|
58
58
|
* choice of header names.
|
|
59
59
|
*
|
package/src/AuthHooks.js
CHANGED
|
@@ -62,8 +62,8 @@ exports.OIDC_HOOK = Symbol.for('OidcHook');
|
|
|
62
62
|
* options.bind(API_KEY_HOOK).to(OneTabletApiKeyHook);
|
|
63
63
|
* ```
|
|
64
64
|
*
|
|
65
|
-
* When NO ApiKeyHook is bound,
|
|
66
|
-
* exactly as it does for an unbound JwtHook. There is no framework default and there never will be
|
|
65
|
+
* When NO ApiKeyHook is bound, a server SERVING an `apiKey(...)` endpoint refuses to boot (#1362,
|
|
66
|
+
* `AuthFilter.assertRouteVerifiable`), exactly as it does for an unbound JwtHook. There is no framework default and there never will be
|
|
67
67
|
* one: the key regime lives in the app's datastore, under the app's hashing scheme, behind the app's
|
|
68
68
|
* choice of header names.
|
|
69
69
|
*
|
package/src/AuthHooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthHooks.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthHooks.ts"],"names":[],"mappings":";;;AAIA,gFAAgF;AAChF,MAAa,SAAS;IAEE;IACA;IAFpB,YACoB,KAAa,EACb,qBAA6B;QAD7B,UAAK,GAAL,KAAK,CAAQ;QACb,0BAAqB,GAArB,qBAAqB,CAAQ;QAE7C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC9E,CAAC;IACL,CAAC;CACJ;AAVD,8BAUC;AAED,+FAA+F;AAC/F,MAAsB,OAAO;CAG5B;AAHD,0BAGC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,MAAsB,QAAQ;CAE7B;AAFD,4BAEC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAsB,UAAU;CAiB/B;AAjBD,gCAiBC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { HttpRequest, RawHttpRequest } from '@webpieces/core-context';\nimport { OidcAuthMethods } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthenticatedMachineIdentity } from './AuthConfig';\n\n/** Framework-normalized result from an application-owned JWT mint operation. */\nexport class MintedJwt {\n constructor(\n public readonly token: string,\n public readonly expiresAtEpochSeconds: number,\n ) {\n if (token.trim() === '') throw new Error('Minted JWT token must be non-empty.');\n if (!Number.isFinite(expiresAtEpochSeconds)) {\n throw new Error('Minted JWT expiry must be a finite epoch-second value.');\n }\n }\n}\n\n/** Optional application JWT credential authority. Authorization lives in AuthorizationHook. */\nexport abstract class JwtHook<TMintRequest> {\n abstract mint(request: TMintRequest): Promise<MintedJwt>;\n abstract parseJwt(token: string): Promise<AuthenticatedCaller>;\n}\n\n/**\n * DI identifier for the optional {@link JwtHook} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(JWT_HOOK)`\n * correct — undefined when unbound. The JwtHook class stays the TYPE and the impl base.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const JWT_HOOK = Symbol.for('JwtHook');\n\n/**\n * OidcHook - the OPTIONAL override for Google OIDC service-to-service verification. Its DI token is the\n * {@link OIDC_HOOK} Symbol injected via `@inject(OIDC_HOOK)` (a Symbol, because the app container uses\n * autobind; rebindable in tests). Bind one ONLY to customize the caller policy — e.g. an app that reads an\n * `ALLOWED_OIDC_CALLERS` env var at its composition root and enforces that allow-list. When NO\n * OidcHook is bound, the framework {@link AuthFilter} runs the built-in {@link DefaultOidcVerifier}\n * directly, so a server that wires nothing still verifies Google OIDC against the endpoint's caller\n * scopes. `verifyOidc` receives EVERY OIDC method the endpoint declares ({@link OidcAuthMethods}: one,\n * or one `oidcSameProject()` (`callerScope: 'same-project'`) plus one `oidcSvcs([...])`\n * (`callerScope: 'services'` with its `callers`)), because they are one bearer credential: verify\n * the token once, admit the call when ANY of them admits the caller, and throw otherwise.\n */\nexport abstract class OidcHook {\n abstract verifyOidc(token: string, methods: OidcAuthMethods): Promise<AuthenticatedMachineIdentity>;\n}\n\n/**\n * DI identifier for the optional {@link OidcHook} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(OIDC_HOOK)`\n * correct — undefined when unbound. The OidcHook class stays the TYPE and the impl base.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const OIDC_HOOK = Symbol.for('OidcHook');\n\n/**\n * ApiKeyHook - the OPTIONAL mechanism behind `apiKey(regime, credentials)`: authenticate a\n * CUSTOMER-held api key\n * against the app's own datastore and return the context to seed. Its DI token is the\n * {@link API_KEY_HOOK} Symbol injected via `@inject(API_KEY_HOOK)` (a Symbol, because the app container\n * uses autobind; rebindable in tests). The fourth hook, symmetric with {@link JwtHook} /\n * {@link OidcHook} / `VendorAuthVerifier` (@webpieces/core-context):\n *\n * ```typescript\n * // AppModule.ts, beside the CompanyJwtHook binding\n * options.bind(API_KEY_HOOK).to(OneTabletApiKeyHook);\n * ```\n *\n * When NO ApiKeyHook is bound,
|
|
1
|
+
{"version":3,"file":"AuthHooks.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthHooks.ts"],"names":[],"mappings":";;;AAIA,gFAAgF;AAChF,MAAa,SAAS;IAEE;IACA;IAFpB,YACoB,KAAa,EACb,qBAA6B;QAD7B,UAAK,GAAL,KAAK,CAAQ;QACb,0BAAqB,GAArB,qBAAqB,CAAQ;QAE7C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QAChF,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC9E,CAAC;IACL,CAAC;CACJ;AAVD,8BAUC;AAED,+FAA+F;AAC/F,MAAsB,OAAO;CAG5B;AAHD,0BAGC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAE9C;;;;;;;;;;;GAWG;AACH,MAAsB,QAAQ;CAE7B;AAFD,4BAEC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,SAAS,GAAG,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;AAEhD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAsB,UAAU;CAiB/B;AAjBD,gCAiBC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC","sourcesContent":["import { HttpRequest, RawHttpRequest } from '@webpieces/core-context';\nimport { OidcAuthMethods } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthenticatedMachineIdentity } from './AuthConfig';\n\n/** Framework-normalized result from an application-owned JWT mint operation. */\nexport class MintedJwt {\n constructor(\n public readonly token: string,\n public readonly expiresAtEpochSeconds: number,\n ) {\n if (token.trim() === '') throw new Error('Minted JWT token must be non-empty.');\n if (!Number.isFinite(expiresAtEpochSeconds)) {\n throw new Error('Minted JWT expiry must be a finite epoch-second value.');\n }\n }\n}\n\n/** Optional application JWT credential authority. Authorization lives in AuthorizationHook. */\nexport abstract class JwtHook<TMintRequest> {\n abstract mint(request: TMintRequest): Promise<MintedJwt>;\n abstract parseJwt(token: string): Promise<AuthenticatedCaller>;\n}\n\n/**\n * DI identifier for the optional {@link JwtHook} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(JWT_HOOK)`\n * correct — undefined when unbound. The JwtHook class stays the TYPE and the impl base.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const JWT_HOOK = Symbol.for('JwtHook');\n\n/**\n * OidcHook - the OPTIONAL override for Google OIDC service-to-service verification. Its DI token is the\n * {@link OIDC_HOOK} Symbol injected via `@inject(OIDC_HOOK)` (a Symbol, because the app container uses\n * autobind; rebindable in tests). Bind one ONLY to customize the caller policy — e.g. an app that reads an\n * `ALLOWED_OIDC_CALLERS` env var at its composition root and enforces that allow-list. When NO\n * OidcHook is bound, the framework {@link AuthFilter} runs the built-in {@link DefaultOidcVerifier}\n * directly, so a server that wires nothing still verifies Google OIDC against the endpoint's caller\n * scopes. `verifyOidc` receives EVERY OIDC method the endpoint declares ({@link OidcAuthMethods}: one,\n * or one `oidcSameProject()` (`callerScope: 'same-project'`) plus one `oidcSvcs([...])`\n * (`callerScope: 'services'` with its `callers`)), because they are one bearer credential: verify\n * the token once, admit the call when ANY of them admits the caller, and throw otherwise.\n */\nexport abstract class OidcHook {\n abstract verifyOidc(token: string, methods: OidcAuthMethods): Promise<AuthenticatedMachineIdentity>;\n}\n\n/**\n * DI identifier for the optional {@link OidcHook} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(OIDC_HOOK)`\n * correct — undefined when unbound. The OidcHook class stays the TYPE and the impl base.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const OIDC_HOOK = Symbol.for('OidcHook');\n\n/**\n * ApiKeyHook - the OPTIONAL mechanism behind `apiKey(regime, credentials)`: authenticate a\n * CUSTOMER-held api key\n * against the app's own datastore and return the context to seed. Its DI token is the\n * {@link API_KEY_HOOK} Symbol injected via `@inject(API_KEY_HOOK)` (a Symbol, because the app container\n * uses autobind; rebindable in tests). The fourth hook, symmetric with {@link JwtHook} /\n * {@link OidcHook} / `VendorAuthVerifier` (@webpieces/core-context):\n *\n * ```typescript\n * // AppModule.ts, beside the CompanyJwtHook binding\n * options.bind(API_KEY_HOOK).to(OneTabletApiKeyHook);\n * ```\n *\n * When NO ApiKeyHook is bound, a server SERVING an `apiKey(...)` endpoint refuses to boot (#1362,\n * `AuthFilter.assertRouteVerifiable`), exactly as it does for an unbound JwtHook. There is no framework default and there never will be\n * one: the key regime lives in the app's datastore, under the app's hashing scheme, behind the app's\n * choice of header names.\n *\n * THE ONE THING THIS HAS THAT `JwtHook.parseJwt` DOES NOT: it receives the whole {@link HttpRequest},\n * not one pre-extracted token. A real key regime validates the key TOGETHER WITH a second header — the\n * organization the caller is acting for — and a hook handed one header's value physically cannot do\n * that cross-check. The framework therefore EXTRACTS no api-key header: which headers carry the\n * credential is the app's business, and `getHeader` / `getHeaderValues` read as many as the regime\n * needs. The contract's `credentials` list DECLARES those header names for readers and spec\n * generators — it is documentation of the regime, never an instruction to this hook, so the hook\n * stays the single place the pair is actually validated. (Being ASYNC is no longer a difference — every hook here is, and for the same reason: an\n * app's strategy reaches the network.)\n *\n * ONE hook serves EVERY regime: `regime` selects which, so a server with a partner-api regime and an\n * internal-tooling regime switches on it rather than binding a token per regime.\n */\nexport abstract class ApiKeyHook {\n /**\n * AUTHENTICATE one inbound request. Return who the caller is plus the {@link AuthenticatedCaller.entries}\n * the framework seeds into `RequestContext` via `putTrusted`, or throw\n * {@link ApiUnauthorizedError} to deny.\n *\n * NOTE the seeded entries are TRUSTED context keys, so return only what THIS hook proved from the\n * credential it just verified. Anything the caller merely asserted on the wire is not admitted by\n * `apiKey(...)` — the mode is deliberately caller-NOT-verified (see `AuthFilter.verifiesCaller`),\n * because a customer is not an internal service.\n *\n * @param regime the first argument of the contract's `apiKey(regime, credentials)` — which key\n * regime this route belongs to.\n * @param request the inbound request; read as many headers as the regime needs with\n * `getHeader` / `getHeaderValues`, either by raw name or by {@link ContextKey}.\n */\n abstract verifyApiKey(regime: string, request: HttpRequest): Promise<AuthenticatedCaller>;\n}\n\n/**\n * DI identifier for the optional {@link ApiKeyHook} binding. It is a Symbol (not the class) so the app\n * container's inversify autobind never auto-constructs this token, keeping `@optional() @inject(API_KEY_HOOK)`\n * correct — undefined when unbound. The ApiKeyHook class stays the TYPE and the impl base.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const API_KEY_HOOK = Symbol.for('ApiKeyHook');\n"]}
|
|
@@ -58,6 +58,10 @@ class AuthHooksCompileAssertions {
|
|
|
58
58
|
/** The ALIGNED spellings must keep compiling; asserted by the ABSENCE of an error. */
|
|
59
59
|
legitimate() {
|
|
60
60
|
void class extends core_context_2.VendorAuthVerifier {
|
|
61
|
+
// The vendors it handles, read at route registration so an unhandled name fails boot (#1362).
|
|
62
|
+
supportedVendorNames() {
|
|
63
|
+
return ['sentry'];
|
|
64
|
+
}
|
|
61
65
|
// No meaningless mint()/refresh(): a server-only verifier implements verify and nothing else.
|
|
62
66
|
async verify(_name, request) {
|
|
63
67
|
// No `raw!`, no `if (!raw) throw`: AuthFilter checked once and the TYPE carries it.
|
|
@@ -76,12 +80,18 @@ class AuthHooksCompileAssertions {
|
|
|
76
80
|
/** Every spelling this change deleted must now be UNWRITABLE. */
|
|
77
81
|
rejected() {
|
|
78
82
|
void class extends core_context_2.VendorAuthVerifier {
|
|
83
|
+
supportedVendorNames() {
|
|
84
|
+
return ['sentry'];
|
|
85
|
+
}
|
|
79
86
|
// @ts-expect-error verify takes (name, RawHttpRequest): the three-parameter spelling is gone
|
|
80
87
|
async verify(_name, _request, _raw) {
|
|
81
88
|
return new AuthConfig_1.AuthenticatedCaller('u1');
|
|
82
89
|
}
|
|
83
90
|
};
|
|
84
91
|
void class extends core_context_2.VendorAuthVerifier {
|
|
92
|
+
supportedVendorNames() {
|
|
93
|
+
return ['sentry'];
|
|
94
|
+
}
|
|
85
95
|
// @ts-expect-error verify returns the caller it proved: a void override must not compile
|
|
86
96
|
async verify(_name, _request) {
|
|
87
97
|
// a hook that verifies and then has no way to say what it proved
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthHooksCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthHooksCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpRequest, RawHttpRequest, RawRequest } from '@webpieces/core-context';\n// @ts-expect-error HeaderReader is DELETED — an ApiKeyHook now receives the whole HttpRequest, so it\n// reaches getHeaderValues and the ContextKey overload of getHeader that the narrow reader hid.\nimport type { HeaderReader } from './AuthHooks';\n// @ts-expect-error AuthValues is RENAMED to AuthenticatedCaller — there is no alias, by policy.\nimport type { AuthValues } from './AuthConfig';\nimport { OidcAuthMethod, OidcAuthMethods } from '@webpieces/core-util';\nimport { VendorAuthMinter, VendorAuthVerifier } from '@webpieces/core-context';\n// @ts-expect-error WebhookAuthCallback is REPLACED by VendorAuthVerifier (the inbound role) — no alias, by policy.\nimport type { WebhookAuthCallback } from './AuthHooks';\n// @ts-expect-error WEBHOOK_AUTH_CALLBACK is REPLACED by VENDOR_AUTH_VERIFIER.\nimport { WEBHOOK_AUTH_CALLBACK } from './AuthHooks';\nimport * as Routing from './index';\nimport { AuthFilter } from './filters/AuthFilter';\nimport { DefaultOidcVerifier } from './DefaultOidcVerifier';\nimport { ApiKeyHook, OidcHook } from './AuthHooks';\nimport { AuthenticatedCaller, AuthenticatedMachineIdentity } from './AuthConfig';\n\n/**\n * COMPILE-TIME assertions that the four auth hooks share ONE shape, and that every spelling this\n * change deleted has actually stopped compiling. Each `@ts-expect-error` FAILS THE BUILD (TS2578,\n * \"unused '@ts-expect-error' directive\") the moment the thing it guards starts compiling again.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — the same reason as {@link JwtHookCompileAssertions}:\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 either way. A type-level guarantee has to be asserted in a file\n * the type-checker actually compiles.\n *\n * WHY IT MATTERS HERE. This repo ships no backwards-compatibility shims: the compile error IS the\n * migration. An implementor that kept `verify(name, request, raw)` or a `HeaderReader` parameter\n * would otherwise sit there compiling, and an accepted shape is never migrated.\n */\nexport class AuthHooksCompileAssertions {\n /**\n * The DELETED type names, kept referenced so the two `@ts-expect-error`s above are load-bearing\n * rather than decorative. Both resolve to `any` under the suppressed error; the assertion is the\n * IMPORT failing, not what these aliases denote.\n */\n // webpieces-disable no-any-unknown -- a suppressed import of a deleted symbol resolves to any; the assertion is the import error itself\n deletedNames(): void {\n const headerReaderIsGone: HeaderReader | undefined = undefined;\n const authValuesIsGone: AuthValues | undefined = undefined;\n const webhookAuthCallbackIsGone: WebhookAuthCallback | undefined = undefined;\n void headerReaderIsGone;\n void authValuesIsGone;\n void webhookAuthCallbackIsGone;\n void WEBHOOK_AUTH_CALLBACK;\n // @ts-expect-error VendorAuthHook is DELETED (#1358): this server binds the INBOUND role VendorAuthVerifier\n void Routing.VendorAuthHook;\n // @ts-expect-error VENDOR_AUTH_HOOK is DELETED (#1358): bind VENDOR_AUTH_VERIFIER\n void Routing.VENDOR_AUTH_HOOK;\n // @ts-expect-error the OUTBOUND role is the client's (http-client-node); the router never exports it\n void Routing.VENDOR_AUTH_MINTER;\n }\n\n /**\n * The server's AuthFilter takes the INBOUND role only. Handing it an outbound minter must not\n * compile: a minter cannot verify, and an AuthFilter depending on one would construct a client's\n * credential source just to receive a call (#1358).\n */\n authFilterRefusesTheMinter(oidc: DefaultOidcVerifier, minter: VendorAuthMinter): void {\n // @ts-expect-error a VendorAuthMinter cannot verify: AuthFilter takes a VendorAuthVerifier\n void new AuthFilter(oidc, undefined, undefined, undefined, minter);\n }\n\n /** The ALIGNED spellings must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): void {\n void class extends VendorAuthVerifier {\n // No meaningless mint()/refresh(): a server-only verifier implements verify and nothing else.\n override async verify(_name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> {\n // No `raw!`, no `if (!raw) throw`: AuthFilter checked once and the TYPE carries it.\n const bytes: Buffer = request.raw.rawBody;\n return new AuthenticatedCaller(`sentry:${bytes.length}`);\n }\n };\n void class extends ApiKeyHook {\n override async verifyApiKey(_name: string, request: HttpRequest): Promise<AuthenticatedCaller> {\n // Both of these were UNREACHABLE through the deleted one-method HeaderReader.\n const all: string[] | undefined = request.getHeaderValues('x-api-key');\n return new AuthenticatedCaller(all?.[0] ?? 'anonymous');\n }\n };\n }\n\n /** Every spelling this change deleted must now be UNWRITABLE. */\n rejected(): void {\n void class extends VendorAuthVerifier {\n // @ts-expect-error verify takes (name, RawHttpRequest): the three-parameter spelling is gone\n override async verify(_name: string, _request: HttpRequest, _raw: RawRequest): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('u1');\n }\n };\n void class extends VendorAuthVerifier {\n // @ts-expect-error verify returns the caller it proved: a void override must not compile\n override async verify(_name: string, _request: RawHttpRequest): Promise<void> {\n // a hook that verifies and then has no way to say what it proved\n }\n };\n }\n\n /**\n * {@link OidcHook.verifyOidc} receives EVERY OIDC method the endpoint declares ({@link OidcAuthMethods}),\n * so a hook verifies the token once and can tell `oidcSameProject()` from `oidcSvcs([...])`. The\n * caller-list override this change deletes (`callers: string[]`, where an EMPTY list meant \"any\n * Google-signed caller\") and a one-method override, which could not see the other scope of a pair,\n * must not compile.\n */\n oidcHookTakesTheMethods(): void {\n void class extends OidcHook {\n override async verifyOidc(_token: string, methods: OidcAuthMethods): Promise<AuthenticatedMachineIdentity> {\n const first = methods[0];\n const named = first.callerScope === 'services' ? first.callers[0] : 'same-project';\n return new AuthenticatedMachineIdentity('oidc', named);\n }\n };\n void class extends OidcHook {\n // @ts-expect-error verifyOidc takes the endpoint's OidcAuthMethods; the deleted caller-list override must not compile\n override async verifyOidc(_token: string, _callers: string[]): Promise<AuthenticatedMachineIdentity> {\n return new AuthenticatedMachineIdentity('oidc', 'caller@example.test');\n }\n };\n void class extends OidcHook {\n // @ts-expect-error verifyOidc takes every OIDC method of the endpoint, not one, so a pair is verified once\n override async verifyOidc(_token: string, _method: OidcAuthMethod): Promise<AuthenticatedMachineIdentity> {\n return new AuthenticatedMachineIdentity('oidc', 'caller@example.test');\n }\n };\n }\n\n /**\n * An {@link HttpRequest} whose `raw` is merely OPTIONAL is NOT a {@link RawHttpRequest}. This is\n * the assignment that makes the narrowing real: without it, `RawHttpRequest` would be a comment.\n */\n rawIsNotOptional(): void {\n const maybeRaw = new HttpRequest('POST', '/hook/sentry/issue', new Map<string, string[]>());\n // @ts-expect-error HttpRequest.raw is optional, so it is not assignable to RawHttpRequest\n const narrowed: RawHttpRequest = maybeRaw;\n void narrowed;\n\n // The other direction is free: a RawHttpRequest IS an HttpRequest everywhere one is wanted.\n const proven = new HttpRequest(\n 'POST',\n '/hook/sentry/issue',\n new Map<string, string[]>(),\n new RawRequest('https://example.com/hook', Buffer.from('{}', 'utf8'), '1.2.3.4'),\n ) as RawHttpRequest;\n const widened: HttpRequest = proven;\n void widened;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AuthHooksCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthHooksCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { HttpRequest, RawHttpRequest, RawRequest } from '@webpieces/core-context';\n// @ts-expect-error HeaderReader is DELETED — an ApiKeyHook now receives the whole HttpRequest, so it\n// reaches getHeaderValues and the ContextKey overload of getHeader that the narrow reader hid.\nimport type { HeaderReader } from './AuthHooks';\n// @ts-expect-error AuthValues is RENAMED to AuthenticatedCaller — there is no alias, by policy.\nimport type { AuthValues } from './AuthConfig';\nimport { OidcAuthMethod, OidcAuthMethods } from '@webpieces/core-util';\nimport { VendorAuthMinter, VendorAuthVerifier } from '@webpieces/core-context';\n// @ts-expect-error WebhookAuthCallback is REPLACED by VendorAuthVerifier (the inbound role) — no alias, by policy.\nimport type { WebhookAuthCallback } from './AuthHooks';\n// @ts-expect-error WEBHOOK_AUTH_CALLBACK is REPLACED by VENDOR_AUTH_VERIFIER.\nimport { WEBHOOK_AUTH_CALLBACK } from './AuthHooks';\nimport * as Routing from './index';\nimport { AuthFilter } from './filters/AuthFilter';\nimport { DefaultOidcVerifier } from './DefaultOidcVerifier';\nimport { ApiKeyHook, OidcHook } from './AuthHooks';\nimport { AuthenticatedCaller, AuthenticatedMachineIdentity } from './AuthConfig';\n\n/**\n * COMPILE-TIME assertions that the four auth hooks share ONE shape, and that every spelling this\n * change deleted has actually stopped compiling. Each `@ts-expect-error` FAILS THE BUILD (TS2578,\n * \"unused '@ts-expect-error' directive\") the moment the thing it guards starts compiling again.\n *\n * WHY THIS IS NOT A `.spec.ts` FILE — the same reason as {@link JwtHookCompileAssertions}:\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 either way. A type-level guarantee has to be asserted in a file\n * the type-checker actually compiles.\n *\n * WHY IT MATTERS HERE. This repo ships no backwards-compatibility shims: the compile error IS the\n * migration. An implementor that kept `verify(name, request, raw)` or a `HeaderReader` parameter\n * would otherwise sit there compiling, and an accepted shape is never migrated.\n */\nexport class AuthHooksCompileAssertions {\n /**\n * The DELETED type names, kept referenced so the two `@ts-expect-error`s above are load-bearing\n * rather than decorative. Both resolve to `any` under the suppressed error; the assertion is the\n * IMPORT failing, not what these aliases denote.\n */\n // webpieces-disable no-any-unknown -- a suppressed import of a deleted symbol resolves to any; the assertion is the import error itself\n deletedNames(): void {\n const headerReaderIsGone: HeaderReader | undefined = undefined;\n const authValuesIsGone: AuthValues | undefined = undefined;\n const webhookAuthCallbackIsGone: WebhookAuthCallback | undefined = undefined;\n void headerReaderIsGone;\n void authValuesIsGone;\n void webhookAuthCallbackIsGone;\n void WEBHOOK_AUTH_CALLBACK;\n // @ts-expect-error VendorAuthHook is DELETED (#1358): this server binds the INBOUND role VendorAuthVerifier\n void Routing.VendorAuthHook;\n // @ts-expect-error VENDOR_AUTH_HOOK is DELETED (#1358): bind VENDOR_AUTH_VERIFIER\n void Routing.VENDOR_AUTH_HOOK;\n // @ts-expect-error the OUTBOUND role is the client's (http-client-node); the router never exports it\n void Routing.VENDOR_AUTH_MINTER;\n }\n\n /**\n * The server's AuthFilter takes the INBOUND role only. Handing it an outbound minter must not\n * compile: a minter cannot verify, and an AuthFilter depending on one would construct a client's\n * credential source just to receive a call (#1358).\n */\n authFilterRefusesTheMinter(oidc: DefaultOidcVerifier, minter: VendorAuthMinter): void {\n // @ts-expect-error a VendorAuthMinter cannot verify: AuthFilter takes a VendorAuthVerifier\n void new AuthFilter(oidc, undefined, undefined, undefined, minter);\n }\n\n /** The ALIGNED spellings must keep compiling; asserted by the ABSENCE of an error. */\n legitimate(): void {\n void class extends VendorAuthVerifier {\n // The vendors it handles, read at route registration so an unhandled name fails boot (#1362).\n override supportedVendorNames(): readonly string[] {\n return ['sentry'];\n }\n\n // No meaningless mint()/refresh(): a server-only verifier implements verify and nothing else.\n override async verify(_name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> {\n // No `raw!`, no `if (!raw) throw`: AuthFilter checked once and the TYPE carries it.\n const bytes: Buffer = request.raw.rawBody;\n return new AuthenticatedCaller(`sentry:${bytes.length}`);\n }\n };\n void class extends ApiKeyHook {\n override async verifyApiKey(_name: string, request: HttpRequest): Promise<AuthenticatedCaller> {\n // Both of these were UNREACHABLE through the deleted one-method HeaderReader.\n const all: string[] | undefined = request.getHeaderValues('x-api-key');\n return new AuthenticatedCaller(all?.[0] ?? 'anonymous');\n }\n };\n }\n\n /** Every spelling this change deleted must now be UNWRITABLE. */\n rejected(): void {\n void class extends VendorAuthVerifier {\n override supportedVendorNames(): readonly string[] {\n return ['sentry'];\n }\n // @ts-expect-error verify takes (name, RawHttpRequest): the three-parameter spelling is gone\n override async verify(_name: string, _request: HttpRequest, _raw: RawRequest): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('u1');\n }\n };\n void class extends VendorAuthVerifier {\n override supportedVendorNames(): readonly string[] {\n return ['sentry'];\n }\n // @ts-expect-error verify returns the caller it proved: a void override must not compile\n override async verify(_name: string, _request: RawHttpRequest): Promise<void> {\n // a hook that verifies and then has no way to say what it proved\n }\n };\n }\n\n /**\n * {@link OidcHook.verifyOidc} receives EVERY OIDC method the endpoint declares ({@link OidcAuthMethods}),\n * so a hook verifies the token once and can tell `oidcSameProject()` from `oidcSvcs([...])`. The\n * caller-list override this change deletes (`callers: string[]`, where an EMPTY list meant \"any\n * Google-signed caller\") and a one-method override, which could not see the other scope of a pair,\n * must not compile.\n */\n oidcHookTakesTheMethods(): void {\n void class extends OidcHook {\n override async verifyOidc(_token: string, methods: OidcAuthMethods): Promise<AuthenticatedMachineIdentity> {\n const first = methods[0];\n const named = first.callerScope === 'services' ? first.callers[0] : 'same-project';\n return new AuthenticatedMachineIdentity('oidc', named);\n }\n };\n void class extends OidcHook {\n // @ts-expect-error verifyOidc takes the endpoint's OidcAuthMethods; the deleted caller-list override must not compile\n override async verifyOidc(_token: string, _callers: string[]): Promise<AuthenticatedMachineIdentity> {\n return new AuthenticatedMachineIdentity('oidc', 'caller@example.test');\n }\n };\n void class extends OidcHook {\n // @ts-expect-error verifyOidc takes every OIDC method of the endpoint, not one, so a pair is verified once\n override async verifyOidc(_token: string, _method: OidcAuthMethod): Promise<AuthenticatedMachineIdentity> {\n return new AuthenticatedMachineIdentity('oidc', 'caller@example.test');\n }\n };\n }\n\n /**\n * An {@link HttpRequest} whose `raw` is merely OPTIONAL is NOT a {@link RawHttpRequest}. This is\n * the assignment that makes the narrowing real: without it, `RawHttpRequest` would be a comment.\n */\n rawIsNotOptional(): void {\n const maybeRaw = new HttpRequest('POST', '/hook/sentry/issue', new Map<string, string[]>());\n // @ts-expect-error HttpRequest.raw is optional, so it is not assignable to RawHttpRequest\n const narrowed: RawHttpRequest = maybeRaw;\n void narrowed;\n\n // The other direction is free: a RawHttpRequest IS an HttpRequest everywhere one is wanted.\n const proven = new HttpRequest(\n 'POST',\n '/hook/sentry/issue',\n new Map<string, string[]>(),\n new RawRequest('https://example.com/hook', Buffer.from('{}', 'utf8'), '1.2.3.4'),\n ) as RawHttpRequest;\n const widened: HttpRequest = proven;\n void widened;\n }\n}\n"]}
|
|
@@ -80,9 +80,21 @@ export declare class RouteBuilderImpl implements RouteBuilder {
|
|
|
80
80
|
* Uses createRouteHandlerWithMeta() to create the handler, then stores it
|
|
81
81
|
* in both the routes array and the routeMap for O(1) lookup.
|
|
82
82
|
*
|
|
83
|
+
* Two STARTUP checks run first, so a wiring mistake refuses to boot instead of 401ing / 403ing
|
|
84
|
+
* the first real request: the authorization policy must be enforceable, and every auth mode the
|
|
85
|
+
* route declares must be verifiable by what is bound (`AuthFilter.assertRouteVerifiable`, #1362).
|
|
86
|
+
*
|
|
83
87
|
* @param route - Route definition with controller class and method name
|
|
84
88
|
*/
|
|
85
89
|
addRoute(route: RouteDefinition): void;
|
|
90
|
+
/**
|
|
91
|
+
* The AuthFilter that will actually enforce routes — the instance `WebpiecesRouter` installed as a
|
|
92
|
+
* fixed filter BEFORE any route is added, so it already holds every app binding. Asking IT (not a
|
|
93
|
+
* fresh `container.get`) means the startup check judges exactly the bindings the request path
|
|
94
|
+
* will use. A hand-assembled builder that installed no AuthFilter enforces no auth mode, so there
|
|
95
|
+
* is nothing to verify.
|
|
96
|
+
*/
|
|
97
|
+
private installedAuthFilter;
|
|
86
98
|
/**
|
|
87
99
|
* Create RouteHandlerWithMeta from a RouteDefinition.
|
|
88
100
|
*
|
package/src/RouteBuilderImpl.js
CHANGED
|
@@ -7,6 +7,7 @@ const WpResponse_1 = require("./WpResponse");
|
|
|
7
7
|
const FilterMatcher_1 = require("./FilterMatcher");
|
|
8
8
|
const core_util_1 = require("@webpieces/core-util");
|
|
9
9
|
const AuthorizationHook_1 = require("./AuthorizationHook");
|
|
10
|
+
const AuthFilter_1 = require("./filters/AuthFilter");
|
|
10
11
|
const log = core_util_1.LogManager.getLogger('RouteBuilder');
|
|
11
12
|
/**
|
|
12
13
|
* FilterWithMeta - Pairs a resolved filter instance with its definition.
|
|
@@ -105,6 +106,10 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
105
106
|
* Uses createRouteHandlerWithMeta() to create the handler, then stores it
|
|
106
107
|
* in both the routes array and the routeMap for O(1) lookup.
|
|
107
108
|
*
|
|
109
|
+
* Two STARTUP checks run first, so a wiring mistake refuses to boot instead of 401ing / 403ing
|
|
110
|
+
* the first real request: the authorization policy must be enforceable, and every auth mode the
|
|
111
|
+
* route declares must be verifiable by what is bound (`AuthFilter.assertRouteVerifiable`, #1362).
|
|
112
|
+
*
|
|
108
113
|
* @param route - Route definition with controller class and method name
|
|
109
114
|
*/
|
|
110
115
|
addRoute(route) {
|
|
@@ -114,12 +119,27 @@ let RouteBuilderImpl = class RouteBuilderImpl {
|
|
|
114
119
|
if (!this.container)
|
|
115
120
|
throw new Error('Set the route container before registering endpoints.');
|
|
116
121
|
this.container.get(AuthorizationHook_1.AuthorizationService).validate(policy);
|
|
122
|
+
this.installedAuthFilter()?.assertRouteVerifiable(route.routeMeta);
|
|
117
123
|
const routeWithMeta = this.createRouteHandlerWithMeta(route);
|
|
118
124
|
this.routes.push(routeWithMeta);
|
|
119
125
|
// Also add to map for O(1) lookup by method:path
|
|
120
126
|
const key = this.createRouteKey(route.routeMeta.httpMethod, route.routeMeta.path);
|
|
121
127
|
this.routeMap.set(key, routeWithMeta);
|
|
122
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* The AuthFilter that will actually enforce routes — the instance `WebpiecesRouter` installed as a
|
|
131
|
+
* fixed filter BEFORE any route is added, so it already holds every app binding. Asking IT (not a
|
|
132
|
+
* fresh `container.get`) means the startup check judges exactly the bindings the request path
|
|
133
|
+
* will use. A hand-assembled builder that installed no AuthFilter enforces no auth mode, so there
|
|
134
|
+
* is nothing to verify.
|
|
135
|
+
*/
|
|
136
|
+
installedAuthFilter() {
|
|
137
|
+
for (const entry of this.filterRegistry) {
|
|
138
|
+
if (entry.filter instanceof AuthFilter_1.AuthFilter)
|
|
139
|
+
return entry.filter;
|
|
140
|
+
}
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
123
143
|
/**
|
|
124
144
|
* Create RouteHandlerWithMeta from a RouteDefinition.
|
|
125
145
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { RouteHandler } from './RouteHandler';\nimport { MethodMeta } from './MethodMeta';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { Service } from '@webpieces/core-util';\nimport { WpResponse } from './WpResponse';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\nimport { AuthorizationService } from './AuthorizationHook';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\ninterface NamedClass {\n name?: string;\n}\n\n/**\n * FilterWithMeta - Pairs a resolved filter instance with its definition.\n * Stores both the DI-resolved filter and the metadata needed for matching.\n */\nexport class FilterWithMeta {\n constructor(\n public filter: HttpFilter,\n public definition: FilterDefinition,\n ) {}\n}\n\n/**\n * RouteHandlerImpl - Concrete implementation of RouteHandler.\n * Wraps a resolved controller and method to invoke on each request.\n */\nexport class RouteHandlerImpl<TResult> implements RouteHandler<TResult> {\n constructor(\n private controller: Record<string, unknown>,\n // webpieces-disable no-any-unknown -- route handlers invoke arbitrary API argument shapes\n private method: (this: unknown, ...args: unknown[]) => Promise<TResult>,\n ) {}\n\n async execute(meta: MethodMeta): Promise<TResult> {\n // Invoke the method with requestDto from meta\n // The controller is already resolved - no DI lookup on every request!\n const result: TResult = await this.method.apply(this.controller, [...meta.requestArgs]);\n return result;\n }\n}\n/**\n * RouteHandlerWithMeta - Pairs a route handler with its definition.\n * Stores both the handler (which wraps the DI-resolved controller) and the route metadata.\n *\n * We use unknown for the generic type since we store different TResult types in the same Map.\n * Type safety is maintained through the generic on RouteDefinition at registration time.\n */\nexport class RouteHandlerWithMeta {\n constructor(\n public invokeControllerHandler: RouteHandler<unknown>,\n public definition: RouteDefinition,\n ) {}\n}\n\n/**\n * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.\n *\n * Similar to Java WebPieces RouteBuilder, this class is responsible for:\n * 1. Registering routes with their handlers\n * 2. Registering filters with priority\n *\n * This class is explicit (not anonymous) to:\n * - Improve traceability and debugging\n * - Make the code easier to understand\n * - Enable better IDE navigation (Cmd+Click on addRoute works!)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()\n * but needs appContainer to resolve filters/controllers. The container is set via\n * setContainer() after appContainer is created (late binding pattern).\n */\n@provideFrameworkSingleton()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n\n /**\n * Map for O(1) route lookup by method:path.\n * Used by both addRoute() and createRouteInvoker() for fast route access.\n */\n private routeMap: Map<string, RouteHandlerWithMeta> = new Map();\n\n /**\n * Create route key for consistent lookup.\n * Key format: \"${METHOD}:${path}\" (e.g., \"POST:/search/item\")\n */\n private createRouteKey(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /**\n * Set the DI container used for resolving filters and controllers.\n * Called by WebpiecesCoreServer after appContainer is created.\n *\n * @param container - The application DI container (appContainer)\n */\n setContainer(container: Container): void {\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * Uses createRouteHandlerWithMeta() to create the handler, then stores it\n * in both the routes array and the routeMap for O(1) lookup.\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const policy = route.routeMeta.authorization;\n if (!route.routeMeta.authMeta || !policy) throw new Error(`Endpoint ${route.routeMeta.path} requires @WpAuth/@WpAuthPublic and @WpAuthorization.`);\n if (!this.container) throw new Error('Set the route container before registering endpoints.');\n this.container.get(AuthorizationService).validate(policy);\n const routeWithMeta = this.createRouteHandlerWithMeta(route);\n this.routes.push(routeWithMeta);\n\n // Also add to map for O(1) lookup by method:path\n const key = this.createRouteKey(route.routeMeta.httpMethod, route.routeMeta.path);\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * Create RouteHandlerWithMeta from a RouteDefinition.\n *\n * Resolves controller from DI container ONCE and creates a handler that\n * invokes the controller method with the request DTO.\n *\n * This method is used by:\n * - addRoute() for production route registration\n * - createRouteInvoker() for test clients (via createApiClient)\n *\n * @param route - Route definition with controller class and method name\n * @returns RouteHandlerWithMeta containing the handler and route definition\n */\n private createRouteHandlerWithMeta<TResult = unknown>(\n route: RouteDefinition,\n ): RouteHandlerWithMeta {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering routes.');\n }\n\n const routeMeta = route.routeMeta;\n\n // Resolve controller instance from DI container ONCE (not on every request!)\n const controller = this.container.get(route.controllerClass) as Record<string, unknown>;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as NamedClass).name || 'Unknown';\n throw new Error(\n `Method ${routeMeta.methodName} not found on controller ${controllerName}`,\n );\n }\n\n const handler = new RouteHandlerImpl<TResult>(\n controller,\n // webpieces-disable no-any-unknown -- resolved controller method has the registered API's arguments\n method as (this: unknown, ...args: unknown[]) => Promise<TResult>,\n );\n\n // Return handler with route definition\n // webpieces-disable no-any-unknown -- heterogeneous handlers share one registration collection\n return new RouteHandlerWithMeta(handler as RouteHandler<unknown>, route);\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Resolves the filter from DI container and pairs it with the filter definition.\n * The definition includes pattern information used for route-specific filtering.\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering filters.');\n }\n\n // Resolve filter instance from DI container\n const filter = this.container.get<HttpFilter>(filterDef.filterClass);\n\n // Store filter with its definition\n const filterWithMeta = new FilterWithMeta(filter, filterDef);\n this.filterRegistry.push(filterWithMeta);\n }\n\n /**\n * Get all registered routes.\n *\n * @returns Map of routes with handlers and definitions, keyed by \"METHOD:path\"\n */\n getRoutes(): RouteHandlerWithMeta[] {\n return this.routes;\n }\n\n /**\n * Get all filters sorted by priority (highest priority first).\n *\n * @returns Array of FilterWithMeta sorted by priority\n */\n getSortedFilters(): Array<FilterWithMeta> {\n return [...this.filterRegistry].sort(\n (a, b) => b.definition.priority - a.definition.priority,\n );\n }\n\n /**\n * Cached filter definitions for lazy route setup.\n */\n private cachedFilterDefinitions?: FilterDefinition[];\n\n /**\n * Get filter definitions, computing once and caching.\n */\n private getFilterDefinitions(): FilterDefinition[] {\n if (!this.cachedFilterDefinitions) {\n const sortedFilters = this.getSortedFilters();\n this.cachedFilterDefinitions = sortedFilters.map((fwm) => {\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n }\n return this.cachedFilterDefinitions;\n }\n\n /**\n * Setup a single route by creating its filter chain.\n * This is called lazily by createHandler() and getRouteService().\n *\n * Creates a Service that wraps the filter chain and controller invocation.\n * The service is DTO-only and has no Express dependency.\n *\n * @param key - Route key in format \"METHOD:path\"\n * @param routeWithMeta - Route handler with metadata\n * @returns The service for this route\n */\n public createRouteHandler(\n routeWithMeta: RouteHandlerWithMeta,\n ): Service<MethodMeta, WpResponse<unknown>> {\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n\n log.info(`Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);\n\n // ONE chain for both HTTP and in-process — no transport tier. The fixed framework\n // filters (LogApiFilter, AuthFilter) are auto-installed and read the transport-neutral\n // HttpRequest, so they run identically in both.\n const filterDefinitions = this.getFilterDefinitions();\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions,\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse<unknown>> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse<unknown>> => {\n const result = await routeWithMeta.invokeControllerHandler.execute(meta);\n // A `responseBody: NONE` endpoint (Promise<void>) has NO response: every transport\n // sends 0 bytes (HTTP 204) and every client resolves undefined, so the in-process\n // client must too. LogApiCall logs an undefined response as `response=undefined`.\n if (routeMeta.noResponseBody) return new WpResponse(undefined);\n // Any other route that yields undefined/null still answers a JSON body: coerce to {}.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\n 'No filters found for route — the framework auto-installs LogApiFilter + AuthFilter, so this indicates a wiring problem.',\n );\n }\n\n // Chain filters: highest priority (first in array) should run first (be outermost)\n // Build from innermost (lowest priority) to outermost (highest priority)\n // Start with controller, then wrap with filters in reverse priority order\n let service: Service<MethodMeta, WpResponse<unknown>> = controllerService;\n for (let i = matchingFilters.length - 1; i >= 0; i--) {\n service = matchingFilters[i].chainService(service);\n }\n\n return service;\n }\n\n /**\n * Create an invoker function for a route (for testing via createApiClient).\n * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,\n * and returns a Service that can be called multiple times without\n * recreating the filter chain.\n *\n * This method is called by WebpiecesServer.createApiClient() during proxy setup.\n * The returned Service is stored as the proxy method and invoked on each call.\n *\n * @param method - HTTP method (GET, POST, etc.)\n * @param path - URL path\n * @returns A Service that invokes the route\n */\n createRouteInvoker(method: string, path: string): Service<MethodMeta, WpResponse<unknown>> {\n // Use routeMap for O(1) lookup (not linear search!)\n const key = this.createRouteKey(method, path);\n const routeWithMeta = this.routeMap.get(key);\n\n if (!routeWithMeta) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Setup filter chain ONCE (not on every invocation!). Same chain as HTTP — auth included.\n return this.createRouteHandler(routeWithMeta);\n }\n\n /**\n * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.\n * Used to build a MethodMeta for an in-process dispatch (e.g. a delivered cloud\n * task) so the filter chain sees the same routeMeta production HTTP would.\n *\n * @returns the route's RouteMetadata, or undefined if no route is registered.\n */\n getRouteMeta(method: string, path: string): RouteMetadata | undefined {\n const key = this.createRouteKey(method, path);\n return this.routeMap.get(key)?.definition.routeMeta;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"RouteBuilderImpl.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/RouteBuilderImpl.ts"],"names":[],"mappings":"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","sourcesContent":["import { Container } from 'inversify';\nimport { RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\nimport { provideFrameworkSingleton } from '@webpieces/core-context';\nimport { RouteHandler } from './RouteHandler';\nimport { MethodMeta } from './MethodMeta';\nimport { RouteMetadata, DocumentDesign } from '@webpieces/core-util';\nimport { Service } from '@webpieces/core-util';\nimport { WpResponse } from './WpResponse';\nimport { FilterMatcher, HttpFilter } from './FilterMatcher';\nimport { LogManager } from '@webpieces/core-util';\nimport { AuthorizationService } from './AuthorizationHook';\nimport { AuthFilter } from './filters/AuthFilter';\n\nconst log = LogManager.getLogger('RouteBuilder');\n\ninterface NamedClass {\n name?: string;\n}\n\n/**\n * FilterWithMeta - Pairs a resolved filter instance with its definition.\n * Stores both the DI-resolved filter and the metadata needed for matching.\n */\nexport class FilterWithMeta {\n constructor(\n public filter: HttpFilter,\n public definition: FilterDefinition,\n ) {}\n}\n\n/**\n * RouteHandlerImpl - Concrete implementation of RouteHandler.\n * Wraps a resolved controller and method to invoke on each request.\n */\nexport class RouteHandlerImpl<TResult> implements RouteHandler<TResult> {\n constructor(\n private controller: Record<string, unknown>,\n // webpieces-disable no-any-unknown -- route handlers invoke arbitrary API argument shapes\n private method: (this: unknown, ...args: unknown[]) => Promise<TResult>,\n ) {}\n\n async execute(meta: MethodMeta): Promise<TResult> {\n // Invoke the method with requestDto from meta\n // The controller is already resolved - no DI lookup on every request!\n const result: TResult = await this.method.apply(this.controller, [...meta.requestArgs]);\n return result;\n }\n}\n/**\n * RouteHandlerWithMeta - Pairs a route handler with its definition.\n * Stores both the handler (which wraps the DI-resolved controller) and the route metadata.\n *\n * We use unknown for the generic type since we store different TResult types in the same Map.\n * Type safety is maintained through the generic on RouteDefinition at registration time.\n */\nexport class RouteHandlerWithMeta {\n constructor(\n public invokeControllerHandler: RouteHandler<unknown>,\n public definition: RouteDefinition,\n ) {}\n}\n\n/**\n * RouteBuilderImpl - Concrete implementation of RouteBuilder interface.\n *\n * Similar to Java WebPieces RouteBuilder, this class is responsible for:\n * 1. Registering routes with their handlers\n * 2. Registering filters with priority\n *\n * This class is explicit (not anonymous) to:\n * - Improve traceability and debugging\n * - Make the code easier to understand\n * - Enable better IDE navigation (Cmd+Click on addRoute works!)\n *\n * DI Pattern: This class is registered in webpiecesContainer via @provideFrameworkSingleton()\n * but needs appContainer to resolve filters/controllers. The container is set via\n * setContainer() after appContainer is created (late binding pattern).\n */\n@provideFrameworkSingleton()\nexport class RouteBuilderImpl implements RouteBuilder {\n private routes: RouteHandlerWithMeta[] = [];\n private filterRegistry: Array<FilterWithMeta> = [];\n private container?: Container;\n\n /**\n * Map for O(1) route lookup by method:path.\n * Used by both addRoute() and createRouteInvoker() for fast route access.\n */\n private routeMap: Map<string, RouteHandlerWithMeta> = new Map();\n\n /**\n * Create route key for consistent lookup.\n * Key format: \"${METHOD}:${path}\" (e.g., \"POST:/search/item\")\n */\n private createRouteKey(method: string, path: string): string {\n return `${method.toUpperCase()}:${path}`;\n }\n\n /**\n * Set the DI container used for resolving filters and controllers.\n * Called by WebpiecesCoreServer after appContainer is created.\n *\n * @param container - The application DI container (appContainer)\n */\n setContainer(container: Container): void {\n this.container = container;\n }\n\n /**\n * Register a route with the router.\n *\n * Uses createRouteHandlerWithMeta() to create the handler, then stores it\n * in both the routes array and the routeMap for O(1) lookup.\n *\n * Two STARTUP checks run first, so a wiring mistake refuses to boot instead of 401ing / 403ing\n * the first real request: the authorization policy must be enforceable, and every auth mode the\n * route declares must be verifiable by what is bound (`AuthFilter.assertRouteVerifiable`, #1362).\n *\n * @param route - Route definition with controller class and method name\n */\n addRoute(route: RouteDefinition): void {\n const policy = route.routeMeta.authorization;\n if (!route.routeMeta.authMeta || !policy) throw new Error(`Endpoint ${route.routeMeta.path} requires @WpAuth/@WpAuthPublic and @WpAuthorization.`);\n if (!this.container) throw new Error('Set the route container before registering endpoints.');\n this.container.get(AuthorizationService).validate(policy);\n this.installedAuthFilter()?.assertRouteVerifiable(route.routeMeta);\n const routeWithMeta = this.createRouteHandlerWithMeta(route);\n this.routes.push(routeWithMeta);\n\n // Also add to map for O(1) lookup by method:path\n const key = this.createRouteKey(route.routeMeta.httpMethod, route.routeMeta.path);\n this.routeMap.set(key, routeWithMeta);\n }\n\n /**\n * The AuthFilter that will actually enforce routes — the instance `WebpiecesRouter` installed as a\n * fixed filter BEFORE any route is added, so it already holds every app binding. Asking IT (not a\n * fresh `container.get`) means the startup check judges exactly the bindings the request path\n * will use. A hand-assembled builder that installed no AuthFilter enforces no auth mode, so there\n * is nothing to verify.\n */\n private installedAuthFilter(): AuthFilter | undefined {\n for (const entry of this.filterRegistry) {\n if (entry.filter instanceof AuthFilter) return entry.filter;\n }\n return undefined;\n }\n\n /**\n * Create RouteHandlerWithMeta from a RouteDefinition.\n *\n * Resolves controller from DI container ONCE and creates a handler that\n * invokes the controller method with the request DTO.\n *\n * This method is used by:\n * - addRoute() for production route registration\n * - createRouteInvoker() for test clients (via createApiClient)\n *\n * @param route - Route definition with controller class and method name\n * @returns RouteHandlerWithMeta containing the handler and route definition\n */\n private createRouteHandlerWithMeta<TResult = unknown>(\n route: RouteDefinition,\n ): RouteHandlerWithMeta {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering routes.');\n }\n\n const routeMeta = route.routeMeta;\n\n // Resolve controller instance from DI container ONCE (not on every request!)\n const controller = this.container.get(route.controllerClass) as Record<string, unknown>;\n\n // Get the controller method\n const method = controller[routeMeta.methodName];\n if (typeof method !== 'function') {\n const controllerName = (route.controllerClass as NamedClass).name || 'Unknown';\n throw new Error(\n `Method ${routeMeta.methodName} not found on controller ${controllerName}`,\n );\n }\n\n const handler = new RouteHandlerImpl<TResult>(\n controller,\n // webpieces-disable no-any-unknown -- resolved controller method has the registered API's arguments\n method as (this: unknown, ...args: unknown[]) => Promise<TResult>,\n );\n\n // Return handler with route definition\n // webpieces-disable no-any-unknown -- heterogeneous handlers share one registration collection\n return new RouteHandlerWithMeta(handler as RouteHandler<unknown>, route);\n }\n\n /**\n * Register a filter with the filter chain.\n *\n * Resolves the filter from DI container and pairs it with the filter definition.\n * The definition includes pattern information used for route-specific filtering.\n *\n * @param filterDef - Filter definition with priority, filter class, and optional filepath pattern\n */\n addFilter(filterDef: FilterDefinition): void {\n if (!this.container) {\n throw new Error('Container not set. Call setContainer() before registering filters.');\n }\n\n // Resolve filter instance from DI container\n const filter = this.container.get<HttpFilter>(filterDef.filterClass);\n\n // Store filter with its definition\n const filterWithMeta = new FilterWithMeta(filter, filterDef);\n this.filterRegistry.push(filterWithMeta);\n }\n\n /**\n * Get all registered routes.\n *\n * @returns Map of routes with handlers and definitions, keyed by \"METHOD:path\"\n */\n getRoutes(): RouteHandlerWithMeta[] {\n return this.routes;\n }\n\n /**\n * Get all filters sorted by priority (highest priority first).\n *\n * @returns Array of FilterWithMeta sorted by priority\n */\n getSortedFilters(): Array<FilterWithMeta> {\n return [...this.filterRegistry].sort(\n (a, b) => b.definition.priority - a.definition.priority,\n );\n }\n\n /**\n * Cached filter definitions for lazy route setup.\n */\n private cachedFilterDefinitions?: FilterDefinition[];\n\n /**\n * Get filter definitions, computing once and caching.\n */\n private getFilterDefinitions(): FilterDefinition[] {\n if (!this.cachedFilterDefinitions) {\n const sortedFilters = this.getSortedFilters();\n this.cachedFilterDefinitions = sortedFilters.map((fwm) => {\n const def = fwm.definition;\n def.filter = fwm.filter;\n return def;\n });\n }\n return this.cachedFilterDefinitions;\n }\n\n /**\n * Setup a single route by creating its filter chain.\n * This is called lazily by createHandler() and getRouteService().\n *\n * Creates a Service that wraps the filter chain and controller invocation.\n * The service is DTO-only and has no Express dependency.\n *\n * @param key - Route key in format \"METHOD:path\"\n * @param routeWithMeta - Route handler with metadata\n * @returns The service for this route\n */\n public createRouteHandler(\n routeWithMeta: RouteHandlerWithMeta,\n ): Service<MethodMeta, WpResponse<unknown>> {\n const route = routeWithMeta.definition;\n const routeMeta = route.routeMeta;\n\n log.info(`Setting up route: ${routeMeta.httpMethod} ${routeMeta.path}`);\n\n // ONE chain for both HTTP and in-process — no transport tier. The fixed framework\n // filters (LogApiFilter, AuthFilter) are auto-installed and read the transport-neutral\n // HttpRequest, so they run identically in both.\n const filterDefinitions = this.getFilterDefinitions();\n\n // Find matching filters for this route\n const matchingFilters = FilterMatcher.findMatchingFilters(\n route.controllerFilepath,\n filterDefinitions,\n );\n\n // Create service that wraps the controller execution\n const controllerService: Service<MethodMeta, WpResponse<unknown>> = {\n invoke: async (meta: MethodMeta): Promise<WpResponse<unknown>> => {\n const result = await routeWithMeta.invokeControllerHandler.execute(meta);\n // A `responseBody: NONE` endpoint (Promise<void>) has NO response: every transport\n // sends 0 bytes (HTTP 204) and every client resolves undefined, so the in-process\n // client must too. LogApiCall logs an undefined response as `response=undefined`.\n if (routeMeta.noResponseBody) return new WpResponse(undefined);\n // Any other route that yields undefined/null still answers a JSON body: coerce to {}.\n return new WpResponse(result ?? {});\n },\n };\n\n if (matchingFilters.length === 0) {\n throw new Error(\n 'No filters found for route — the framework auto-installs LogApiFilter + AuthFilter, so this indicates a wiring problem.',\n );\n }\n\n // Chain filters: highest priority (first in array) should run first (be outermost)\n // Build from innermost (lowest priority) to outermost (highest priority)\n // Start with controller, then wrap with filters in reverse priority order\n let service: Service<MethodMeta, WpResponse<unknown>> = controllerService;\n for (let i = matchingFilters.length - 1; i >= 0; i--) {\n service = matchingFilters[i].chainService(service);\n }\n\n return service;\n }\n\n /**\n * Create an invoker function for a route (for testing via createApiClient).\n * Uses routeMap for O(1) lookup, sets up the filter chain ONCE,\n * and returns a Service that can be called multiple times without\n * recreating the filter chain.\n *\n * This method is called by WebpiecesServer.createApiClient() during proxy setup.\n * The returned Service is stored as the proxy method and invoked on each call.\n *\n * @param method - HTTP method (GET, POST, etc.)\n * @param path - URL path\n * @returns A Service that invokes the route\n */\n createRouteInvoker(method: string, path: string): Service<MethodMeta, WpResponse<unknown>> {\n // Use routeMap for O(1) lookup (not linear search!)\n const key = this.createRouteKey(method, path);\n const routeWithMeta = this.routeMap.get(key);\n\n if (!routeWithMeta) {\n throw new Error(`Route not found: ${method} ${path}`);\n }\n\n // Setup filter chain ONCE (not on every invocation!). Same chain as HTTP — auth included.\n return this.createRouteHandler(routeWithMeta);\n }\n\n /**\n * Look up the RouteMetadata (incl. authMeta) for a registered route by method+path.\n * Used to build a MethodMeta for an in-process dispatch (e.g. a delivered cloud\n * task) so the filter chain sees the same routeMeta production HTTP would.\n *\n * @returns the route's RouteMetadata, or undefined if no route is registered.\n */\n getRouteMeta(method: string, path: string): RouteMetadata | undefined {\n const key = this.createRouteKey(method, path);\n return this.routeMap.get(key)?.definition.routeMeta;\n }\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A SERVED endpoint declares an auth mode this server cannot verify, because the binding that mode
|
|
3
|
+
* needs is absent (#1362). Thrown at STARTUP, from route registration (`RouteBuilderImpl.addRoute` ->
|
|
4
|
+
* `AuthFilter.assertRouteVerifiable`), so the process refuses to boot instead of answering the first
|
|
5
|
+
* vendor / partner / user request with a 401 that reads as an outage on their side.
|
|
6
|
+
*
|
|
7
|
+
* Its own type, carrying the facts as fields, so a boot log or a spec names the endpoint and the mode
|
|
8
|
+
* without parsing the message. The message itself carries the exact binding line to add.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UnverifiableAuthModeError extends Error {
|
|
11
|
+
/** `Contract.method (VERB /path)` — which served endpoint cannot be verified. */
|
|
12
|
+
readonly endpoint: string;
|
|
13
|
+
/** The declared mode nobody can verify, e.g. `vendorAuth('bugfixer-sentry')`, `jwt()`. */
|
|
14
|
+
readonly mode: string;
|
|
15
|
+
constructor(message: string,
|
|
16
|
+
/** `Contract.method (VERB /path)` — which served endpoint cannot be verified. */
|
|
17
|
+
endpoint: string,
|
|
18
|
+
/** The declared mode nobody can verify, e.g. `vendorAuth('bugfixer-sentry')`, `jwt()`. */
|
|
19
|
+
mode: string);
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnverifiableAuthModeError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A SERVED endpoint declares an auth mode this server cannot verify, because the binding that mode
|
|
6
|
+
* needs is absent (#1362). Thrown at STARTUP, from route registration (`RouteBuilderImpl.addRoute` ->
|
|
7
|
+
* `AuthFilter.assertRouteVerifiable`), so the process refuses to boot instead of answering the first
|
|
8
|
+
* vendor / partner / user request with a 401 that reads as an outage on their side.
|
|
9
|
+
*
|
|
10
|
+
* Its own type, carrying the facts as fields, so a boot log or a spec names the endpoint and the mode
|
|
11
|
+
* without parsing the message. The message itself carries the exact binding line to add.
|
|
12
|
+
*/
|
|
13
|
+
class UnverifiableAuthModeError extends Error {
|
|
14
|
+
endpoint;
|
|
15
|
+
mode;
|
|
16
|
+
constructor(message,
|
|
17
|
+
/** `Contract.method (VERB /path)` — which served endpoint cannot be verified. */
|
|
18
|
+
endpoint,
|
|
19
|
+
/** The declared mode nobody can verify, e.g. `vendorAuth('bugfixer-sentry')`, `jwt()`. */
|
|
20
|
+
mode) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.endpoint = endpoint;
|
|
23
|
+
this.mode = mode;
|
|
24
|
+
this.name = 'UnverifiableAuthModeError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.UnverifiableAuthModeError = UnverifiableAuthModeError;
|
|
28
|
+
//# sourceMappingURL=UnverifiableAuthModeError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UnverifiableAuthModeError.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/UnverifiableAuthModeError.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;GAQG;AACH,MAAa,yBAA0B,SAAQ,KAAK;IAI5B;IAEA;IALpB,YACI,OAAe;IACf,iFAAiF;IACjE,QAAgB;IAChC,0FAA0F;IAC1E,IAAY;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,aAAQ,GAAR,QAAQ,CAAQ;QAEhB,SAAI,GAAJ,IAAI,CAAQ;QAG5B,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC5C,CAAC;CACJ;AAXD,8DAWC","sourcesContent":["/**\n * A SERVED endpoint declares an auth mode this server cannot verify, because the binding that mode\n * needs is absent (#1362). Thrown at STARTUP, from route registration (`RouteBuilderImpl.addRoute` ->\n * `AuthFilter.assertRouteVerifiable`), so the process refuses to boot instead of answering the first\n * vendor / partner / user request with a 401 that reads as an outage on their side.\n *\n * Its own type, carrying the facts as fields, so a boot log or a spec names the endpoint and the mode\n * without parsing the message. The message itself carries the exact binding line to add.\n */\nexport class UnverifiableAuthModeError extends Error {\n constructor(\n message: string,\n /** `Contract.method (VERB /path)` — which served endpoint cannot be verified. */\n public readonly endpoint: string,\n /** The declared mode nobody can verify, e.g. `vendorAuth('bugfixer-sentry')`, `jwt()`. */\n public readonly mode: string,\n ) {\n super(message);\n this.name = 'UnverifiableAuthModeError';\n }\n}\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { VendorAuthVerifier } from '@webpieces/core-context';
|
|
2
|
+
import { RouteMetadata } from '@webpieces/core-util';
|
|
2
3
|
import { Filter, Service } from '@webpieces/core-util';
|
|
3
4
|
import { WpResponse } from '../WpResponse';
|
|
4
5
|
import { MethodMeta } from '../MethodMeta';
|
|
@@ -11,21 +12,24 @@ import { DefaultOidcVerifier } from '../DefaultOidcVerifier';
|
|
|
11
12
|
* RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.
|
|
12
13
|
*
|
|
13
14
|
* It enforces the endpoint's AuthMode from separately-bound pieces, each OPTIONAL except the OIDC
|
|
14
|
-
* default
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* default. An app binds only the pieces its SERVED routes declare — and a served route that declares
|
|
16
|
+
* a mode whose piece is absent FAILS STARTUP ({@link assertRouteVerifiable}, #1362), so none of the
|
|
17
|
+
* rows below is ever "not enabled" at request time:
|
|
18
|
+
* - shared-secret → constant-time compare vs the {@link AuthConfig} secret VALUE (state). Startup
|
|
19
|
+
* requires an AuthConfig holding the route's key.
|
|
17
20
|
* - jwt → the bound {@link JwtHook} (`parseJwt`, awaited — an app's
|
|
18
|
-
* strategy may reach a JWKS or a datastore).
|
|
19
|
-
*
|
|
21
|
+
* strategy may reach a JWKS or a datastore). Startup requires a JwtHook: JWT needs
|
|
22
|
+
* an app secret + payload shape.
|
|
20
23
|
* - oidc → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}
|
|
21
24
|
* run DIRECTLY — so a server that wires NOTHING still verifies Google OIDC against
|
|
22
25
|
* the endpoint's caller scope (`oidcSameProject()` or `oidcSvcs([...])`).
|
|
23
26
|
* - vendorAuth → the bound {@link VendorAuthVerifier} `verify`s the VENDOR's credential over the
|
|
24
|
-
* retained raw request.
|
|
25
|
-
* vendor call is never waved through because wiring was
|
|
26
|
-
* only — this filter never depends on the outbound
|
|
27
|
+
* retained raw request. Startup requires a verifier whose `supportedVendorNames()` lists the
|
|
28
|
+
* route's name: an unverified vendor call is never waved through because wiring was
|
|
29
|
+
* forgotten. The INBOUND role only — this filter never depends on the outbound
|
|
30
|
+
* VendorAuthMinter (#1358).
|
|
27
31
|
* - apikey → the bound {@link ApiKeyHook} looks the CUSTOMER's key up (async, over the whole
|
|
28
|
-
* header set) and returns the context to seed.
|
|
32
|
+
* header set) and returns the context to seed. Startup requires an ApiKeyHook.
|
|
29
33
|
* - public → BEST-EFFORT jwt parse (only if a JwtHook is bound): stamp the user's context so
|
|
30
34
|
* a logged-out page still knows who is logged in; never fails.
|
|
31
35
|
* - local-only → serve only when {@link RuntimeLocality} says this process is a developer's
|
|
@@ -43,6 +47,29 @@ export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>>
|
|
|
43
47
|
private readonly apiKeyHook?;
|
|
44
48
|
private readonly callerContext;
|
|
45
49
|
constructor(oidcVerifier: DefaultOidcVerifier, authConfig?: AuthConfig | undefined, jwtHook?: JwtHook<never> | undefined, oidcHook?: OidcHook | undefined, vendorAuthVerifier?: VendorAuthVerifier | undefined, apiKeyHook?: ApiKeyHook | undefined);
|
|
50
|
+
/**
|
|
51
|
+
* STARTUP check (#1362), called by route registration for EVERY served route: each auth mode the
|
|
52
|
+
* route declares must be verifiable by what is bound, or boot fails naming the endpoint, the mode
|
|
53
|
+
* and the exact binding line. STRICT — a multi-method route (`[oidcSameProject(), vendorAuth('x')]`)
|
|
54
|
+
* with no verifier fails too: a declared alternative nobody can verify is dead, and is almost
|
|
55
|
+
* always a wiring mistake. OIDC needs no app binding (the framework verifier) and a public route
|
|
56
|
+
* none.
|
|
57
|
+
*
|
|
58
|
+
* Reads bindings this filter already holds and nothing else: it never calls `verify`, `parseJwt`
|
|
59
|
+
* or `verifyApiKey`, touches no network, and constructs nothing (#1358's cycle stays closed).
|
|
60
|
+
* The request path resolves each piece through the SAME `require*` methods, so there is one
|
|
61
|
+
* decision, made at boot. The one boot-only check is a verifier's `supportedVendorNames()`: at request time
|
|
62
|
+
* the verifier's OWN dispatch stays the backstop for a name it does not know, and refuses (401).
|
|
63
|
+
*/
|
|
64
|
+
assertRouteVerifiable(routeMeta: RouteMetadata): void;
|
|
65
|
+
private static endpointOf;
|
|
66
|
+
private unverifiable;
|
|
67
|
+
private requireJwtHook;
|
|
68
|
+
private requireSharedSecrets;
|
|
69
|
+
private requireVendorVerifier;
|
|
70
|
+
/** Boot only: the bound verifier must DECLARE the route's vendor (#1362). Never calls verify. */
|
|
71
|
+
private assertVendorDeclared;
|
|
72
|
+
private requireApiKeyHook;
|
|
46
73
|
filter(meta: MethodMeta, nextFilter: Service<MethodMeta, WpResponse<unknown>>): Promise<WpResponse<unknown>>;
|
|
47
74
|
/**
|
|
48
75
|
* Try each declared credential in order. The endpoint's OIDC methods (one `oidcSameProject()` and
|
|
@@ -88,17 +115,18 @@ export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>>
|
|
|
88
115
|
private static surfaceFor;
|
|
89
116
|
/**
|
|
90
117
|
* `vendorAuth(name)`: hand the app's {@link VendorAuthVerifier} the verbatim request and let it call the
|
|
91
|
-
* VENDOR's own validator.
|
|
118
|
+
* VENDOR's own validator. A missing verifier, or one not declaring `name`, is not a case here: it
|
|
119
|
+
* failed STARTUP ({@link assertRouteVerifiable}). Two ways to fail, both 401, both before the
|
|
120
|
+
* controller is entered:
|
|
92
121
|
*
|
|
93
|
-
* 1. NO
|
|
94
|
-
* an unverified vendor call must never be waved through because wiring was forgotten.
|
|
95
|
-
* 2. NO raw request — the transport kept no bytes. `assertEveryVendorAuthEndpointRetainsRawBody`
|
|
122
|
+
* 1. NO raw request — the transport kept no bytes. `assertEveryVendorAuthEndpointRetainsRawBody`
|
|
96
123
|
* normally makes this a startup error, so reaching it means either a hand-registered route or
|
|
97
124
|
* an in-process caller (a spec) that published an HttpRequest with no {@link RawRequest}. The
|
|
98
125
|
* message says which fix applies rather than leaving a bare 401.
|
|
99
|
-
*
|
|
126
|
+
* 2. The verifier threw — the credential did not verify (including a name its own dispatch does
|
|
127
|
+
* not know: that stays the request-time backstop).
|
|
100
128
|
*
|
|
101
|
-
* Case
|
|
129
|
+
* Case 1 is checked HERE and nowhere else. {@link hasRawBytes} narrows the request to
|
|
102
130
|
* {@link RawHttpRequest} at this one gate, so the verifier's signature promises `raw` is present and
|
|
103
131
|
* no vendor implementation ever writes `raw!` or a guard of its own.
|
|
104
132
|
*/
|
|
@@ -113,15 +141,14 @@ export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>>
|
|
|
113
141
|
/**
|
|
114
142
|
* `apiKey(regime, credentials)`: hand the app's {@link ApiKeyHook} the regime name and the inbound
|
|
115
143
|
* headers and let it look the CUSTOMER's key up. The declared `credentials` are NOT read here — they
|
|
116
|
-
* describe the contract for generators; the hook owns extraction.
|
|
117
|
-
* before the
|
|
144
|
+
* describe the contract for generators; the hook owns extraction. A missing hook is not a case here:
|
|
145
|
+
* it failed STARTUP ({@link assertRouteVerifiable}). Two ways to fail, both 401, both before the
|
|
146
|
+
* controller:
|
|
118
147
|
*
|
|
119
|
-
* 1. NO
|
|
120
|
-
* unverified partner request must never be waved through because wiring was forgotten.
|
|
121
|
-
* 2. NO inbound request in scope — there are no headers to read, so there is nothing to verify. That
|
|
148
|
+
* 1. NO inbound request in scope — there are no headers to read, so there is nothing to verify. That
|
|
122
149
|
* means a caller drove this route without publishing an HttpRequest; the message says so rather
|
|
123
150
|
* than leaving a bare 401.
|
|
124
|
-
*
|
|
151
|
+
* 2. The hook threw — the key, or the key/organization pair, did not check out.
|
|
125
152
|
*
|
|
126
153
|
* On success the hook's {@link AuthenticatedCaller} is stamped exactly as a jwt parse's is, which is
|
|
127
154
|
* what puts the resolved organization into `RequestContext` for every downstream repository call.
|
|
@@ -13,6 +13,7 @@ const AuthHooks_1 = require("../AuthHooks");
|
|
|
13
13
|
const AuthenticatedCallerContext_1 = require("../AuthenticatedCallerContext");
|
|
14
14
|
const AuthorizationHook_1 = require("../AuthorizationHook");
|
|
15
15
|
const DefaultOidcVerifier_1 = require("../DefaultOidcVerifier");
|
|
16
|
+
const UnverifiableAuthModeError_1 = require("../UnverifiableAuthModeError");
|
|
16
17
|
const log = core_util_1.LogManager.getLogger('AuthFilter');
|
|
17
18
|
/**
|
|
18
19
|
* The ONE credential header, read straight off the inbound HttpRequest.
|
|
@@ -39,21 +40,24 @@ const SHARED_SECRET_SCHEME = 'Webpieces';
|
|
|
39
40
|
* RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.
|
|
40
41
|
*
|
|
41
42
|
* It enforces the endpoint's AuthMode from separately-bound pieces, each OPTIONAL except the OIDC
|
|
42
|
-
* default
|
|
43
|
-
*
|
|
44
|
-
*
|
|
43
|
+
* default. An app binds only the pieces its SERVED routes declare — and a served route that declares
|
|
44
|
+
* a mode whose piece is absent FAILS STARTUP ({@link assertRouteVerifiable}, #1362), so none of the
|
|
45
|
+
* rows below is ever "not enabled" at request time:
|
|
46
|
+
* - shared-secret → constant-time compare vs the {@link AuthConfig} secret VALUE (state). Startup
|
|
47
|
+
* requires an AuthConfig holding the route's key.
|
|
45
48
|
* - jwt → the bound {@link JwtHook} (`parseJwt`, awaited — an app's
|
|
46
|
-
* strategy may reach a JWKS or a datastore).
|
|
47
|
-
*
|
|
49
|
+
* strategy may reach a JWKS or a datastore). Startup requires a JwtHook: JWT needs
|
|
50
|
+
* an app secret + payload shape.
|
|
48
51
|
* - oidc → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}
|
|
49
52
|
* run DIRECTLY — so a server that wires NOTHING still verifies Google OIDC against
|
|
50
53
|
* the endpoint's caller scope (`oidcSameProject()` or `oidcSvcs([...])`).
|
|
51
54
|
* - vendorAuth → the bound {@link VendorAuthVerifier} `verify`s the VENDOR's credential over the
|
|
52
|
-
* retained raw request.
|
|
53
|
-
* vendor call is never waved through because wiring was
|
|
54
|
-
* only — this filter never depends on the outbound
|
|
55
|
+
* retained raw request. Startup requires a verifier whose `supportedVendorNames()` lists the
|
|
56
|
+
* route's name: an unverified vendor call is never waved through because wiring was
|
|
57
|
+
* forgotten. The INBOUND role only — this filter never depends on the outbound
|
|
58
|
+
* VendorAuthMinter (#1358).
|
|
55
59
|
* - apikey → the bound {@link ApiKeyHook} looks the CUSTOMER's key up (async, over the whole
|
|
56
|
-
* header set) and returns the context to seed.
|
|
60
|
+
* header set) and returns the context to seed. Startup requires an ApiKeyHook.
|
|
57
61
|
* - public → BEST-EFFORT jwt parse (only if a JwtHook is bound): stamp the user's context so
|
|
58
62
|
* a logged-out page still knows who is logged in; never fails.
|
|
59
63
|
* - local-only → serve only when {@link RuntimeLocality} says this process is a developer's
|
|
@@ -79,6 +83,92 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
79
83
|
this.vendorAuthVerifier = vendorAuthVerifier;
|
|
80
84
|
this.apiKeyHook = apiKeyHook;
|
|
81
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* STARTUP check (#1362), called by route registration for EVERY served route: each auth mode the
|
|
88
|
+
* route declares must be verifiable by what is bound, or boot fails naming the endpoint, the mode
|
|
89
|
+
* and the exact binding line. STRICT — a multi-method route (`[oidcSameProject(), vendorAuth('x')]`)
|
|
90
|
+
* with no verifier fails too: a declared alternative nobody can verify is dead, and is almost
|
|
91
|
+
* always a wiring mistake. OIDC needs no app binding (the framework verifier) and a public route
|
|
92
|
+
* none.
|
|
93
|
+
*
|
|
94
|
+
* Reads bindings this filter already holds and nothing else: it never calls `verify`, `parseJwt`
|
|
95
|
+
* or `verifyApiKey`, touches no network, and constructs nothing (#1358's cycle stays closed).
|
|
96
|
+
* The request path resolves each piece through the SAME `require*` methods, so there is one
|
|
97
|
+
* decision, made at boot. The one boot-only check is a verifier's `supportedVendorNames()`: at request time
|
|
98
|
+
* the verifier's OWN dispatch stays the backstop for a name it does not know, and refuses (401).
|
|
99
|
+
*/
|
|
100
|
+
assertRouteVerifiable(routeMeta) {
|
|
101
|
+
const auth = routeMeta.authMeta;
|
|
102
|
+
if (!auth)
|
|
103
|
+
throw new core_util_1.ApiImplementationError('Every endpoint requires @WpAuth or @WpAuthPublic.');
|
|
104
|
+
for (const method of auth.methods) {
|
|
105
|
+
switch (method.kind) {
|
|
106
|
+
case 'jwt':
|
|
107
|
+
this.requireJwtHook(routeMeta);
|
|
108
|
+
break;
|
|
109
|
+
case 'shared-secret':
|
|
110
|
+
this.requireSharedSecrets(method.secretKey, routeMeta);
|
|
111
|
+
break;
|
|
112
|
+
case 'vendorAuth':
|
|
113
|
+
this.assertVendorDeclared(this.requireVendorVerifier(method.name, routeMeta), method.name, routeMeta);
|
|
114
|
+
break;
|
|
115
|
+
case 'apikey':
|
|
116
|
+
this.requireApiKeyHook(method.regime, routeMeta);
|
|
117
|
+
break;
|
|
118
|
+
case 'oidc':
|
|
119
|
+
case 'public':
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// webpieces-disable no-function-outside-class -- static pure formatter of the route identity, kept beside its only callers
|
|
125
|
+
static endpointOf(routeMeta) {
|
|
126
|
+
return `${routeMeta.apiName ?? routeMeta.controllerClassName ?? 'UnknownApi'}.${routeMeta.methodName} (${routeMeta.httpMethod} ${routeMeta.path})`;
|
|
127
|
+
}
|
|
128
|
+
unverifiable(routeMeta, mode, problem, bindLine) {
|
|
129
|
+
const endpoint = AuthFilter_1.endpointOf(routeMeta);
|
|
130
|
+
return new UnverifiableAuthModeError_1.UnverifiableAuthModeError(`Endpoint ${endpoint} declares ${mode}, but ${problem}, so this server cannot verify it and ` +
|
|
131
|
+
`refuses to start. Add:\n ${bindLine}\n` +
|
|
132
|
+
`(Or remove ${mode} from the endpoint's @WpAuth if this server should not accept it.)`, endpoint, mode);
|
|
133
|
+
}
|
|
134
|
+
requireJwtHook(routeMeta) {
|
|
135
|
+
if (this.jwtHook)
|
|
136
|
+
return this.jwtHook;
|
|
137
|
+
throw this.unverifiable(routeMeta, 'jwt()', 'no JwtHook is bound', 'binder.bind(JWT_HOOK).to(YourJwtHook).inSingletonScope();');
|
|
138
|
+
}
|
|
139
|
+
requireSharedSecrets(secretKey, routeMeta) {
|
|
140
|
+
const config = this.authConfig;
|
|
141
|
+
const mode = `sharedSecret('${secretKey}')`;
|
|
142
|
+
if (!config) {
|
|
143
|
+
throw this.unverifiable(routeMeta, mode, 'no AuthConfig is bound', `binder.bind(AUTH_CONFIG).to(YourAuthConfig).inSingletonScope(); // YourAuthConfig extends AuthConfig and passes { '${secretKey}': new SharedSecrets(secret1, secret2) }`);
|
|
144
|
+
}
|
|
145
|
+
const accepted = config.sharedSecrets[secretKey];
|
|
146
|
+
if (accepted)
|
|
147
|
+
return accepted;
|
|
148
|
+
throw this.unverifiable(routeMeta, mode, `the bound AuthConfig (${config.constructor.name}) holds no SharedSecrets for '${secretKey}'`, `'${secretKey}': new SharedSecrets(secret1, secret2), // add to the sharedSecrets ${config.constructor.name} passes to AuthConfig, which AUTH_CONFIG is bound to`);
|
|
149
|
+
}
|
|
150
|
+
requireVendorVerifier(name, routeMeta) {
|
|
151
|
+
const verifier = this.vendorAuthVerifier;
|
|
152
|
+
const mode = `vendorAuth('${name}')`;
|
|
153
|
+
if (!verifier) {
|
|
154
|
+
throw this.unverifiable(routeMeta, mode, 'no VendorAuthVerifier is bound (a VENDOR_AUTH_MINTER binding only signs OUTBOUND calls)', 'binder.bind(VENDOR_AUTH_VERIFIER).to(YourVendorAuthVerifier).inSingletonScope();');
|
|
155
|
+
}
|
|
156
|
+
return verifier;
|
|
157
|
+
}
|
|
158
|
+
/** Boot only: the bound verifier must DECLARE the route's vendor (#1362). Never calls verify. */
|
|
159
|
+
assertVendorDeclared(verifier, name, routeMeta) {
|
|
160
|
+
const supported = verifier.supportedVendorNames();
|
|
161
|
+
if (supported.includes(name))
|
|
162
|
+
return;
|
|
163
|
+
const mode = `vendorAuth('${name}')`;
|
|
164
|
+
const declared = supported.map((n) => `'${n}'`).join(', ');
|
|
165
|
+
throw this.unverifiable(routeMeta, mode, `the bound VendorAuthVerifier (${verifier.constructor.name}) returns [${declared}] from supportedVendorNames(), which does not include '${name}'`, `'${name}' to ${verifier.constructor.name}.supportedVendorNames() (return [${declared}, '${name}']) AND handle '${name}' in its verify()`);
|
|
166
|
+
}
|
|
167
|
+
requireApiKeyHook(regime, routeMeta) {
|
|
168
|
+
if (this.apiKeyHook)
|
|
169
|
+
return this.apiKeyHook;
|
|
170
|
+
throw this.unverifiable(routeMeta, `apiKey('${regime}', credentials)`, 'no ApiKeyHook is bound', 'binder.bind(API_KEY_HOOK).to(YourApiKeyHook).inSingletonScope();');
|
|
171
|
+
}
|
|
82
172
|
// webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility
|
|
83
173
|
async filter(meta, nextFilter) {
|
|
84
174
|
const auth = meta.routeMeta.authMeta;
|
|
@@ -133,13 +223,13 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
133
223
|
async authenticateMethod(mode, header, meta, oidc) {
|
|
134
224
|
switch (mode.kind) {
|
|
135
225
|
case 'jwt':
|
|
136
|
-
await this.enforceJwt(header);
|
|
226
|
+
await this.enforceJwt(header, meta.routeMeta);
|
|
137
227
|
return;
|
|
138
228
|
case 'oidc':
|
|
139
229
|
await this.enforceOidc(header, oidc ?? [mode]);
|
|
140
230
|
return;
|
|
141
231
|
case 'shared-secret':
|
|
142
|
-
this.enforceSharedSecret(this.credential(header, SHARED_SECRET_SCHEME), mode.secretKey);
|
|
232
|
+
this.enforceSharedSecret(this.credential(header, SHARED_SECRET_SCHEME), mode.secretKey, meta.routeMeta);
|
|
143
233
|
core_context_1.RequestContext.putTrusted(AuthorizationHook_1.VERIFIED_MACHINE_CALLER, new AuthorizationHook_1.VerifiedMachineCaller('shared-secret', mode.secretKey));
|
|
144
234
|
return;
|
|
145
235
|
case 'vendorAuth':
|
|
@@ -207,27 +297,23 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
207
297
|
}
|
|
208
298
|
/**
|
|
209
299
|
* `vendorAuth(name)`: hand the app's {@link VendorAuthVerifier} the verbatim request and let it call the
|
|
210
|
-
* VENDOR's own validator.
|
|
300
|
+
* VENDOR's own validator. A missing verifier, or one not declaring `name`, is not a case here: it
|
|
301
|
+
* failed STARTUP ({@link assertRouteVerifiable}). Two ways to fail, both 401, both before the
|
|
302
|
+
* controller is entered:
|
|
211
303
|
*
|
|
212
|
-
* 1. NO
|
|
213
|
-
* an unverified vendor call must never be waved through because wiring was forgotten.
|
|
214
|
-
* 2. NO raw request — the transport kept no bytes. `assertEveryVendorAuthEndpointRetainsRawBody`
|
|
304
|
+
* 1. NO raw request — the transport kept no bytes. `assertEveryVendorAuthEndpointRetainsRawBody`
|
|
215
305
|
* normally makes this a startup error, so reaching it means either a hand-registered route or
|
|
216
306
|
* an in-process caller (a spec) that published an HttpRequest with no {@link RawRequest}. The
|
|
217
307
|
* message says which fix applies rather than leaving a bare 401.
|
|
218
|
-
*
|
|
308
|
+
* 2. The verifier threw — the credential did not verify (including a name its own dispatch does
|
|
309
|
+
* not know: that stays the request-time backstop).
|
|
219
310
|
*
|
|
220
|
-
* Case
|
|
311
|
+
* Case 1 is checked HERE and nowhere else. {@link hasRawBytes} narrows the request to
|
|
221
312
|
* {@link RawHttpRequest} at this one gate, so the verifier's signature promises `raw` is present and
|
|
222
313
|
* no vendor implementation ever writes `raw!` or a guard of its own.
|
|
223
314
|
*/
|
|
224
315
|
async enforceVendorAuth(name, meta) {
|
|
225
|
-
|
|
226
|
-
log.warn(`Refusing vendorAuth('${name}') endpoint ${meta.routeMeta.path}: no VendorAuthVerifier is bound. ` +
|
|
227
|
-
`Bind the INBOUND role (binder.bind(VENDOR_AUTH_VERIFIER).to(YourVendorAuthVerifier).inSingletonScope()) ` +
|
|
228
|
-
`to enable vendor verification; a VENDOR_AUTH_MINTER binding only signs outbound calls.`);
|
|
229
|
-
throw new core_util_1.ApiUnauthorizedError('Vendor auth is not enabled on this server');
|
|
230
|
-
}
|
|
316
|
+
const verifier = this.requireVendorVerifier(name, meta.routeMeta);
|
|
231
317
|
const request = core_context_1.RequestContext.getRequest();
|
|
232
318
|
if (!this.hasRawBytes(request)) {
|
|
233
319
|
log.warn(`Refusing vendorAuth('${name}') endpoint ${meta.routeMeta.path}: the inbound request carries ` +
|
|
@@ -237,7 +323,7 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
237
323
|
}
|
|
238
324
|
// Throws ApiUnauthorizedError to deny. On success the vendor account the signature proved is
|
|
239
325
|
// stamped through the SAME path a jwt or api-key caller takes.
|
|
240
|
-
const caller = await
|
|
326
|
+
const caller = await verifier.verify(name, request);
|
|
241
327
|
this.callerContext.publish(caller);
|
|
242
328
|
}
|
|
243
329
|
/**
|
|
@@ -252,25 +338,20 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
252
338
|
/**
|
|
253
339
|
* `apiKey(regime, credentials)`: hand the app's {@link ApiKeyHook} the regime name and the inbound
|
|
254
340
|
* headers and let it look the CUSTOMER's key up. The declared `credentials` are NOT read here — they
|
|
255
|
-
* describe the contract for generators; the hook owns extraction.
|
|
256
|
-
* before the
|
|
341
|
+
* describe the contract for generators; the hook owns extraction. A missing hook is not a case here:
|
|
342
|
+
* it failed STARTUP ({@link assertRouteVerifiable}). Two ways to fail, both 401, both before the
|
|
343
|
+
* controller:
|
|
257
344
|
*
|
|
258
|
-
* 1. NO
|
|
259
|
-
* unverified partner request must never be waved through because wiring was forgotten.
|
|
260
|
-
* 2. NO inbound request in scope — there are no headers to read, so there is nothing to verify. That
|
|
345
|
+
* 1. NO inbound request in scope — there are no headers to read, so there is nothing to verify. That
|
|
261
346
|
* means a caller drove this route without publishing an HttpRequest; the message says so rather
|
|
262
347
|
* than leaving a bare 401.
|
|
263
|
-
*
|
|
348
|
+
* 2. The hook threw — the key, or the key/organization pair, did not check out.
|
|
264
349
|
*
|
|
265
350
|
* On success the hook's {@link AuthenticatedCaller} is stamped exactly as a jwt parse's is, which is
|
|
266
351
|
* what puts the resolved organization into `RequestContext` for every downstream repository call.
|
|
267
352
|
*/
|
|
268
353
|
async enforceApiKey(regime, meta) {
|
|
269
|
-
|
|
270
|
-
log.warn(`Refusing apiKey('${regime}') endpoint ${meta.routeMeta.path}: no ApiKeyHook is bound. ` +
|
|
271
|
-
`Bind one (options.bind(API_KEY_HOOK).to(YourApiKeyHook)) to enable api-key verification.`);
|
|
272
|
-
throw new core_util_1.ApiUnauthorizedError('API-key auth is not enabled on this server');
|
|
273
|
-
}
|
|
354
|
+
const hook = this.requireApiKeyHook(regime, meta.routeMeta);
|
|
274
355
|
const request = core_context_1.RequestContext.getRequest();
|
|
275
356
|
if (!request) {
|
|
276
357
|
log.warn(`Refusing apiKey('${regime}') endpoint ${meta.routeMeta.path}: no inbound HttpRequest is ` +
|
|
@@ -280,7 +361,7 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
280
361
|
}
|
|
281
362
|
// Throws ApiUnauthorizedError to deny. The hook gets the WHOLE request so it can cross-check
|
|
282
363
|
// the key against a second header (the organization the customer is acting for).
|
|
283
|
-
const caller = await
|
|
364
|
+
const caller = await hook.verifyApiKey(regime, request);
|
|
284
365
|
this.callerContext.publish(caller);
|
|
285
366
|
}
|
|
286
367
|
/**
|
|
@@ -357,13 +438,12 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
357
438
|
// Same shape as an unregistered route — see the method doc for why this is not a 403.
|
|
358
439
|
throw new core_util_1.ApiEndpointNotFoundError(`No endpoint at ${meta.routeMeta.path}`);
|
|
359
440
|
}
|
|
360
|
-
async enforceJwt(header) {
|
|
441
|
+
async enforceJwt(header, routeMeta) {
|
|
442
|
+
const hook = this.requireJwtHook(routeMeta);
|
|
361
443
|
const token = this.credential(header, BEARER_SCHEME);
|
|
362
444
|
if (!token)
|
|
363
445
|
throw new core_util_1.ApiUnauthorizedError('Authentication required');
|
|
364
|
-
|
|
365
|
-
throw new core_util_1.ApiUnauthorizedError('User-JWT auth is not enabled on this server');
|
|
366
|
-
this.callerContext.publish(await this.jwtHook.parseJwt(token));
|
|
446
|
+
this.callerContext.publish(await hook.parseJwt(token));
|
|
367
447
|
}
|
|
368
448
|
async enforceOidc(header, methods) {
|
|
369
449
|
const token = this.credential(header, BEARER_SCHEME);
|
|
@@ -377,9 +457,9 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
|
|
|
377
457
|
core_context_1.RequestContext.putTrusted(AuthorizationHook_1.VERIFIED_MACHINE_CALLER, new AuthorizationHook_1.VerifiedMachineCaller(identity.mechanism, identity.identity));
|
|
378
458
|
}
|
|
379
459
|
/** `provided` is the Authorization bearer value — the secret itself, same header as a JWT. */
|
|
380
|
-
enforceSharedSecret(provided, secretKey) {
|
|
381
|
-
const accepted = this.
|
|
382
|
-
if (!
|
|
460
|
+
enforceSharedSecret(provided, secretKey, routeMeta) {
|
|
461
|
+
const accepted = this.requireSharedSecrets(secretKey, routeMeta);
|
|
462
|
+
if (!provided || !this.matchesEither(provided, accepted)) {
|
|
383
463
|
throw new core_util_1.ApiUnauthorizedError('Invalid shared secret for sharedSecret(...) endpoint');
|
|
384
464
|
}
|
|
385
465
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-routing/src/filters/AuthFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, optional } from 'inversify';\nimport { timingSafeEqual } from 'crypto';\nimport {\n provideFrameworkSingleton,\n HttpRequest,\n RawHttpRequest,\n RequestContext,\n VendorAuthVerifier,\n VENDOR_AUTH_VERIFIER,\n} from '@webpieces/core-context';\nimport {\n AuthMode,\n OidcAuthMethod,\n OidcAuthMethods,\n ApiEndpointNotFoundError,\n ApiUnauthorizedError,\n ApiImplementationError,\n LogManager,\n RuntimeLocality,\n Surface,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { Filter, Service } from '@webpieces/core-util';\nimport { WpResponse } from '../WpResponse';\nimport { MethodMeta } from '../MethodMeta';\nimport { AuthConfig, AUTH_CONFIG, SharedSecrets } from '../AuthConfig';\nimport {\n ApiKeyHook,\n API_KEY_HOOK,\n JwtHook,\n JWT_HOOK,\n OidcHook,\n OIDC_HOOK,\n} from '../AuthHooks';\nimport { AuthenticatedCallerContext } from '../AuthenticatedCallerContext';\nimport { VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from '../AuthorizationHook';\nimport { DefaultOidcVerifier } from '../DefaultOidcVerifier';\n\nconst log = LogManager.getLogger('AuthFilter');\n\n/**\n * The ONE credential header, read straight off the inbound HttpRequest.\n *\n * Deliberately NOT a ContextKey: a ContextKey with an httpHeader is a TRANSFERRED key, which would\n * put the caller's credential into RequestContext and hence onto every outbound call this service\n * makes, and onto every Cloud Task it enqueues. A credential belongs to ONE request hop.\n */\nconst AUTHORIZATION_HEADER = 'authorization';\n\n/**\n * The scheme (first word of the Authorization value) names WHICH credential follows, so a secret\n * can never be mistaken for a token, nor accepted where the other was expected:\n *\n * Authorization: Bearer <user JWT | service OIDC token>\n * Authorization: Webpieces <sharedSecret(...) value>\n *\n * The scheme is REQUIRED. A bare value with no scheme is rejected.\n */\nconst BEARER_SCHEME = 'Bearer';\nconst SHARED_SECRET_SCHEME = 'Webpieces';\n\n/**\n * AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on every\n * route. It is TRANSPORT-NEUTRAL: it reads the raw credential from the {@link HttpRequest} in\n * RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.\n *\n * It enforces the endpoint's AuthMode from separately-bound pieces, each OPTIONAL except the OIDC\n * default:\n * - shared-secret → constant-time compare vs the {@link AuthConfig} secret VALUE (state). No\n * AuthConfig bound → no accepted secret → fail fast (401).\n * - jwt → the bound {@link JwtHook} (`parseJwt`, awaited — an app's\n * strategy may reach a JWKS or a datastore). No JwtHook bound → \"not enabled\"\n * (401): JWT needs an app secret + payload shape.\n * - oidc → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}\n * run DIRECTLY — so a server that wires NOTHING still verifies Google OIDC against\n * the endpoint's caller scope (`oidcSameProject()` or `oidcSvcs([...])`).\n * - vendorAuth → the bound {@link VendorAuthVerifier} `verify`s the VENDOR's credential over the\n * retained raw request. No VendorAuthVerifier bound → 401, like jwt: an unverified\n * vendor call is never waved through because wiring was forgotten. The INBOUND role\n * only — this filter never depends on the outbound VendorAuthMinter (#1358).\n * - apikey → the bound {@link ApiKeyHook} looks the CUSTOMER's key up (async, over the whole\n * header set) and returns the context to seed. No ApiKeyHook bound → 401, like jwt.\n * - public → BEST-EFFORT jwt parse (only if a JwtHook is bound): stamp the user's context so\n * a logged-out page still knows who is logged in; never fails.\n * - local-only → serve only when {@link RuntimeLocality} says this process is a developer's\n * machine; otherwise 404, indistinguishable from the route not existing (which,\n * off-local, it does not — `ApiRoutingFactory` never registered it).\n *\n * Zero wiring = OIDC just works; an app only binds the hooks it actually uses.\n */\n@provideFrameworkSingleton()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\nexport class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private readonly callerContext = new AuthenticatedCallerContext();\n\n constructor(\n // Framework default, always available — verifies Google OIDC with zero app wiring.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- AuthFilter is DI-resolved via the esbuild/vitest path, which elides type-only imports (no design:paramtypes), so every param needs its explicit token\n @inject(DefaultOidcVerifier) private readonly oidcVerifier: DefaultOidcVerifier,\n // @optional: only bind an AuthConfig to enable sharedSecret(...) endpoints.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(AUTH_CONFIG) private readonly authConfig?: AuthConfig,\n // @optional: only bind a JwtHook to enable jwt() endpoints.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(JWT_HOOK) private readonly jwtHook?: JwtHook<never>,\n // @optional: only bind an OidcHook to OVERRIDE the DefaultOidcVerifier caller policy.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(OIDC_HOOK) private readonly oidcHook?: OidcHook,\n // @optional: only bind a VendorAuthVerifier to enable vendorAuth(...) endpoints. Unbound = every\n // such endpoint 401s, which is the ONE default that must not be the other way round.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional()\n @inject(VENDOR_AUTH_VERIFIER)\n private readonly vendorAuthVerifier?: VendorAuthVerifier,\n // @optional: only bind an ApiKeyHook to enable apiKey(...) endpoints. Unbound = every such\n // endpoint 401s, for the same reason as the vendor hook above.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(API_KEY_HOOK) private readonly apiKeyHook?: ApiKeyHook,\n ) {\n super();\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\n override async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const auth = meta.routeMeta.authMeta;\n if (!auth) throw new ApiImplementationError('Every endpoint requires @WpAuth or @WpAuthPublic.');\n if (meta.routeMeta.localOnly) this.enforceLocalOnly(meta);\n if (meta.invocationAuthentication) {\n meta.invocationAuthentication.assertTarget(meta.routeMeta);\n return nextFilter.invoke(meta);\n }\n const header = RequestContext.getRequest()?.getHeader(AUTHORIZATION_HEADER);\n if (auth.methods[0].kind === 'public') {\n await this.bestEffortJwt(header);\n this.callerContext.reconcileWireTrust(false);\n return nextFilter.invoke(meta);\n }\n const selected = await this.authenticateAlternatives(auth.methods, header, meta);\n this.applySurface(selected);\n this.callerContext.reconcileWireTrust(AuthFilter.verifiesCaller(selected));\n return nextFilter.invoke(meta);\n }\n\n /**\n * Try each declared credential in order. The endpoint's OIDC methods (one `oidcSameProject()` and\n * one `oidcSvcs([...])` may pair) are ONE bearer credential, so they are ONE alternative, tried at\n * the position of the first: the token is verified once against all of them.\n */\n private async authenticateAlternatives(methods: readonly AuthMode[], header: string | undefined, meta: MethodMeta): Promise<AuthMode> {\n const oidc = AuthFilter.oidcMethodsOf(methods);\n const alternatives = methods.filter((method: AuthMode) => method.kind !== 'oidc' || method === oidc?.[0]);\n for (const method of alternatives) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- failed credentials may try another declared authenticator; invariants propagate\n try {\n await this.authenticateMethod(method, header, meta, oidc);\n return method;\n } catch (err: unknown) { // webpieces-disable no-any-unknown -- structured credential rejection at the boundary\n const error = toError(err);\n if (!(error instanceof ApiUnauthorizedError) || alternatives.length === 1) throw error;\n }\n }\n throw new ApiUnauthorizedError('No declared credential mechanism authenticated this request.');\n }\n\n /** The endpoint's OIDC methods in declaration order, or undefined when it declares none. */\n // webpieces-disable no-function-outside-class -- static pure reader of the AuthMode list, kept beside its only caller\n private static oidcMethodsOf(methods: readonly AuthMode[]): OidcAuthMethods | undefined {\n const oidc = methods.filter((method: AuthMode): method is OidcAuthMethod => method.kind === 'oidc');\n const first = oidc[0];\n return first === undefined ? undefined : [first, ...oidc.slice(1)];\n }\n\n private async authenticateMethod(mode: AuthMode, header: string | undefined, meta: MethodMeta, oidc: OidcAuthMethods | undefined): Promise<void> {\n switch (mode.kind) {\n case 'jwt': await this.enforceJwt(header); return;\n case 'oidc': await this.enforceOidc(header, oidc ?? [mode]); return;\n case 'shared-secret':\n this.enforceSharedSecret(this.credential(header, SHARED_SECRET_SCHEME), mode.secretKey);\n RequestContext.putTrusted(VERIFIED_MACHINE_CALLER, new VerifiedMachineCaller('shared-secret', mode.secretKey));\n return;\n case 'vendorAuth': await this.enforceVendorAuth(mode.name, meta); return;\n case 'apikey': await this.enforceApiKey(mode.regime, meta); return;\n case 'public': throw new ApiImplementationError('Public is not a protected auth alternative.');\n }\n }\n\n /**\n * Stamp WHICH KIND OF CALLER this is, from the auth mode that matched — `jwt()` is a browser\n * GUI, `apiKey(...)` is an external partner. See {@link Surface}.\n *\n * SET AT THE EDGE ONLY. The surface belongs to the ORIGINAL caller, so a hop that already has one\n * INHERITS it unchanged and this returns immediately. Two different callers put a value there\n * before this runs, and neither may be overwritten:\n *\n * - the MCP bridge, which stamps `llm` before invoking a `@WpAuthorization` tool through `ApiFactory`\n * — that in-process hop still runs this filter, and its HTTP auth mode says nothing about the\n * model on the other end;\n * - an OIDC (`oidcSameProject()` / `oidcSvcs(...)`) service-to-service hop, whose caller's surface arrived on the wire. OIDC is an\n * internal hop, NOT a surface of its own, so it deliberately maps to `undefined` below and the\n * propagated value survives.\n *\n * Running BEFORE {@link reconcileWireTrust} is what makes the trusted-header rule work for this\n * key: on a `jwt()` edge a caller who also SENDS `x-wp-surface` must match the value the\n * framework just derived, and on a public route nothing vouches for it at all, so it is a 401.\n */\n private applySurface(mode: AuthMode): void {\n if (RequestContext.getTrusted(WebpiecesCoreHeaders.SURFACE) !== undefined) {\n return;\n }\n const surface = AuthFilter.surfaceFor(mode);\n if (surface !== undefined) {\n RequestContext.putTrusted(WebpiecesCoreHeaders.SURFACE, surface);\n }\n }\n\n /**\n * The auth mode -> {@link Surface} table, and the ONE place it is written down.\n *\n * `undefined` means \"this mode is not a surface\", which is a different statement from \"unknown\":\n * `oidc` and `shared-secret` are internal hops that RELAY somebody else's surface, `vendorAuth`'s\n * sender is a vendor with no webpieces surface at all, and `local-only` is a developer's own box.\n *\n * An exhaustive switch with NO `default`, like {@link verifiesCaller} and\n * `DestinationTrust.forAuthMode`: a new AuthMode kind must fail to compile here rather than\n * silently landing on `gui` (which would publish 266 to a partner) or on `public-api` (which would\n * publish a real 4xx to a GUI).\n */\n // webpieces-disable no-function-outside-class -- static pure mapping from the AuthMode union, kept beside its only caller (mirrors verifiesCaller)\n private static surfaceFor(mode: AuthMode): Surface | undefined {\n switch (mode.kind) {\n case 'jwt':\n return 'gui';\n case 'apikey':\n return 'public-api';\n case 'oidc':\n case 'shared-secret':\n case 'vendorAuth':\n case 'public':\n return undefined;\n }\n }\n\n /**\n * `vendorAuth(name)`: hand the app's {@link VendorAuthVerifier} the verbatim request and let it call the\n * VENDOR's own validator. Three ways to fail, all 401, all before the controller is entered:\n *\n * 1. NO verifier bound — the endpoint is not enabled. Matches {@link JwtHook}'s documented behavior;\n * an unverified vendor call must never be waved through because wiring was forgotten.\n * 2. NO raw request — the transport kept no bytes. `assertEveryVendorAuthEndpointRetainsRawBody`\n * normally makes this a startup error, so reaching it means either a hand-registered route or\n * an in-process caller (a spec) that published an HttpRequest with no {@link RawRequest}. The\n * message says which fix applies rather than leaving a bare 401.\n * 3. The verifier threw — the credential did not verify.\n *\n * Case 2 is checked HERE and nowhere else. {@link hasRawBytes} narrows the request to\n * {@link RawHttpRequest} at this one gate, so the verifier's signature promises `raw` is present and\n * no vendor implementation ever writes `raw!` or a guard of its own.\n */\n private async enforceVendorAuth(name: string, meta: MethodMeta): Promise<void> {\n if (!this.vendorAuthVerifier) {\n log.warn(\n `Refusing vendorAuth('${name}') endpoint ${meta.routeMeta.path}: no VendorAuthVerifier is bound. ` +\n `Bind the INBOUND role (binder.bind(VENDOR_AUTH_VERIFIER).to(YourVendorAuthVerifier).inSingletonScope()) ` +\n `to enable vendor verification; a VENDOR_AUTH_MINTER binding only signs outbound calls.`,\n );\n throw new ApiUnauthorizedError('Vendor auth is not enabled on this server');\n }\n const request = RequestContext.getRequest();\n if (!this.hasRawBytes(request)) {\n log.warn(\n `Refusing vendorAuth('${name}') endpoint ${meta.routeMeta.path}: the inbound request carries ` +\n `no raw bytes. Declare @Endpoint(POST, path, WRITE, EXTERNAL_INBOUND, { calledBy: '${name}', rawBody: true }); a ` +\n `spec driving this route in-process must publish an HttpRequest built with a RawRequest.`,\n );\n throw new ApiUnauthorizedError(\n 'Vendor credential cannot be verified: no raw request was retained',\n );\n }\n // Throws ApiUnauthorizedError to deny. On success the vendor account the signature proved is\n // stamped through the SAME path a jwt or api-key caller takes.\n const caller = await this.vendorAuthVerifier.verify(name, request);\n this.callerContext.publish(caller);\n }\n\n /**\n * The ONE place the framework decides a request carries the verbatim bytes. A TYPE PREDICATE, so\n * the `true` branch hands {@link enforceVendorAuth} a {@link RawHttpRequest} with no cast and no\n * non-null assertion — the bad state stops being representable past this line rather than being\n * re-thrown about by every hook.\n */\n private hasRawBytes(request: HttpRequest | undefined): request is RawHttpRequest {\n return request?.raw !== undefined;\n }\n\n /**\n * `apiKey(regime, credentials)`: hand the app's {@link ApiKeyHook} the regime name and the inbound\n * headers and let it look the CUSTOMER's key up. The declared `credentials` are NOT read here — they\n * describe the contract for generators; the hook owns extraction. Three ways to fail, all 401, all\n * before the controller:\n *\n * 1. NO hook bound — the endpoint is not enabled. Matches {@link JwtHook}'s documented behavior; an\n * unverified partner request must never be waved through because wiring was forgotten.\n * 2. NO inbound request in scope — there are no headers to read, so there is nothing to verify. That\n * means a caller drove this route without publishing an HttpRequest; the message says so rather\n * than leaving a bare 401.\n * 3. The hook threw — the key, or the key/organization pair, did not check out.\n *\n * On success the hook's {@link AuthenticatedCaller} is stamped exactly as a jwt parse's is, which is\n * what puts the resolved organization into `RequestContext` for every downstream repository call.\n */\n private async enforceApiKey(regime: string, meta: MethodMeta): Promise<void> {\n if (!this.apiKeyHook) {\n log.warn(\n `Refusing apiKey('${regime}') endpoint ${meta.routeMeta.path}: no ApiKeyHook is bound. ` +\n `Bind one (options.bind(API_KEY_HOOK).to(YourApiKeyHook)) to enable api-key verification.`,\n );\n throw new ApiUnauthorizedError('API-key auth is not enabled on this server');\n }\n const request = RequestContext.getRequest();\n if (!request) {\n log.warn(\n `Refusing apiKey('${regime}') endpoint ${meta.routeMeta.path}: no inbound HttpRequest is ` +\n `in scope, so the hook has no headers to read. A spec driving this route in-process must ` +\n `publish an HttpRequest carrying the api-key headers.`,\n );\n throw new ApiUnauthorizedError(\n 'API key cannot be verified: no inbound request was published',\n );\n }\n // Throws ApiUnauthorizedError to deny. The hook gets the WHOLE request so it can cross-check\n // the key against a second header (the organization the customer is acting for).\n const caller = await this.apiKeyHook.verifyApiKey(regime, request);\n this.callerContext.publish(caller);\n }\n\n /**\n * Does this mode authenticate the CALLER ITSELF (as opposed to a user, or nobody)? The INBOUND\n * twin of {@link DestinationTrust.forAuthMode}, and deliberately the same question: the client\n * omits trusted keys for a destination that cannot verify it, and the server rejects trusted keys\n * on a route that cannot verify the sender. One rule, two ends — if they disagreed, every call\n * would fail with a 401 that looks like a framework bug.\n *\n * - `oidc` / `shared-secret` → TRUE. An internal service is on the other end and the trusted\n * context it forwarded may be believed. This is what makes cross-service identity propagation\n * work.\n * - `jwt` / `public` → FALSE. A user JWT proves who the USER is; the SENDER is still whoever\n * holds the token, i.e. a browser.\n * - `local-only` → FALSE. It verifies WHERE WE ARE RUNNING, not who is calling — anything on\n * localhost reaches it, and it has no authenticator, so nothing can ever vouch for an inbound\n * trusted header. Any such header therefore rejects the request, which is exactly right.\n *\n * - `apikey` → FALSE. See the comment on that branch: the sender is a CUSTOMER.\n *\n * An exhaustive switch with NO `default`, returning on every branch: a new AuthMode kind is a\n * COMPILE error here (TS7030, no ending return) rather than silently landing on one posture. The\n * boolean expression this replaced defaulted every future mode to \"not verified\" — the safe\n * answer, but arrived at by accident rather than by decision.\n */\n // webpieces-disable no-function-outside-class -- static pure mapping from the AuthMode union, kept beside its only caller (mirrors DestinationTrust.forAuthMode)\n private static verifiesCaller(mode: AuthMode): boolean {\n switch (mode.kind) {\n case 'oidc':\n case 'shared-secret':\n return true;\n case 'jwt':\n case 'public':\n // `vendorAuth` DOES authenticate its sender — but the sender is an outside VENDOR, not a\n // peer in this repo, and a vendor neither speaks nor forwards webpieces context headers.\n // So there is no forwarded identity to believe, and admitting one would mean trusting a\n // key a vendor's payload could carry. Same answer as the OUTBOUND half\n // (DestinationTrust.forAuthMode), which is the invariant that keeps the two ends agreeing.\n case 'vendorAuth':\n // `apikey` authenticates the SENDER — but the sender is a CUSTOMER's codebase, not a peer\n // service in this repo, so its forwarded trusted context is exactly what must NOT be\n // believed: admitting it would let a partner assert another customer's org id on the wire.\n // The hook's OWN derived entries still land (applyAuthenticatedCaller), and reconcileWireTrust then\n // admits an inbound trusted header only when the hook independently derived the same value.\n case 'apikey':\n return false;\n }\n }\n\n /**\n * `@WpLocalOnly`: serve only on a developer's machine, and off-local behave EXACTLY as if the\n * endpoint did not exist.\n *\n * WHY 404 AND NOT THE 403 APPS HAND-ROLLED. Off-local the route is not registered at all\n * (`ApiRoutingFactory` skips it), so the ordinary way to reach this path already answers 404. A\n * 403 from here would be a DIFFERENT answer from the same framework for the same endpoint, and\n * the difference is itself the leak: 403 confirms \"this path exists in production, you merely\n * lack permission\", which is a map of the dev-only surface for anyone probing. A local-only\n * endpoint should not admit it exists. Both gates therefore return the same 404, and this one is\n * the backstop for routes registered by hand through `RouteBuilder` rather than by\n * `ApiRoutingFactory`.\n *\n * The log line names WHICH reason applies, because \"you are deployed\" and \"nobody declared a\n * locality\" have completely different fixes and both look like a bare 404 from outside.\n */\n private enforceLocalOnly(meta: MethodMeta): void {\n if (RuntimeLocality.isLocalDevelopment()) {\n return;\n }\n log.warn(\n `Refusing @WpLocalOnly endpoint ${meta.routeMeta.path}: ` +\n (RuntimeLocality.isDeclared()\n ? 'this process declared itself DEPLOYED.'\n : 'no startup declared a RuntimeLocality, so this process is treated as DEPLOYED. ' +\n 'Pass the locality into RuntimeSetupOptions if this really is a developer machine.'),\n );\n // Same shape as an unregistered route — see the method doc for why this is not a 403.\n throw new ApiEndpointNotFoundError(`No endpoint at ${meta.routeMeta.path}`);\n }\n\n private async enforceJwt(header: string | undefined): Promise<void> {\n const token = this.credential(header, BEARER_SCHEME);\n if (!token) throw new ApiUnauthorizedError('Authentication required');\n if (!this.jwtHook) throw new ApiUnauthorizedError('User-JWT auth is not enabled on this server');\n this.callerContext.publish(await this.jwtHook.parseJwt(token));\n }\n\n private async enforceOidc(header: string | undefined, methods: OidcAuthMethods): Promise<void> {\n const token = this.credential(header, BEARER_SCHEME);\n if (!token) throw new ApiUnauthorizedError('Missing OIDC bearer token');\n const identity = this.oidcHook\n ? await this.oidcHook.verifyOidc(token, methods)\n : await this.oidcVerifier.verify(token, methods);\n if (!identity) throw new ApiImplementationError('OIDC verifier must return its verified machine identity.');\n RequestContext.putTrusted(VERIFIED_MACHINE_CALLER, new VerifiedMachineCaller(identity.mechanism, identity.identity));\n }\n\n /** `provided` is the Authorization bearer value — the secret itself, same header as a JWT. */\n private enforceSharedSecret(provided: string | undefined, secretKey: string): void {\n const accepted = this.authConfig?.sharedSecrets[secretKey];\n if (!accepted || !provided || !this.matchesEither(provided, accepted)) {\n throw new ApiUnauthorizedError(\n 'Invalid shared secret for sharedSecret(...) endpoint',\n );\n }\n }\n\n /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */\n private matchesEither(provided: string, accepted: SharedSecrets): boolean {\n return (\n (accepted.secret1 !== '' && this.constantTimeEquals(provided, accepted.secret1)) ||\n (accepted.secret2 !== '' && this.constantTimeEquals(provided, accepted.secret2))\n );\n }\n\n /** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */\n private async bestEffortJwt(header: string | undefined): Promise<void> {\n const token = this.credential(header, BEARER_SCHEME);\n if (!this.jwtHook || !token) {\n return;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort on a public route: a bad/absent token just means \"not logged in\", must not fail the request\n try {\n this.callerContext.publish(await this.jwtHook.parseJwt(token));\n } catch (err: unknown) {\n const error = toError(err);\n if (!(error instanceof ApiUnauthorizedError)) throw error;\n log.debug(\n 'Best-effort JWT parse on a public endpoint failed (treating as anonymous): ',\n error,\n );\n }\n }\n\n /**\n * The credential value IF the header carries the expected scheme, else undefined.\n *\n * Strict: a bare value with no scheme, or a value under the WRONG scheme (a shared secret sent\n * where a JWT is expected), yields undefined and the caller 401s.\n */\n private credential(header: string | undefined, scheme: string): string | undefined {\n if (!header) {\n return undefined;\n }\n const prefix = `${scheme} `;\n return header.startsWith(prefix) ? header.substring(prefix.length) : undefined;\n }\n\n private constantTimeEquals(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) {\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"AuthFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-routing/src/filters/AuthFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, optional } from 'inversify';\nimport { timingSafeEqual } from 'crypto';\nimport {\n provideFrameworkSingleton,\n HttpRequest,\n RawHttpRequest,\n RequestContext,\n VendorAuthVerifier,\n VENDOR_AUTH_VERIFIER,\n} from '@webpieces/core-context';\nimport {\n AuthMode,\n OidcAuthMethod,\n OidcAuthMethods,\n ApiEndpointNotFoundError,\n ApiUnauthorizedError,\n ApiImplementationError,\n LogManager,\n RouteMetadata,\n RuntimeLocality,\n Surface,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { Filter, Service } from '@webpieces/core-util';\nimport { WpResponse } from '../WpResponse';\nimport { MethodMeta } from '../MethodMeta';\nimport { AuthConfig, AUTH_CONFIG, SharedSecrets } from '../AuthConfig';\nimport {\n ApiKeyHook,\n API_KEY_HOOK,\n JwtHook,\n JWT_HOOK,\n OidcHook,\n OIDC_HOOK,\n} from '../AuthHooks';\nimport { AuthenticatedCallerContext } from '../AuthenticatedCallerContext';\nimport { VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from '../AuthorizationHook';\nimport { DefaultOidcVerifier } from '../DefaultOidcVerifier';\nimport { UnverifiableAuthModeError } from '../UnverifiableAuthModeError';\n\nconst log = LogManager.getLogger('AuthFilter');\n\n/**\n * The ONE credential header, read straight off the inbound HttpRequest.\n *\n * Deliberately NOT a ContextKey: a ContextKey with an httpHeader is a TRANSFERRED key, which would\n * put the caller's credential into RequestContext and hence onto every outbound call this service\n * makes, and onto every Cloud Task it enqueues. A credential belongs to ONE request hop.\n */\nconst AUTHORIZATION_HEADER = 'authorization';\n\n/**\n * The scheme (first word of the Authorization value) names WHICH credential follows, so a secret\n * can never be mistaken for a token, nor accepted where the other was expected:\n *\n * Authorization: Bearer <user JWT | service OIDC token>\n * Authorization: Webpieces <sharedSecret(...) value>\n *\n * The scheme is REQUIRED. A bare value with no scheme is rejected.\n */\nconst BEARER_SCHEME = 'Bearer';\nconst SHARED_SECRET_SCHEME = 'Webpieces';\n\n/**\n * AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on every\n * route. It is TRANSPORT-NEUTRAL: it reads the raw credential from the {@link HttpRequest} in\n * RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.\n *\n * It enforces the endpoint's AuthMode from separately-bound pieces, each OPTIONAL except the OIDC\n * default. An app binds only the pieces its SERVED routes declare — and a served route that declares\n * a mode whose piece is absent FAILS STARTUP ({@link assertRouteVerifiable}, #1362), so none of the\n * rows below is ever \"not enabled\" at request time:\n * - shared-secret → constant-time compare vs the {@link AuthConfig} secret VALUE (state). Startup\n * requires an AuthConfig holding the route's key.\n * - jwt → the bound {@link JwtHook} (`parseJwt`, awaited — an app's\n * strategy may reach a JWKS or a datastore). Startup requires a JwtHook: JWT needs\n * an app secret + payload shape.\n * - oidc → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}\n * run DIRECTLY — so a server that wires NOTHING still verifies Google OIDC against\n * the endpoint's caller scope (`oidcSameProject()` or `oidcSvcs([...])`).\n * - vendorAuth → the bound {@link VendorAuthVerifier} `verify`s the VENDOR's credential over the\n * retained raw request. Startup requires a verifier whose `supportedVendorNames()` lists the\n * route's name: an unverified vendor call is never waved through because wiring was\n * forgotten. The INBOUND role only — this filter never depends on the outbound\n * VendorAuthMinter (#1358).\n * - apikey → the bound {@link ApiKeyHook} looks the CUSTOMER's key up (async, over the whole\n * header set) and returns the context to seed. Startup requires an ApiKeyHook.\n * - public → BEST-EFFORT jwt parse (only if a JwtHook is bound): stamp the user's context so\n * a logged-out page still knows who is logged in; never fails.\n * - local-only → serve only when {@link RuntimeLocality} says this process is a developer's\n * machine; otherwise 404, indistinguishable from the route not existing (which,\n * off-local, it does not — `ApiRoutingFactory` never registered it).\n *\n * Zero wiring = OIDC just works; an app only binds the hooks it actually uses.\n */\n@provideFrameworkSingleton()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\nexport class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n private readonly callerContext = new AuthenticatedCallerContext();\n\n constructor(\n // Framework default, always available — verifies Google OIDC with zero app wiring.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- AuthFilter is DI-resolved via the esbuild/vitest path, which elides type-only imports (no design:paramtypes), so every param needs its explicit token\n @inject(DefaultOidcVerifier) private readonly oidcVerifier: DefaultOidcVerifier,\n // @optional: only bind an AuthConfig to enable sharedSecret(...) endpoints (startup refuses a\n // served one without it — see assertRouteVerifiable).\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(AUTH_CONFIG) private readonly authConfig?: AuthConfig,\n // @optional: only bind a JwtHook to enable jwt() endpoints.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(JWT_HOOK) private readonly jwtHook?: JwtHook<never>,\n // @optional: only bind an OidcHook to OVERRIDE the DefaultOidcVerifier caller policy.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(OIDC_HOOK) private readonly oidcHook?: OidcHook,\n // @optional: only bind a VendorAuthVerifier to enable vendorAuth(...) endpoints. Unbound = a\n // server serving one refuses to boot (assertRouteVerifiable), never an allow-all.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional()\n @inject(VENDOR_AUTH_VERIFIER)\n private readonly vendorAuthVerifier?: VendorAuthVerifier,\n // @optional: only bind an ApiKeyHook to enable apiKey(...) endpoints. Unbound = a server serving\n // one refuses to boot, for the same reason as the vendor verifier above.\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- see above: explicit token required for DI-resolved param\n @optional() @inject(API_KEY_HOOK) private readonly apiKeyHook?: ApiKeyHook,\n ) {\n super();\n }\n\n /**\n * STARTUP check (#1362), called by route registration for EVERY served route: each auth mode the\n * route declares must be verifiable by what is bound, or boot fails naming the endpoint, the mode\n * and the exact binding line. STRICT — a multi-method route (`[oidcSameProject(), vendorAuth('x')]`)\n * with no verifier fails too: a declared alternative nobody can verify is dead, and is almost\n * always a wiring mistake. OIDC needs no app binding (the framework verifier) and a public route\n * none.\n *\n * Reads bindings this filter already holds and nothing else: it never calls `verify`, `parseJwt`\n * or `verifyApiKey`, touches no network, and constructs nothing (#1358's cycle stays closed).\n * The request path resolves each piece through the SAME `require*` methods, so there is one\n * decision, made at boot. The one boot-only check is a verifier's `supportedVendorNames()`: at request time\n * the verifier's OWN dispatch stays the backstop for a name it does not know, and refuses (401).\n */\n assertRouteVerifiable(routeMeta: RouteMetadata): void {\n const auth = routeMeta.authMeta;\n if (!auth) throw new ApiImplementationError('Every endpoint requires @WpAuth or @WpAuthPublic.');\n for (const method of auth.methods) {\n switch (method.kind) {\n case 'jwt': this.requireJwtHook(routeMeta); break;\n case 'shared-secret': this.requireSharedSecrets(method.secretKey, routeMeta); break;\n case 'vendorAuth': this.assertVendorDeclared(this.requireVendorVerifier(method.name, routeMeta), method.name, routeMeta); break;\n case 'apikey': this.requireApiKeyHook(method.regime, routeMeta); break;\n case 'oidc':\n case 'public':\n break;\n }\n }\n }\n\n // webpieces-disable no-function-outside-class -- static pure formatter of the route identity, kept beside its only callers\n private static endpointOf(routeMeta: RouteMetadata): string {\n return `${routeMeta.apiName ?? routeMeta.controllerClassName ?? 'UnknownApi'}.${routeMeta.methodName} (${routeMeta.httpMethod} ${routeMeta.path})`;\n }\n\n private unverifiable(routeMeta: RouteMetadata, mode: string, problem: string, bindLine: string): UnverifiableAuthModeError {\n const endpoint = AuthFilter.endpointOf(routeMeta);\n return new UnverifiableAuthModeError(\n `Endpoint ${endpoint} declares ${mode}, but ${problem}, so this server cannot verify it and ` +\n `refuses to start. Add:\\n ${bindLine}\\n` +\n `(Or remove ${mode} from the endpoint's @WpAuth if this server should not accept it.)`,\n endpoint,\n mode,\n );\n }\n\n private requireJwtHook(routeMeta: RouteMetadata): JwtHook<never> {\n if (this.jwtHook) return this.jwtHook;\n throw this.unverifiable(routeMeta, 'jwt()', 'no JwtHook is bound',\n 'binder.bind(JWT_HOOK).to(YourJwtHook).inSingletonScope();');\n }\n\n private requireSharedSecrets(secretKey: string, routeMeta: RouteMetadata): SharedSecrets {\n const config = this.authConfig;\n const mode = `sharedSecret('${secretKey}')`;\n if (!config) {\n throw this.unverifiable(routeMeta, mode, 'no AuthConfig is bound',\n `binder.bind(AUTH_CONFIG).to(YourAuthConfig).inSingletonScope(); // YourAuthConfig extends AuthConfig and passes { '${secretKey}': new SharedSecrets(secret1, secret2) }`);\n }\n const accepted = config.sharedSecrets[secretKey];\n if (accepted) return accepted;\n throw this.unverifiable(routeMeta, mode,\n `the bound AuthConfig (${config.constructor.name}) holds no SharedSecrets for '${secretKey}'`,\n `'${secretKey}': new SharedSecrets(secret1, secret2), // add to the sharedSecrets ${config.constructor.name} passes to AuthConfig, which AUTH_CONFIG is bound to`);\n }\n\n private requireVendorVerifier(name: string, routeMeta: RouteMetadata): VendorAuthVerifier {\n const verifier = this.vendorAuthVerifier;\n const mode = `vendorAuth('${name}')`;\n if (!verifier) {\n throw this.unverifiable(routeMeta, mode,\n 'no VendorAuthVerifier is bound (a VENDOR_AUTH_MINTER binding only signs OUTBOUND calls)',\n 'binder.bind(VENDOR_AUTH_VERIFIER).to(YourVendorAuthVerifier).inSingletonScope();');\n }\n return verifier;\n }\n\n /** Boot only: the bound verifier must DECLARE the route's vendor (#1362). Never calls verify. */\n private assertVendorDeclared(verifier: VendorAuthVerifier, name: string, routeMeta: RouteMetadata): void {\n const supported = verifier.supportedVendorNames();\n if (supported.includes(name)) return;\n const mode = `vendorAuth('${name}')`;\n const declared = supported.map((n: string) => `'${n}'`).join(', ');\n throw this.unverifiable(routeMeta, mode,\n `the bound VendorAuthVerifier (${verifier.constructor.name}) returns [${declared}] from supportedVendorNames(), which does not include '${name}'`,\n `'${name}' to ${verifier.constructor.name}.supportedVendorNames() (return [${declared}, '${name}']) AND handle '${name}' in its verify()`);\n }\n\n private requireApiKeyHook(regime: string, routeMeta: RouteMetadata): ApiKeyHook {\n if (this.apiKeyHook) return this.apiKeyHook;\n throw this.unverifiable(routeMeta, `apiKey('${regime}', credentials)`, 'no ApiKeyHook is bound',\n 'binder.bind(API_KEY_HOOK).to(YourApiKeyHook).inSingletonScope();');\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\n override async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const auth = meta.routeMeta.authMeta;\n if (!auth) throw new ApiImplementationError('Every endpoint requires @WpAuth or @WpAuthPublic.');\n if (meta.routeMeta.localOnly) this.enforceLocalOnly(meta);\n if (meta.invocationAuthentication) {\n meta.invocationAuthentication.assertTarget(meta.routeMeta);\n return nextFilter.invoke(meta);\n }\n const header = RequestContext.getRequest()?.getHeader(AUTHORIZATION_HEADER);\n if (auth.methods[0].kind === 'public') {\n await this.bestEffortJwt(header);\n this.callerContext.reconcileWireTrust(false);\n return nextFilter.invoke(meta);\n }\n const selected = await this.authenticateAlternatives(auth.methods, header, meta);\n this.applySurface(selected);\n this.callerContext.reconcileWireTrust(AuthFilter.verifiesCaller(selected));\n return nextFilter.invoke(meta);\n }\n\n /**\n * Try each declared credential in order. The endpoint's OIDC methods (one `oidcSameProject()` and\n * one `oidcSvcs([...])` may pair) are ONE bearer credential, so they are ONE alternative, tried at\n * the position of the first: the token is verified once against all of them.\n */\n private async authenticateAlternatives(methods: readonly AuthMode[], header: string | undefined, meta: MethodMeta): Promise<AuthMode> {\n const oidc = AuthFilter.oidcMethodsOf(methods);\n const alternatives = methods.filter((method: AuthMode) => method.kind !== 'oidc' || method === oidc?.[0]);\n for (const method of alternatives) {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- failed credentials may try another declared authenticator; invariants propagate\n try {\n await this.authenticateMethod(method, header, meta, oidc);\n return method;\n } catch (err: unknown) { // webpieces-disable no-any-unknown -- structured credential rejection at the boundary\n const error = toError(err);\n if (!(error instanceof ApiUnauthorizedError) || alternatives.length === 1) throw error;\n }\n }\n throw new ApiUnauthorizedError('No declared credential mechanism authenticated this request.');\n }\n\n /** The endpoint's OIDC methods in declaration order, or undefined when it declares none. */\n // webpieces-disable no-function-outside-class -- static pure reader of the AuthMode list, kept beside its only caller\n private static oidcMethodsOf(methods: readonly AuthMode[]): OidcAuthMethods | undefined {\n const oidc = methods.filter((method: AuthMode): method is OidcAuthMethod => method.kind === 'oidc');\n const first = oidc[0];\n return first === undefined ? undefined : [first, ...oidc.slice(1)];\n }\n\n private async authenticateMethod(mode: AuthMode, header: string | undefined, meta: MethodMeta, oidc: OidcAuthMethods | undefined): Promise<void> {\n switch (mode.kind) {\n case 'jwt': await this.enforceJwt(header, meta.routeMeta); return;\n case 'oidc': await this.enforceOidc(header, oidc ?? [mode]); return;\n case 'shared-secret':\n this.enforceSharedSecret(this.credential(header, SHARED_SECRET_SCHEME), mode.secretKey, meta.routeMeta);\n RequestContext.putTrusted(VERIFIED_MACHINE_CALLER, new VerifiedMachineCaller('shared-secret', mode.secretKey));\n return;\n case 'vendorAuth': await this.enforceVendorAuth(mode.name, meta); return;\n case 'apikey': await this.enforceApiKey(mode.regime, meta); return;\n case 'public': throw new ApiImplementationError('Public is not a protected auth alternative.');\n }\n }\n\n /**\n * Stamp WHICH KIND OF CALLER this is, from the auth mode that matched — `jwt()` is a browser\n * GUI, `apiKey(...)` is an external partner. See {@link Surface}.\n *\n * SET AT THE EDGE ONLY. The surface belongs to the ORIGINAL caller, so a hop that already has one\n * INHERITS it unchanged and this returns immediately. Two different callers put a value there\n * before this runs, and neither may be overwritten:\n *\n * - the MCP bridge, which stamps `llm` before invoking a `@WpAuthorization` tool through `ApiFactory`\n * — that in-process hop still runs this filter, and its HTTP auth mode says nothing about the\n * model on the other end;\n * - an OIDC (`oidcSameProject()` / `oidcSvcs(...)`) service-to-service hop, whose caller's surface arrived on the wire. OIDC is an\n * internal hop, NOT a surface of its own, so it deliberately maps to `undefined` below and the\n * propagated value survives.\n *\n * Running BEFORE {@link reconcileWireTrust} is what makes the trusted-header rule work for this\n * key: on a `jwt()` edge a caller who also SENDS `x-wp-surface` must match the value the\n * framework just derived, and on a public route nothing vouches for it at all, so it is a 401.\n */\n private applySurface(mode: AuthMode): void {\n if (RequestContext.getTrusted(WebpiecesCoreHeaders.SURFACE) !== undefined) {\n return;\n }\n const surface = AuthFilter.surfaceFor(mode);\n if (surface !== undefined) {\n RequestContext.putTrusted(WebpiecesCoreHeaders.SURFACE, surface);\n }\n }\n\n /**\n * The auth mode -> {@link Surface} table, and the ONE place it is written down.\n *\n * `undefined` means \"this mode is not a surface\", which is a different statement from \"unknown\":\n * `oidc` and `shared-secret` are internal hops that RELAY somebody else's surface, `vendorAuth`'s\n * sender is a vendor with no webpieces surface at all, and `local-only` is a developer's own box.\n *\n * An exhaustive switch with NO `default`, like {@link verifiesCaller} and\n * `DestinationTrust.forAuthMode`: a new AuthMode kind must fail to compile here rather than\n * silently landing on `gui` (which would publish 266 to a partner) or on `public-api` (which would\n * publish a real 4xx to a GUI).\n */\n // webpieces-disable no-function-outside-class -- static pure mapping from the AuthMode union, kept beside its only caller (mirrors verifiesCaller)\n private static surfaceFor(mode: AuthMode): Surface | undefined {\n switch (mode.kind) {\n case 'jwt':\n return 'gui';\n case 'apikey':\n return 'public-api';\n case 'oidc':\n case 'shared-secret':\n case 'vendorAuth':\n case 'public':\n return undefined;\n }\n }\n\n /**\n * `vendorAuth(name)`: hand the app's {@link VendorAuthVerifier} the verbatim request and let it call the\n * VENDOR's own validator. A missing verifier, or one not declaring `name`, is not a case here: it\n * failed STARTUP ({@link assertRouteVerifiable}). Two ways to fail, both 401, both before the\n * controller is entered:\n *\n * 1. NO raw request — the transport kept no bytes. `assertEveryVendorAuthEndpointRetainsRawBody`\n * normally makes this a startup error, so reaching it means either a hand-registered route or\n * an in-process caller (a spec) that published an HttpRequest with no {@link RawRequest}. The\n * message says which fix applies rather than leaving a bare 401.\n * 2. The verifier threw — the credential did not verify (including a name its own dispatch does\n * not know: that stays the request-time backstop).\n *\n * Case 1 is checked HERE and nowhere else. {@link hasRawBytes} narrows the request to\n * {@link RawHttpRequest} at this one gate, so the verifier's signature promises `raw` is present and\n * no vendor implementation ever writes `raw!` or a guard of its own.\n */\n private async enforceVendorAuth(name: string, meta: MethodMeta): Promise<void> {\n const verifier = this.requireVendorVerifier(name, meta.routeMeta);\n const request = RequestContext.getRequest();\n if (!this.hasRawBytes(request)) {\n log.warn(\n `Refusing vendorAuth('${name}') endpoint ${meta.routeMeta.path}: the inbound request carries ` +\n `no raw bytes. Declare @Endpoint(POST, path, WRITE, EXTERNAL_INBOUND, { calledBy: '${name}', rawBody: true }); a ` +\n `spec driving this route in-process must publish an HttpRequest built with a RawRequest.`,\n );\n throw new ApiUnauthorizedError(\n 'Vendor credential cannot be verified: no raw request was retained',\n );\n }\n // Throws ApiUnauthorizedError to deny. On success the vendor account the signature proved is\n // stamped through the SAME path a jwt or api-key caller takes.\n const caller = await verifier.verify(name, request);\n this.callerContext.publish(caller);\n }\n\n /**\n * The ONE place the framework decides a request carries the verbatim bytes. A TYPE PREDICATE, so\n * the `true` branch hands {@link enforceVendorAuth} a {@link RawHttpRequest} with no cast and no\n * non-null assertion — the bad state stops being representable past this line rather than being\n * re-thrown about by every hook.\n */\n private hasRawBytes(request: HttpRequest | undefined): request is RawHttpRequest {\n return request?.raw !== undefined;\n }\n\n /**\n * `apiKey(regime, credentials)`: hand the app's {@link ApiKeyHook} the regime name and the inbound\n * headers and let it look the CUSTOMER's key up. The declared `credentials` are NOT read here — they\n * describe the contract for generators; the hook owns extraction. A missing hook is not a case here:\n * it failed STARTUP ({@link assertRouteVerifiable}). Two ways to fail, both 401, both before the\n * controller:\n *\n * 1. NO inbound request in scope — there are no headers to read, so there is nothing to verify. That\n * means a caller drove this route without publishing an HttpRequest; the message says so rather\n * than leaving a bare 401.\n * 2. The hook threw — the key, or the key/organization pair, did not check out.\n *\n * On success the hook's {@link AuthenticatedCaller} is stamped exactly as a jwt parse's is, which is\n * what puts the resolved organization into `RequestContext` for every downstream repository call.\n */\n private async enforceApiKey(regime: string, meta: MethodMeta): Promise<void> {\n const hook = this.requireApiKeyHook(regime, meta.routeMeta);\n const request = RequestContext.getRequest();\n if (!request) {\n log.warn(\n `Refusing apiKey('${regime}') endpoint ${meta.routeMeta.path}: no inbound HttpRequest is ` +\n `in scope, so the hook has no headers to read. A spec driving this route in-process must ` +\n `publish an HttpRequest carrying the api-key headers.`,\n );\n throw new ApiUnauthorizedError(\n 'API key cannot be verified: no inbound request was published',\n );\n }\n // Throws ApiUnauthorizedError to deny. The hook gets the WHOLE request so it can cross-check\n // the key against a second header (the organization the customer is acting for).\n const caller = await hook.verifyApiKey(regime, request);\n this.callerContext.publish(caller);\n }\n\n /**\n * Does this mode authenticate the CALLER ITSELF (as opposed to a user, or nobody)? The INBOUND\n * twin of {@link DestinationTrust.forAuthMode}, and deliberately the same question: the client\n * omits trusted keys for a destination that cannot verify it, and the server rejects trusted keys\n * on a route that cannot verify the sender. One rule, two ends — if they disagreed, every call\n * would fail with a 401 that looks like a framework bug.\n *\n * - `oidc` / `shared-secret` → TRUE. An internal service is on the other end and the trusted\n * context it forwarded may be believed. This is what makes cross-service identity propagation\n * work.\n * - `jwt` / `public` → FALSE. A user JWT proves who the USER is; the SENDER is still whoever\n * holds the token, i.e. a browser.\n * - `local-only` → FALSE. It verifies WHERE WE ARE RUNNING, not who is calling — anything on\n * localhost reaches it, and it has no authenticator, so nothing can ever vouch for an inbound\n * trusted header. Any such header therefore rejects the request, which is exactly right.\n *\n * - `apikey` → FALSE. See the comment on that branch: the sender is a CUSTOMER.\n *\n * An exhaustive switch with NO `default`, returning on every branch: a new AuthMode kind is a\n * COMPILE error here (TS7030, no ending return) rather than silently landing on one posture. The\n * boolean expression this replaced defaulted every future mode to \"not verified\" — the safe\n * answer, but arrived at by accident rather than by decision.\n */\n // webpieces-disable no-function-outside-class -- static pure mapping from the AuthMode union, kept beside its only caller (mirrors DestinationTrust.forAuthMode)\n private static verifiesCaller(mode: AuthMode): boolean {\n switch (mode.kind) {\n case 'oidc':\n case 'shared-secret':\n return true;\n case 'jwt':\n case 'public':\n // `vendorAuth` DOES authenticate its sender — but the sender is an outside VENDOR, not a\n // peer in this repo, and a vendor neither speaks nor forwards webpieces context headers.\n // So there is no forwarded identity to believe, and admitting one would mean trusting a\n // key a vendor's payload could carry. Same answer as the OUTBOUND half\n // (DestinationTrust.forAuthMode), which is the invariant that keeps the two ends agreeing.\n case 'vendorAuth':\n // `apikey` authenticates the SENDER — but the sender is a CUSTOMER's codebase, not a peer\n // service in this repo, so its forwarded trusted context is exactly what must NOT be\n // believed: admitting it would let a partner assert another customer's org id on the wire.\n // The hook's OWN derived entries still land (applyAuthenticatedCaller), and reconcileWireTrust then\n // admits an inbound trusted header only when the hook independently derived the same value.\n case 'apikey':\n return false;\n }\n }\n\n /**\n * `@WpLocalOnly`: serve only on a developer's machine, and off-local behave EXACTLY as if the\n * endpoint did not exist.\n *\n * WHY 404 AND NOT THE 403 APPS HAND-ROLLED. Off-local the route is not registered at all\n * (`ApiRoutingFactory` skips it), so the ordinary way to reach this path already answers 404. A\n * 403 from here would be a DIFFERENT answer from the same framework for the same endpoint, and\n * the difference is itself the leak: 403 confirms \"this path exists in production, you merely\n * lack permission\", which is a map of the dev-only surface for anyone probing. A local-only\n * endpoint should not admit it exists. Both gates therefore return the same 404, and this one is\n * the backstop for routes registered by hand through `RouteBuilder` rather than by\n * `ApiRoutingFactory`.\n *\n * The log line names WHICH reason applies, because \"you are deployed\" and \"nobody declared a\n * locality\" have completely different fixes and both look like a bare 404 from outside.\n */\n private enforceLocalOnly(meta: MethodMeta): void {\n if (RuntimeLocality.isLocalDevelopment()) {\n return;\n }\n log.warn(\n `Refusing @WpLocalOnly endpoint ${meta.routeMeta.path}: ` +\n (RuntimeLocality.isDeclared()\n ? 'this process declared itself DEPLOYED.'\n : 'no startup declared a RuntimeLocality, so this process is treated as DEPLOYED. ' +\n 'Pass the locality into RuntimeSetupOptions if this really is a developer machine.'),\n );\n // Same shape as an unregistered route — see the method doc for why this is not a 403.\n throw new ApiEndpointNotFoundError(`No endpoint at ${meta.routeMeta.path}`);\n }\n\n private async enforceJwt(header: string | undefined, routeMeta: RouteMetadata): Promise<void> {\n const hook = this.requireJwtHook(routeMeta);\n const token = this.credential(header, BEARER_SCHEME);\n if (!token) throw new ApiUnauthorizedError('Authentication required');\n this.callerContext.publish(await hook.parseJwt(token));\n }\n\n private async enforceOidc(header: string | undefined, methods: OidcAuthMethods): Promise<void> {\n const token = this.credential(header, BEARER_SCHEME);\n if (!token) throw new ApiUnauthorizedError('Missing OIDC bearer token');\n const identity = this.oidcHook\n ? await this.oidcHook.verifyOidc(token, methods)\n : await this.oidcVerifier.verify(token, methods);\n if (!identity) throw new ApiImplementationError('OIDC verifier must return its verified machine identity.');\n RequestContext.putTrusted(VERIFIED_MACHINE_CALLER, new VerifiedMachineCaller(identity.mechanism, identity.identity));\n }\n\n /** `provided` is the Authorization bearer value — the secret itself, same header as a JWT. */\n private enforceSharedSecret(provided: string | undefined, secretKey: string, routeMeta: RouteMetadata): void {\n const accepted = this.requireSharedSecrets(secretKey, routeMeta);\n if (!provided || !this.matchesEither(provided, accepted)) {\n throw new ApiUnauthorizedError(\n 'Invalid shared secret for sharedSecret(...) endpoint',\n );\n }\n }\n\n /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */\n private matchesEither(provided: string, accepted: SharedSecrets): boolean {\n return (\n (accepted.secret1 !== '' && this.constantTimeEquals(provided, accepted.secret1)) ||\n (accepted.secret2 !== '' && this.constantTimeEquals(provided, accepted.secret2))\n );\n }\n\n /** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */\n private async bestEffortJwt(header: string | undefined): Promise<void> {\n const token = this.credential(header, BEARER_SCHEME);\n if (!this.jwtHook || !token) {\n return;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- best-effort on a public route: a bad/absent token just means \"not logged in\", must not fail the request\n try {\n this.callerContext.publish(await this.jwtHook.parseJwt(token));\n } catch (err: unknown) {\n const error = toError(err);\n if (!(error instanceof ApiUnauthorizedError)) throw error;\n log.debug(\n 'Best-effort JWT parse on a public endpoint failed (treating as anonymous): ',\n error,\n );\n }\n }\n\n /**\n * The credential value IF the header carries the expected scheme, else undefined.\n *\n * Strict: a bare value with no scheme, or a value under the WRONG scheme (a shared secret sent\n * where a JWT is expected), yields undefined and the caller 401s.\n */\n private credential(header: string | undefined, scheme: string): string | undefined {\n if (!header) {\n return undefined;\n }\n const prefix = `${scheme} `;\n return header.startsWith(prefix) ? header.substring(prefix.length) : undefined;\n }\n\n private constantTimeEquals(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) {\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export { ApiClient, ApiClientProxy } from './ApiClient';
|
|
|
22
22
|
export { AuthConfig, AUTH_CONFIG, AuthenticatedCaller, AuthenticatedMachineIdentity, AUTHENTICATED_CALLER_KEY, SharedSecrets, } from './AuthConfig';
|
|
23
23
|
export { JwtHook, MintedJwt, JWT_HOOK, OidcHook, OIDC_HOOK, ApiKeyHook, API_KEY_HOOK, } from './AuthHooks';
|
|
24
24
|
export { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from '@webpieces/core-context';
|
|
25
|
+
export { UnverifiableAuthModeError } from './UnverifiableAuthModeError';
|
|
25
26
|
export { DefaultOidcVerifier } from './DefaultOidcVerifier';
|
|
26
27
|
export { DefaultJwtHook, DefaultJwtMintRequest } from './DefaultJwtHook';
|
|
27
28
|
export { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WRITE_IDEMPOTENT = exports.READ = exports.verbCarriesBody = exports.DELETE = exports.PATCH = exports.PUT = exports.POST = exports.GET = exports.METADATA_KEYS = exports.HttpContractMapper = exports.RouteMetadataFactory = exports.RouteMetadata = exports.AuthMeta = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMeta = exports.isApiPath = exports.isRawBody = exports.isFormPost = exports.getHttpParameterDeclarations = exports.getEndpointOperation = exports.getEndpointHttpMethod = exports.getEndpointOptions = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.getAuthorization = exports.AuthorizationType = exports.WpAuthorization = exports.apiKey = exports.vendorAuth = exports.sharedSecret = exports.oidcSvcs = exports.oidcSameProject = exports.jwt = exports.isLocalOnly = exports.WpLocalOnly = exports.WpAuth = exports.WpAuthPublic = exports.QueryParams = exports.QueryString = exports.QueryParam = exports.PathParam = exports.Endpoint = exports.ApiPath = void 0;
|
|
4
|
-
exports.
|
|
5
|
-
exports.WiringPolicy = exports.AuthorizedApiDocument = exports.VerifiedMachineCaller = exports.VERIFIED_MACHINE_CALLER = exports.AUTHORIZATION_HOOK = exports.CanonicalUserRoles = exports.AuthorizationService = exports.AuthorizationHook = exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RuntimeSetupOptions = exports.setupRuntime = exports.WebpiecesRouterFactory = exports.WebpiecesRouter = exports.DefaultJwtMintRequest = void 0;
|
|
4
|
+
exports.DefaultOidcVerifier = exports.UnverifiableAuthModeError = exports.VENDOR_AUTH_VERIFIER = exports.VendorAuthVerifier = exports.API_KEY_HOOK = exports.ApiKeyHook = exports.OIDC_HOOK = exports.OidcHook = exports.JWT_HOOK = exports.MintedJwt = exports.JwtHook = exports.SharedSecrets = exports.AUTHENTICATED_CALLER_KEY = exports.AuthenticatedMachineIdentity = exports.AuthenticatedCaller = exports.AUTH_CONFIG = exports.AuthConfig = exports.ApiClient = exports.PubSubBindOptions = exports.ClientBindOptions = exports.ContainerBinder = exports.NodeWiringModules = exports.FilterMatcher = exports.LogApiFilter = exports.RouteHandler = exports.MethodMeta = exports.InvocationAuthentication = exports.AuthenticatedCallerContext = exports.WpResponse = exports.RawRequest = exports.HttpRequest = exports.FilterDefinition = exports.RouteDefinition = exports.ApiRoutingFactory = exports.buildFrameworkModule = exports.provideFrameworkSingletonDefaultForApi = exports.provideFrameworkSingleton = exports.provideSingletonDefaultForApi = exports.ROUTING_METADATA_KEYS = exports.SourceFile = exports.isDocumentDesign = exports.DocumentDesign = exports.ExternalContextKeys = exports.EXTERNAL_OUTBOUND = exports.EXTERNAL_INBOUND = exports.CRON = exports.CLOUDTASKS = exports.RPC = exports.NONE = exports.WRITE = void 0;
|
|
5
|
+
exports.WiringPolicy = exports.AuthorizedApiDocument = exports.VerifiedMachineCaller = exports.VERIFIED_MACHINE_CALLER = exports.AUTHORIZATION_HOOK = exports.CanonicalUserRoles = exports.AuthorizationService = exports.AuthorizationHook = exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RuntimeSetupOptions = exports.setupRuntime = exports.WebpiecesRouterFactory = exports.WebpiecesRouter = exports.DefaultJwtMintRequest = exports.DefaultJwtHook = void 0;
|
|
6
6
|
// Re-export API decorators from core-util for convenience
|
|
7
7
|
var core_util_1 = require("@webpieces/core-util");
|
|
8
8
|
Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return core_util_1.ApiPath; } });
|
|
@@ -123,7 +123,8 @@ Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function ()
|
|
|
123
123
|
// Auth: the app-provided, container-bound pieces the framework AuthFilter injects.
|
|
124
124
|
// - AuthConfig: shared-secret STATE (sharedSecret(...) values).
|
|
125
125
|
// - JwtHook / OidcHook / VendorAuthVerifier / ApiKeyHook: OPTIONAL verification mechanisms
|
|
126
|
-
// (bind only what
|
|
126
|
+
// (bind only what your served routes declare; a served route declaring a mode whose piece is
|
|
127
|
+
// unbound FAILS STARTUP with UnverifiableAuthModeError naming the binding, never open — #1362).
|
|
127
128
|
// - DefaultOidcVerifier: the built-in Google OIDC verifier used when no OidcHook is bound.
|
|
128
129
|
var AuthConfig_1 = require("./AuthConfig");
|
|
129
130
|
Object.defineProperty(exports, "AuthConfig", { enumerable: true, get: function () { return AuthConfig_1.AuthConfig; } });
|
|
@@ -146,6 +147,8 @@ Object.defineProperty(exports, "API_KEY_HOOK", { enumerable: true, get: function
|
|
|
146
147
|
var core_context_4 = require("@webpieces/core-context");
|
|
147
148
|
Object.defineProperty(exports, "VendorAuthVerifier", { enumerable: true, get: function () { return core_context_4.VendorAuthVerifier; } });
|
|
148
149
|
Object.defineProperty(exports, "VENDOR_AUTH_VERIFIER", { enumerable: true, get: function () { return core_context_4.VENDOR_AUTH_VERIFIER; } });
|
|
150
|
+
var UnverifiableAuthModeError_1 = require("./UnverifiableAuthModeError");
|
|
151
|
+
Object.defineProperty(exports, "UnverifiableAuthModeError", { enumerable: true, get: function () { return UnverifiableAuthModeError_1.UnverifiableAuthModeError; } });
|
|
149
152
|
var DefaultOidcVerifier_1 = require("./DefaultOidcVerifier");
|
|
150
153
|
Object.defineProperty(exports, "DefaultOidcVerifier", { enumerable: true, get: function () { return DefaultOidcVerifier_1.DefaultOidcVerifier; } });
|
|
151
154
|
// DefaultJwtHook: batteries-included HS256 JwtHook — `new DefaultJwtHook(secret)` and go.
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":"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","sourcesContent":["// Re-export API decorators from core-util for convenience\nexport {\n ApiPath,\n Endpoint,\n PathParam,\n QueryParam,\n QueryString,\n QueryParams,\n WpAuthPublic,\n WpAuth, WpLocalOnly, isLocalOnly, jwt, oidcSameProject, oidcSvcs, sharedSecret, vendorAuth, apiKey,\n WpAuthorization, AuthorizationType, getAuthorization,\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n getEndpointHttpMethod,\n getEndpointOperation,\n getHttpParameterDeclarations,\n isFormPost,\n isRawBody,\n isApiPath,\n getAuthMeta,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n AuthMeta,\n RouteMetadata,\n RouteMetadataFactory,\n HttpContractMapper,\n METADATA_KEYS,\n ValidateImplementation,\n GET,\n POST,\n PUT,\n PATCH,\n DELETE,\n verbCarriesBody,\n READ,\n WRITE_IDEMPOTENT,\n WRITE,\n NONE,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL_INBOUND,\n EXTERNAL_OUTBOUND,\n ExternalContextKeys,\n // @DocumentDesign moved to core-util (design-root marker, browser + Node);\n // re-exported here for back-compat.\n DocumentDesign,\n isDocumentDesign,\n} from '@webpieces/core-util';\nexport type {\n AuthMode,\n ApiKind,\n HttpMethod,\n EndpointOperation,\n EndpointKind,\n EndpointOptions,\n EndpointResponseType,\n} from '@webpieces/core-util';\n\n// Server-side routing decorators and utilities\nexport { SourceFile, ROUTING_METADATA_KEYS } from './decorators';\n\n// DI provider decorators moved to core-context; re-exported here for back-compat\nexport { provideSingletonDefaultForApi } from '@webpieces/core-context';\n// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonDefaultForApi,\n buildFrameworkModule,\n} from '@webpieces/core-context';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types\nexport { Routes, RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\n\n// The transport-neutral request type (defined in core-context; this is http-routing's\n// public request — a transport adapter builds one and the chain reads it from RequestContext).\nexport { HttpRequest, RawHttpRequest, RawRequest } from '@webpieces/core-context';\n\n// The INBOUND chain's response type. `Filter`, `Service` and `FilterChain` are NOT re-exported\n// here: they moved to @webpieces/core-util so the outbound client chain is the SAME abstraction\n// rather than a second spelling of it. Import them from '@webpieces/core-util'.\nexport { WpResponse } from './WpResponse';\nexport { AuthenticatedCallerContext } from './AuthenticatedCallerContext';\nexport { InvocationAuthentication } from './InvocationAuthentication';\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\n\n// LogApiFilter: the fixed OUTERMOST framework filter (auto-installed at 1,000,000 above\n// AuthFilter). Exported for reference/testing only — apps must NOT install it themselves.\nexport { LogApiFilter } from './filters/LogApiFilter';\n\n// RouteBuilderImpl (the route table + chain composer) is now INTERNAL — it is never\n// handed to upper layers. The express layer consumes ApiFactory.apiClients() instead.\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// The app's server-surface declaration: DI bind modules + route groups + headers.\nexport type { Wiring, AppWiring, BindModule, RouteModule } from './Wiring';\nexport { NodeWiringModules } from './Wiring';\n// The Node binder every BindModule.configure receives.\nexport type { Binder } from './Binder';\nexport { ContainerBinder, ClientBindOptions, PubSubBindOptions } from './Binder';\n\n// The public API-surface abstraction: declare routes/filters, get them back as ApiClient[].\nexport { ApiFactory } from './ApiFactory';\nexport { ApiClient, ApiClientProxy } from './ApiClient';\n\n// Auth: the app-provided, container-bound pieces the framework AuthFilter injects.\n// - AuthConfig: shared-secret STATE (sharedSecret(...) values).\n// - JwtHook / OidcHook / VendorAuthVerifier / ApiKeyHook: OPTIONAL verification mechanisms\n// (bind only what you use; unbound means the matching endpoints 401, never open).\n// - DefaultOidcVerifier: the built-in Google OIDC verifier used when no OidcHook is bound.\nexport {\n AuthConfig,\n AUTH_CONFIG,\n AuthenticatedCaller,\n AuthenticatedMachineIdentity,\n AUTHENTICATED_CALLER_KEY,\n SharedSecrets,\n} from './AuthConfig';\nexport {\n JwtHook,\n MintedJwt,\n JWT_HOOK,\n OidcHook,\n OIDC_HOOK,\n ApiKeyHook,\n API_KEY_HOOK,\n} from './AuthHooks';\n// vendorAuth(name), INBOUND role: the verifier this server's AuthFilter calls. Defined in\n// @webpieces/core-context beside its OUTBOUND twin VendorAuthMinter (re-exported by\n// @webpieces/http-client-node); the two are independent DI roles, never one binding (#1358).\nexport { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from '@webpieces/core-context';\nexport { DefaultOidcVerifier } from './DefaultOidcVerifier';\n// DefaultJwtHook: batteries-included HS256 JwtHook — `new DefaultJwtHook(secret)` and go.\nexport { DefaultJwtHook, DefaultJwtMintRequest } from './DefaultJwtHook';\n\n// Above-boundary context setup shared by every transport adapter.\n\n// Node-only router (the express-free heart: container + filter chain + in-process client)\nexport { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';\n\n// The ONE transport-free startup sequence (headers → logging → router → routes) → ApiFactory.\n// Reusable by any company/app and any framework adapter; a company wraps it with its own headers.\nexport { setupRuntime, RuntimeSetupOptions } from './setupRuntime';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n\n\nexport { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZATION_HOOK, VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from './AuthorizationHook';\nexport { AuthorizedApiDocument } from './AuthorizedApiDocument';\nexport type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';\n\nexport { WiringPolicy } from '@webpieces/http-client-core';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;;;AAAA,0DAA0D;AAC1D,kDAsD8B;AArD1B,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,sGAAA,SAAS,OAAA;AACT,uGAAA,UAAU,OAAA;AACV,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AACX,yGAAA,YAAY,OAAA;AACZ,mGAAA,MAAM,OAAA;AAAE,wGAAA,WAAW,OAAA;AAAE,wGAAA,WAAW,OAAA;AAAE,gGAAA,GAAG,OAAA;AAAE,4GAAA,eAAe,OAAA;AAAE,qGAAA,QAAQ,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,uGAAA,UAAU,OAAA;AAAE,mGAAA,MAAM,OAAA;AAClG,4GAAA,eAAe,OAAA;AAAE,8GAAA,iBAAiB,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AACpD,gGAAA,GAAG,OAAA;AACH,mGAAA,MAAM,OAAA;AACN,kGAAA,KAAK,OAAA;AACL,uGAAA,UAAU,OAAA;AACV,yGAAA,YAAY,OAAA;AACZ,+GAAA,kBAAkB,OAAA;AAClB,kHAAA,qBAAqB,OAAA;AACrB,iHAAA,oBAAoB,OAAA;AACpB,yHAAA,4BAA4B,OAAA;AAC5B,uGAAA,UAAU,OAAA;AACV,sGAAA,SAAS,OAAA;AACT,sGAAA,SAAS,OAAA;AACT,wGAAA,WAAW,OAAA;AACX,2HAAA,8BAA8B,OAAA;AAC9B,uGAAA,UAAU,OAAA;AACV,0GAAA,aAAa,OAAA;AACb,oHAAA,uBAAuB,OAAA;AACvB,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,0GAAA,aAAa,OAAA;AACb,iHAAA,oBAAoB,OAAA;AACpB,+GAAA,kBAAkB,OAAA;AAClB,0GAAA,aAAa,OAAA;AAEb,gGAAA,GAAG,OAAA;AACH,iGAAA,IAAI,OAAA;AACJ,gGAAA,GAAG,OAAA;AACH,kGAAA,KAAK,OAAA;AACL,mGAAA,MAAM,OAAA;AACN,4GAAA,eAAe,OAAA;AACf,iGAAA,IAAI,OAAA;AACJ,6GAAA,gBAAgB,OAAA;AAChB,kGAAA,KAAK,OAAA;AACL,iGAAA,IAAI,OAAA;AACJ,gGAAA,GAAG,OAAA;AACH,uGAAA,UAAU,OAAA;AACV,iGAAA,IAAI,OAAA;AACJ,6GAAA,gBAAgB,OAAA;AAChB,8GAAA,iBAAiB,OAAA;AACjB,gHAAA,mBAAmB,OAAA;AACnB,2EAA2E;AAC3E,oCAAoC;AACpC,2GAAA,cAAc,OAAA;AACd,6GAAA,gBAAgB,OAAA;AAYpB,+CAA+C;AAC/C,2CAAiE;AAAxD,wGAAA,UAAU,OAAA;AAAE,mHAAA,qBAAqB,OAAA;AAE1C,iFAAiF;AACjF,wDAAwE;AAA/D,6HAAA,6BAA6B,OAAA;AACtC,gGAAgG;AAChG,wDAIiC;AAH7B,yHAAA,yBAAyB,OAAA;AACzB,sIAAA,sCAAsC,OAAA;AACtC,oHAAA,oBAAoB,OAAA;AAGxB,yDAAmE;AAA1D,sHAAA,iBAAiB,OAAA;AAE1B,qBAAqB;AACrB,2CAAuF;AAAxD,6GAAA,eAAe,OAAA;AAAE,8GAAA,gBAAgB,OAAA;AAEhE,sFAAsF;AACtF,+FAA+F;AAC/F,wDAAkF;AAAzE,2GAAA,WAAW,OAAA;AAAkB,0GAAA,UAAU,OAAA;AAEhD,+FAA+F;AAC/F,gGAAgG;AAChG,gFAAgF;AAChF,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,2EAA0E;AAAjE,wIAAA,0BAA0B,OAAA;AACnC,uEAAsE;AAA7D,oIAAA,wBAAwB,OAAA;AACjC,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,wFAAwF;AACxF,0FAA0F;AAC1F,uDAAsD;AAA7C,4GAAA,YAAY,OAAA;AAErB,oFAAoF;AACpF,sFAAsF;AAEtF,kBAAkB;AAClB,iDAA4D;AAAnD,8GAAA,aAAa,OAAA;AAItB,mCAA6C;AAApC,2GAAA,iBAAiB,OAAA;AAG1B,mCAAiF;AAAxE,yGAAA,eAAe,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAI9D,yCAAwD;AAA/C,sGAAA,SAAS,OAAA;AAElB,mFAAmF;AACnF,iEAAiE;AACjE,4FAA4F;AAC5F,gGAAgG;AAChG,mGAAmG;AACnG,4FAA4F;AAC5F,2CAOsB;AANlB,wGAAA,UAAU,OAAA;AACV,yGAAA,WAAW,OAAA;AACX,iHAAA,mBAAmB,OAAA;AACnB,0HAAA,4BAA4B,OAAA;AAC5B,sHAAA,wBAAwB,OAAA;AACxB,2GAAA,aAAa,OAAA;AAEjB,yCAQqB;AAPjB,oGAAA,OAAO,OAAA;AACP,sGAAA,SAAS,OAAA;AACT,qGAAA,QAAQ,OAAA;AACR,qGAAA,QAAQ,OAAA;AACR,sGAAA,SAAS,OAAA;AACT,uGAAA,UAAU,OAAA;AACV,yGAAA,YAAY,OAAA;AAEhB,0FAA0F;AAC1F,oFAAoF;AACpF,6FAA6F;AAC7F,wDAAmF;AAA1E,kHAAA,kBAAkB,OAAA;AAAE,oHAAA,oBAAoB,OAAA;AACjD,yEAAwE;AAA/D,sIAAA,yBAAyB,OAAA;AAClC,6DAA4D;AAAnD,0HAAA,mBAAmB,OAAA;AAC5B,0FAA0F;AAC1F,mDAAyE;AAAhE,gHAAA,cAAc,OAAA;AAAE,uHAAA,qBAAqB,OAAA;AAE9C,kEAAkE;AAElE,0FAA0F;AAC1F,qDAAoG;AAA3F,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA;AAEhD,8FAA8F;AAC9F,kGAAkG;AAClG,+CAAmE;AAA1D,4GAAA,YAAY,OAAA;AAAE,mHAAA,mBAAmB,OAAA;AAE1C,uBAAuB;AACvB,qDAA4E;AAAnE,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA;AAGhD,yDAAsK;AAA7J,sHAAA,iBAAiB,OAAA;AAAE,yHAAA,oBAAoB,OAAA;AAAE,uHAAA,kBAAkB,OAAA;AAAE,uHAAA,kBAAkB,OAAA;AAAE,4HAAA,uBAAuB,OAAA;AAAE,0HAAA,qBAAqB,OAAA;AACxI,iEAAgE;AAAvD,8HAAA,qBAAqB,OAAA;AAG9B,gEAA2D;AAAlD,gHAAA,YAAY,OAAA","sourcesContent":["// Re-export API decorators from core-util for convenience\nexport {\n ApiPath,\n Endpoint,\n PathParam,\n QueryParam,\n QueryString,\n QueryParams,\n WpAuthPublic,\n WpAuth, WpLocalOnly, isLocalOnly, jwt, oidcSameProject, oidcSvcs, sharedSecret, vendorAuth, apiKey,\n WpAuthorization, AuthorizationType, getAuthorization,\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n getEndpointOptions,\n getEndpointHttpMethod,\n getEndpointOperation,\n getHttpParameterDeclarations,\n isFormPost,\n isRawBody,\n isApiPath,\n getAuthMeta,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n AuthMeta,\n RouteMetadata,\n RouteMetadataFactory,\n HttpContractMapper,\n METADATA_KEYS,\n ValidateImplementation,\n GET,\n POST,\n PUT,\n PATCH,\n DELETE,\n verbCarriesBody,\n READ,\n WRITE_IDEMPOTENT,\n WRITE,\n NONE,\n RPC,\n CLOUDTASKS,\n CRON,\n EXTERNAL_INBOUND,\n EXTERNAL_OUTBOUND,\n ExternalContextKeys,\n // @DocumentDesign moved to core-util (design-root marker, browser + Node);\n // re-exported here for back-compat.\n DocumentDesign,\n isDocumentDesign,\n} from '@webpieces/core-util';\nexport type {\n AuthMode,\n ApiKind,\n HttpMethod,\n EndpointOperation,\n EndpointKind,\n EndpointOptions,\n EndpointResponseType,\n} from '@webpieces/core-util';\n\n// Server-side routing decorators and utilities\nexport { SourceFile, ROUTING_METADATA_KEYS } from './decorators';\n\n// DI provider decorators moved to core-context; re-exported here for back-compat\nexport { provideSingletonDefaultForApi } from '@webpieces/core-context';\n// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonDefaultForApi,\n buildFrameworkModule,\n} from '@webpieces/core-context';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types\nexport { Routes, RouteBuilder, RouteDefinition, FilterDefinition } from './WebAppMeta';\n\n// The transport-neutral request type (defined in core-context; this is http-routing's\n// public request — a transport adapter builds one and the chain reads it from RequestContext).\nexport { HttpRequest, RawHttpRequest, RawRequest } from '@webpieces/core-context';\n\n// The INBOUND chain's response type. `Filter`, `Service` and `FilterChain` are NOT re-exported\n// here: they moved to @webpieces/core-util so the outbound client chain is the SAME abstraction\n// rather than a second spelling of it. Import them from '@webpieces/core-util'.\nexport { WpResponse } from './WpResponse';\nexport { AuthenticatedCallerContext } from './AuthenticatedCallerContext';\nexport { InvocationAuthentication } from './InvocationAuthentication';\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\n\n// LogApiFilter: the fixed OUTERMOST framework filter (auto-installed at 1,000,000 above\n// AuthFilter). Exported for reference/testing only — apps must NOT install it themselves.\nexport { LogApiFilter } from './filters/LogApiFilter';\n\n// RouteBuilderImpl (the route table + chain composer) is now INTERNAL — it is never\n// handed to upper layers. The express layer consumes ApiFactory.apiClients() instead.\n\n// Filter matching\nexport { FilterMatcher, HttpFilter } from './FilterMatcher';\n\n// The app's server-surface declaration: DI bind modules + route groups + headers.\nexport type { Wiring, AppWiring, BindModule, RouteModule } from './Wiring';\nexport { NodeWiringModules } from './Wiring';\n// The Node binder every BindModule.configure receives.\nexport type { Binder } from './Binder';\nexport { ContainerBinder, ClientBindOptions, PubSubBindOptions } from './Binder';\n\n// The public API-surface abstraction: declare routes/filters, get them back as ApiClient[].\nexport { ApiFactory } from './ApiFactory';\nexport { ApiClient, ApiClientProxy } from './ApiClient';\n\n// Auth: the app-provided, container-bound pieces the framework AuthFilter injects.\n// - AuthConfig: shared-secret STATE (sharedSecret(...) values).\n// - JwtHook / OidcHook / VendorAuthVerifier / ApiKeyHook: OPTIONAL verification mechanisms\n// (bind only what your served routes declare; a served route declaring a mode whose piece is\n// unbound FAILS STARTUP with UnverifiableAuthModeError naming the binding, never open — #1362).\n// - DefaultOidcVerifier: the built-in Google OIDC verifier used when no OidcHook is bound.\nexport {\n AuthConfig,\n AUTH_CONFIG,\n AuthenticatedCaller,\n AuthenticatedMachineIdentity,\n AUTHENTICATED_CALLER_KEY,\n SharedSecrets,\n} from './AuthConfig';\nexport {\n JwtHook,\n MintedJwt,\n JWT_HOOK,\n OidcHook,\n OIDC_HOOK,\n ApiKeyHook,\n API_KEY_HOOK,\n} from './AuthHooks';\n// vendorAuth(name), INBOUND role: the verifier this server's AuthFilter calls. Defined in\n// @webpieces/core-context beside its OUTBOUND twin VendorAuthMinter (re-exported by\n// @webpieces/http-client-node); the two are independent DI roles, never one binding (#1358).\nexport { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from '@webpieces/core-context';\nexport { UnverifiableAuthModeError } from './UnverifiableAuthModeError';\nexport { DefaultOidcVerifier } from './DefaultOidcVerifier';\n// DefaultJwtHook: batteries-included HS256 JwtHook — `new DefaultJwtHook(secret)` and go.\nexport { DefaultJwtHook, DefaultJwtMintRequest } from './DefaultJwtHook';\n\n// Above-boundary context setup shared by every transport adapter.\n\n// Node-only router (the express-free heart: container + filter chain + in-process client)\nexport { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';\n\n// The ONE transport-free startup sequence (headers → logging → router → routes) → ApiFactory.\n// Reusable by any company/app and any framework adapter; a company wraps it with its own headers.\nexport { setupRuntime, RuntimeSetupOptions } from './setupRuntime';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n\n\nexport { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZATION_HOOK, VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from './AuthorizationHook';\nexport { AuthorizedApiDocument } from './AuthorizedApiDocument';\nexport type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';\n\nexport { WiringPolicy } from '@webpieces/http-client-core';\n"]}
|