@webpieces/http-routing 0.4.990 → 0.4.992

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/http-routing",
3
- "version": "0.4.990",
3
+ "version": "0.4.992",
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.990",
26
- "@webpieces/core-util": "0.4.990",
27
- "@webpieces/gcp-identity": "0.4.990",
25
+ "@webpieces/core-context": "0.4.992",
26
+ "@webpieces/core-util": "0.4.992",
27
+ "@webpieces/gcp-identity": "0.4.992",
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.990",
32
- "@webpieces/http-client-node": "0.4.990",
33
- "@webpieces/cloudtasks-client": "0.4.990"
31
+ "@webpieces/http-client-core": "0.4.992",
32
+ "@webpieces/http-client-node": "0.4.992",
33
+ "@webpieces/cloudtasks-client": "0.4.992"
34
34
  }
35
35
  }
@@ -0,0 +1,55 @@
1
+ import { HttpRequest } from '@webpieces/core-context';
2
+ import { AuthMethod, OidcAuthMethods } from '@webpieces/core-util';
3
+ import { AuthConfig, AuthenticatedCaller, AuthenticatedMachineIdentity, SharedSecrets } from './AuthConfig';
4
+ import { ApiKeyHook, JwtHook, OidcHook } from './AuthHooks';
5
+ import { DefaultOidcVerifier } from './DefaultOidcVerifier';
6
+ /**
7
+ * The ONE constant-time comparison behind `sharedSecret(...)`, shared by {@link AuthFilter} and
8
+ * {@link CredentialVerifiers} so the two doors cannot disagree about what a matching secret is.
9
+ */
10
+ export declare class SharedSecretComparison {
11
+ /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */
12
+ static matchesEither(provided: string, accepted: SharedSecrets): boolean;
13
+ private static constantTimeEquals;
14
+ }
15
+ /**
16
+ * The credential VERIFIERS an app bound, for a door that is not a route — today the MCP HTTP endpoint
17
+ * (`@webpieces/mcp-server`), whose `setAuth([...])` takes the same `@WpAuth` builders an endpoint
18
+ * does. It holds exactly the pieces {@link AuthFilter} holds, resolved from the same container with the
19
+ * same tokens, so a `jwt()` on an MCP door is verified by the SAME `JwtHook` that verifies a `jwt()`
20
+ * endpoint, and `oidcSameProject()` by the same `OidcHook` / {@link DefaultOidcVerifier}:
21
+ *
22
+ * - `oidc` → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}.
23
+ * - `jwt` → the bound {@link JwtHook}.
24
+ * - `shared-secret` → the bound {@link AuthConfig}'s values, compared in constant time.
25
+ * - `apikey` → the bound {@link ApiKeyHook}.
26
+ *
27
+ * It verifies; it does not publish. Publishing the caller into `RequestContext` and reconciling inbound
28
+ * trusted headers stay the door's job, exactly as they are {@link AuthFilter}'s.
29
+ *
30
+ * Get it with `router.credentialVerifiers()`. {@link missingVerifier} is the startup check: a door that
31
+ * accepts a mode nobody can verify refuses to boot, naming the binding line, the same rule #1362 gives
32
+ * served routes.
33
+ */
34
+ export declare class CredentialVerifiers {
35
+ private readonly oidcVerifier;
36
+ private readonly authConfig?;
37
+ private readonly jwtHook?;
38
+ private readonly oidcHook?;
39
+ private readonly apiKeyHook?;
40
+ constructor(oidcVerifier: DefaultOidcVerifier, authConfig?: AuthConfig | undefined, jwtHook?: JwtHook<never> | undefined, oidcHook?: OidcHook | undefined, apiKeyHook?: ApiKeyHook | undefined);
41
+ /**
42
+ * The binding line a door must add before it can accept `method`, or `undefined` when the method
43
+ * is verifiable with what is bound. OIDC always is (the framework verifier). `vendorAuth` and
44
+ * `public` are not credential verifiers a door can use, so they are reported too.
45
+ */
46
+ missingVerifier(method: AuthMethod): string | undefined;
47
+ /** `jwt()`: the app's {@link JwtHook}. Throws `ApiUnauthorizedError` to deny. */
48
+ verifyJwt(token: string): Promise<AuthenticatedCaller>;
49
+ /** `oidcSameProject()` / `oidcSvcs([...])`: one token, verified once against every declared scope. */
50
+ verifyOidc(token: string, methods: OidcAuthMethods): Promise<AuthenticatedMachineIdentity>;
51
+ /** `sharedSecret(key)`: the `Webpieces <secret>` value against the bound secrets for `key`. */
52
+ verifySharedSecret(provided: string, secretKey: string): void;
53
+ /** `apiKey(regime, credentials)`: the app's {@link ApiKeyHook}, over the whole inbound request. */
54
+ verifyApiKey(regime: string, request: HttpRequest): Promise<AuthenticatedCaller>;
55
+ }
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CredentialVerifiers = exports.SharedSecretComparison = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const inversify_1 = require("inversify");
6
+ const crypto_1 = require("crypto");
7
+ const core_context_1 = require("@webpieces/core-context");
8
+ const core_util_1 = require("@webpieces/core-util");
9
+ const AuthConfig_1 = require("./AuthConfig");
10
+ const AuthHooks_1 = require("./AuthHooks");
11
+ const DefaultOidcVerifier_1 = require("./DefaultOidcVerifier");
12
+ /**
13
+ * The ONE constant-time comparison behind `sharedSecret(...)`, shared by {@link AuthFilter} and
14
+ * {@link CredentialVerifiers} so the two doors cannot disagree about what a matching secret is.
15
+ */
16
+ class SharedSecretComparison {
17
+ /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */
18
+ // webpieces-disable no-function-outside-class -- stateless comparison shared by two verifiers
19
+ static matchesEither(provided, accepted) {
20
+ return ((accepted.secret1 !== '' &&
21
+ SharedSecretComparison.constantTimeEquals(provided, accepted.secret1)) ||
22
+ (accepted.secret2 !== '' &&
23
+ SharedSecretComparison.constantTimeEquals(provided, accepted.secret2)));
24
+ }
25
+ // webpieces-disable no-function-outside-class -- stateless comparison shared by two verifiers
26
+ static constantTimeEquals(a, b) {
27
+ const bufA = Buffer.from(a, 'utf8');
28
+ const bufB = Buffer.from(b, 'utf8');
29
+ if (bufA.length !== bufB.length) {
30
+ return false;
31
+ }
32
+ return (0, crypto_1.timingSafeEqual)(bufA, bufB);
33
+ }
34
+ }
35
+ exports.SharedSecretComparison = SharedSecretComparison;
36
+ /**
37
+ * The credential VERIFIERS an app bound, for a door that is not a route — today the MCP HTTP endpoint
38
+ * (`@webpieces/mcp-server`), whose `setAuth([...])` takes the same `@WpAuth` builders an endpoint
39
+ * does. It holds exactly the pieces {@link AuthFilter} holds, resolved from the same container with the
40
+ * same tokens, so a `jwt()` on an MCP door is verified by the SAME `JwtHook` that verifies a `jwt()`
41
+ * endpoint, and `oidcSameProject()` by the same `OidcHook` / {@link DefaultOidcVerifier}:
42
+ *
43
+ * - `oidc` → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}.
44
+ * - `jwt` → the bound {@link JwtHook}.
45
+ * - `shared-secret` → the bound {@link AuthConfig}'s values, compared in constant time.
46
+ * - `apikey` → the bound {@link ApiKeyHook}.
47
+ *
48
+ * It verifies; it does not publish. Publishing the caller into `RequestContext` and reconciling inbound
49
+ * trusted headers stay the door's job, exactly as they are {@link AuthFilter}'s.
50
+ *
51
+ * Get it with `router.credentialVerifiers()`. {@link missingVerifier} is the startup check: a door that
52
+ * accepts a mode nobody can verify refuses to boot, naming the binding line, the same rule #1362 gives
53
+ * served routes.
54
+ */
55
+ let CredentialVerifiers = class CredentialVerifiers {
56
+ oidcVerifier;
57
+ authConfig;
58
+ jwtHook;
59
+ oidcHook;
60
+ apiKeyHook;
61
+ constructor(oidcVerifier, authConfig, jwtHook, oidcHook, apiKeyHook) {
62
+ this.oidcVerifier = oidcVerifier;
63
+ this.authConfig = authConfig;
64
+ this.jwtHook = jwtHook;
65
+ this.oidcHook = oidcHook;
66
+ this.apiKeyHook = apiKeyHook;
67
+ }
68
+ /**
69
+ * The binding line a door must add before it can accept `method`, or `undefined` when the method
70
+ * is verifiable with what is bound. OIDC always is (the framework verifier). `vendorAuth` and
71
+ * `public` are not credential verifiers a door can use, so they are reported too.
72
+ */
73
+ missingVerifier(method) {
74
+ switch (method.kind) {
75
+ case 'oidc':
76
+ return undefined;
77
+ case 'jwt':
78
+ return this.jwtHook
79
+ ? undefined
80
+ : 'binder.bind(JWT_HOOK).to(YourJwtHook).inSingletonScope();';
81
+ case 'apikey':
82
+ return this.apiKeyHook
83
+ ? undefined
84
+ : 'binder.bind(API_KEY_HOOK).to(YourApiKeyHook).inSingletonScope();';
85
+ case 'shared-secret':
86
+ return this.authConfig?.sharedSecrets[method.secretKey]
87
+ ? undefined
88
+ : `binder.bind(AUTH_CONFIG).to(YourAuthConfig).inSingletonScope(); // holding { '${method.secretKey}': new SharedSecrets(secret1, secret2) }`;
89
+ case 'vendorAuth':
90
+ return `vendorAuth('${method.name}') identifies a vendor, not a caller to authorize; it cannot be verified on this door`;
91
+ }
92
+ }
93
+ /** `jwt()`: the app's {@link JwtHook}. Throws `ApiUnauthorizedError` to deny. */
94
+ async verifyJwt(token) {
95
+ if (!this.jwtHook)
96
+ throw new core_util_1.ApiImplementationError('jwt() was accepted with no JwtHook bound.');
97
+ return this.jwtHook.parseJwt(token);
98
+ }
99
+ /** `oidcSameProject()` / `oidcSvcs([...])`: one token, verified once against every declared scope. */
100
+ async verifyOidc(token, methods) {
101
+ const identity = this.oidcHook
102
+ ? await this.oidcHook.verifyOidc(token, methods)
103
+ : await this.oidcVerifier.verify(token, methods);
104
+ if (!identity)
105
+ throw new core_util_1.ApiImplementationError('OIDC verifier must return its verified machine identity.');
106
+ return identity;
107
+ }
108
+ /** `sharedSecret(key)`: the `Webpieces <secret>` value against the bound secrets for `key`. */
109
+ verifySharedSecret(provided, secretKey) {
110
+ const accepted = this.authConfig?.sharedSecrets[secretKey];
111
+ if (!accepted)
112
+ throw new core_util_1.ApiImplementationError(`sharedSecret('${secretKey}') was accepted with no AuthConfig value bound.`);
113
+ if (!SharedSecretComparison.matchesEither(provided, accepted)) {
114
+ throw new core_util_1.ApiUnauthorizedError('Invalid shared secret for sharedSecret(...) credential');
115
+ }
116
+ }
117
+ /** `apiKey(regime, credentials)`: the app's {@link ApiKeyHook}, over the whole inbound request. */
118
+ async verifyApiKey(regime, request) {
119
+ if (!this.apiKeyHook)
120
+ throw new core_util_1.ApiImplementationError('apiKey(...) was accepted with no ApiKeyHook bound.');
121
+ return this.apiKeyHook.verifyApiKey(regime, request);
122
+ }
123
+ };
124
+ exports.CredentialVerifiers = CredentialVerifiers;
125
+ exports.CredentialVerifiers = CredentialVerifiers = tslib_1.__decorate([
126
+ (0, core_context_1.provideFrameworkSingleton)(),
127
+ tslib_1.__param(0, (0, inversify_1.inject)(DefaultOidcVerifier_1.DefaultOidcVerifier)),
128
+ tslib_1.__param(1, (0, inversify_1.optional)()),
129
+ tslib_1.__param(1, (0, inversify_1.inject)(AuthConfig_1.AUTH_CONFIG)),
130
+ tslib_1.__param(2, (0, inversify_1.optional)()),
131
+ tslib_1.__param(2, (0, inversify_1.inject)(AuthHooks_1.JWT_HOOK)),
132
+ tslib_1.__param(3, (0, inversify_1.optional)()),
133
+ tslib_1.__param(3, (0, inversify_1.inject)(AuthHooks_1.OIDC_HOOK)),
134
+ tslib_1.__param(4, (0, inversify_1.optional)()),
135
+ tslib_1.__param(4, (0, inversify_1.inject)(AuthHooks_1.API_KEY_HOOK)),
136
+ tslib_1.__metadata("design:paramtypes", [DefaultOidcVerifier_1.DefaultOidcVerifier,
137
+ AuthConfig_1.AuthConfig,
138
+ AuthHooks_1.JwtHook,
139
+ AuthHooks_1.OidcHook,
140
+ AuthHooks_1.ApiKeyHook])
141
+ ], CredentialVerifiers);
142
+ //# sourceMappingURL=CredentialVerifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CredentialVerifiers.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/CredentialVerifiers.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, optional } from 'inversify';\nimport { timingSafeEqual } from 'crypto';\nimport { HttpRequest, provideFrameworkSingleton } from '@webpieces/core-context';\nimport {\n ApiImplementationError,\n ApiUnauthorizedError,\n AuthMethod,\n OidcAuthMethods,\n} from '@webpieces/core-util';\nimport {\n AuthConfig,\n AUTH_CONFIG,\n AuthenticatedCaller,\n AuthenticatedMachineIdentity,\n SharedSecrets,\n} from './AuthConfig';\nimport { ApiKeyHook, API_KEY_HOOK, JwtHook, JWT_HOOK, OidcHook, OIDC_HOOK } from './AuthHooks';\nimport { DefaultOidcVerifier } from './DefaultOidcVerifier';\n\n/**\n * The ONE constant-time comparison behind `sharedSecret(...)`, shared by {@link AuthFilter} and\n * {@link CredentialVerifiers} so the two doors cannot disagree about what a matching secret is.\n */\nexport class SharedSecretComparison {\n /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */\n // webpieces-disable no-function-outside-class -- stateless comparison shared by two verifiers\n static matchesEither(provided: string, accepted: SharedSecrets): boolean {\n return (\n (accepted.secret1 !== '' &&\n SharedSecretComparison.constantTimeEquals(provided, accepted.secret1)) ||\n (accepted.secret2 !== '' &&\n SharedSecretComparison.constantTimeEquals(provided, accepted.secret2))\n );\n }\n\n // webpieces-disable no-function-outside-class -- stateless comparison shared by two verifiers\n private static 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\n/**\n * The credential VERIFIERS an app bound, for a door that is not a route — today the MCP HTTP endpoint\n * (`@webpieces/mcp-server`), whose `setAuth([...])` takes the same `@WpAuth` builders an endpoint\n * does. It holds exactly the pieces {@link AuthFilter} holds, resolved from the same container with the\n * same tokens, so a `jwt()` on an MCP door is verified by the SAME `JwtHook` that verifies a `jwt()`\n * endpoint, and `oidcSameProject()` by the same `OidcHook` / {@link DefaultOidcVerifier}:\n *\n * - `oidc` → the bound {@link OidcHook} if any, else the framework {@link DefaultOidcVerifier}.\n * - `jwt` → the bound {@link JwtHook}.\n * - `shared-secret` → the bound {@link AuthConfig}'s values, compared in constant time.\n * - `apikey` → the bound {@link ApiKeyHook}.\n *\n * It verifies; it does not publish. Publishing the caller into `RequestContext` and reconciling inbound\n * trusted headers stay the door's job, exactly as they are {@link AuthFilter}'s.\n *\n * Get it with `router.credentialVerifiers()`. {@link missingVerifier} is the startup check: a door that\n * accepts a mode nobody can verify refuses to boot, naming the binding line, the same rule #1362 gives\n * served routes.\n */\n@provideFrameworkSingleton()\nexport class CredentialVerifiers {\n constructor(\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- DI-resolved param; the esbuild/vitest path elides type-only imports (no design:paramtypes), so the explicit token is required\n @inject(DefaultOidcVerifier) private readonly oidcVerifier: DefaultOidcVerifier,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- explicit token required for DI-resolved param\n @optional() @inject(AUTH_CONFIG) private readonly authConfig?: AuthConfig,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- explicit token required for DI-resolved param\n @optional() @inject(JWT_HOOK) private readonly jwtHook?: JwtHook<never>,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- explicit token required for DI-resolved param\n @optional() @inject(OIDC_HOOK) private readonly oidcHook?: OidcHook,\n // webpieces-disable inject-annotation-not-needed-for-concrete-class -- explicit token required for DI-resolved param\n @optional() @inject(API_KEY_HOOK) private readonly apiKeyHook?: ApiKeyHook,\n ) {}\n\n /**\n * The binding line a door must add before it can accept `method`, or `undefined` when the method\n * is verifiable with what is bound. OIDC always is (the framework verifier). `vendorAuth` and\n * `public` are not credential verifiers a door can use, so they are reported too.\n */\n missingVerifier(method: AuthMethod): string | undefined {\n switch (method.kind) {\n case 'oidc':\n return undefined;\n case 'jwt':\n return this.jwtHook\n ? undefined\n : 'binder.bind(JWT_HOOK).to(YourJwtHook).inSingletonScope();';\n case 'apikey':\n return this.apiKeyHook\n ? undefined\n : 'binder.bind(API_KEY_HOOK).to(YourApiKeyHook).inSingletonScope();';\n case 'shared-secret':\n return this.authConfig?.sharedSecrets[method.secretKey]\n ? undefined\n : `binder.bind(AUTH_CONFIG).to(YourAuthConfig).inSingletonScope(); // holding { '${method.secretKey}': new SharedSecrets(secret1, secret2) }`;\n case 'vendorAuth':\n return `vendorAuth('${method.name}') identifies a vendor, not a caller to authorize; it cannot be verified on this door`;\n }\n }\n\n /** `jwt()`: the app's {@link JwtHook}. Throws `ApiUnauthorizedError` to deny. */\n async verifyJwt(token: string): Promise<AuthenticatedCaller> {\n if (!this.jwtHook)\n throw new ApiImplementationError('jwt() was accepted with no JwtHook bound.');\n return this.jwtHook.parseJwt(token);\n }\n\n /** `oidcSameProject()` / `oidcSvcs([...])`: one token, verified once against every declared scope. */\n async verifyOidc(\n token: string,\n methods: OidcAuthMethods,\n ): Promise<AuthenticatedMachineIdentity> {\n const identity = this.oidcHook\n ? await this.oidcHook.verifyOidc(token, methods)\n : await this.oidcVerifier.verify(token, methods);\n if (!identity)\n throw new ApiImplementationError(\n 'OIDC verifier must return its verified machine identity.',\n );\n return identity;\n }\n\n /** `sharedSecret(key)`: the `Webpieces <secret>` value against the bound secrets for `key`. */\n verifySharedSecret(provided: string, secretKey: string): void {\n const accepted = this.authConfig?.sharedSecrets[secretKey];\n if (!accepted)\n throw new ApiImplementationError(\n `sharedSecret('${secretKey}') was accepted with no AuthConfig value bound.`,\n );\n if (!SharedSecretComparison.matchesEither(provided, accepted)) {\n throw new ApiUnauthorizedError(\n 'Invalid shared secret for sharedSecret(...) credential',\n );\n }\n }\n\n /** `apiKey(regime, credentials)`: the app's {@link ApiKeyHook}, over the whole inbound request. */\n async verifyApiKey(regime: string, request: HttpRequest): Promise<AuthenticatedCaller> {\n if (!this.apiKeyHook)\n throw new ApiImplementationError('apiKey(...) was accepted with no ApiKeyHook bound.');\n return this.apiKeyHook.verifyApiKey(regime, request);\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { LazyController } from './LazyController';
2
2
  import { AuthorizationService } from './AuthorizationHook';
3
+ import { CredentialVerifiers } from './CredentialVerifiers';
3
4
  import { InvocationAuthentication } from './InvocationAuthentication';
4
5
  import { Container, ContainerModule } from 'inversify';
5
6
  import { RouteBuilderImpl } from './RouteBuilderImpl';
@@ -105,6 +106,12 @@ export declare class WebpiecesRouter implements ApiFactory {
105
106
  */
106
107
  apiClients(): ApiClient[];
107
108
  authorizationService(): AuthorizationService;
109
+ /**
110
+ * The credential verifiers this app bound (JwtHook, OidcHook / DefaultOidcVerifier, AuthConfig,
111
+ * ApiKeyHook), for a door that is not a route — the MCP HTTP endpoint's `setAuth([...])` verifies
112
+ * its `@WpAuth` modes with exactly these, so it cannot drift from {@link AuthFilter}.
113
+ */
114
+ credentialVerifiers(): CredentialVerifiers;
108
115
  /**
109
116
  * Warm and validate every optional implementation in a SEPARATE build/integration process with
110
117
  * fake external providers. Never invoke this in the production entry point or from a request.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebpiecesRouterFactory = exports.WebpiecesRouter = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const AuthorizationHook_1 = require("./AuthorizationHook");
6
+ const CredentialVerifiers_1 = require("./CredentialVerifiers");
6
7
  const inversify_1 = require("inversify");
7
8
  const binding_decorators_1 = require("@inversifyjs/binding-decorators");
8
9
  const core_util_1 = require("@webpieces/core-util");
@@ -146,6 +147,14 @@ let WebpiecesRouter = class WebpiecesRouter {
146
147
  authorizationService() {
147
148
  return this.appContainer.get(AuthorizationHook_1.AuthorizationService);
148
149
  }
150
+ /**
151
+ * The credential verifiers this app bound (JwtHook, OidcHook / DefaultOidcVerifier, AuthConfig,
152
+ * ApiKeyHook), for a door that is not a route — the MCP HTTP endpoint's `setAuth([...])` verifies
153
+ * its `@WpAuth` modes with exactly these, so it cannot drift from {@link AuthFilter}.
154
+ */
155
+ credentialVerifiers() {
156
+ return this.appContainer.get(CredentialVerifiers_1.CredentialVerifiers);
157
+ }
149
158
  /**
150
159
  * Warm and validate every optional implementation in a SEPARATE build/integration process with
151
160
  * fake external providers. Never invoke this in the production entry point or from a request.
@@ -1 +1 @@
1
- {"version":3,"file":"WebpiecesRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebpiecesRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { LazyController } from './LazyController';\nimport { AuthorizationService } from './AuthorizationHook';\nimport { InvocationAuthentication } from './InvocationAuthentication';\nimport { Container, ContainerModule, inject } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport {\n provideFrameworkSingleton,\n buildFrameworkModule,\n RequestContext,\n} from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiRoutingFactory } from './ApiRoutingFactory';\nimport type { ClassType } from './ClassType';\nimport { FilterDefinition } from './WebAppMeta';\nimport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\nimport { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { LogApiFilter } from './filters/LogApiFilter';\nimport { AuthFilter } from './filters/AuthFilter';\nimport { AuthorizationFilter } from './filters/AuthorizationFilter';\nimport {\n RuntimeStartupObserver,\n RuntimeStartupPhase,\n RuntimeStartupTiming,\n} from './RuntimeStartupTiming';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create} — one object (config lives inside it).\n *\n * appBindings - DI ContainerModules to load (framework + app). Loaded after the\n * @provideSingleton auto-scan so they can add/override bindings.\n * appOverrides - A single ContainerModule loaded LAST so tests can rebind real\n * controllers/clients to mocks (see @webpieces/core-mock createMock()).\n * config - Optional {@link WebpiecesConfig} (recording flags, etc.); defaults to a fresh one.\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n config?: WebpiecesConfig;\n /** Optional startup spans; no observer means no clock or timing-output work. */\n startupObserver?: RuntimeStartupObserver;\n}\n\n/**\n * WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter\n * chain + an in-process API client. It has NO express dependency, so it runs anywhere\n * node runs and is fully testable with zero HTTP.\n *\n * DI-resolved from the platform container (like the old WebpiecesServerImpl):\n * `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in\n * initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.\n *\n * Two-container pattern (mirrors Java WebPieces):\n * - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)\n * - appContainer : your controllers/filters/modules (a child of the framework one)\n *\n * Usage:\n * ```typescript\n * const router = await WebpiecesRouterFactory.create({ appBindings: [AppModule] });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, MyFilter, '*')); // your own filters\n * // (LogApiFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven.\n * // LogApiFilter logs request+response for EVERY call — do NOT install it yourself.)\n *\n * // test (no express): runs the SAME filter chain (incl. auth) -> controller\n * const api = router.createApiClient(SaveApi);\n * await api.save(new SaveRequest(...));\n * ```\n *\n * To serve real HTTP, hand this router to the express adapter in @webpieces/http-server\n * (bindExpress / bindAndStartExpress) — express lifecycle lives THERE, never here.\n *\n * @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.\n */\n@DocumentDesign()\n@provideFrameworkSingleton()\nexport class WebpiecesRouter implements ApiFactory {\n private webpiecesContainer!: Container;\n private appContainer!: Container;\n private startupTiming = new RuntimeStartupTiming();\n\n constructor(\n @inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl,\n @inject(ApiClientFactory) private readonly apiClientFactory: ApiClientFactory,\n ) {}\n\n /**\n * Build the app container (child of the framework container), load the @provideSingleton\n * auto-scan + appBindings + appOverrides, and point the RouteBuilder at it. Called once by\n * the factory after this router is resolved from the framework container.\n */\n async initialize(\n webpiecesContainer: Container,\n options: WebpiecesRouterOptions,\n ): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n this.startupTiming = new RuntimeStartupTiming(options.startupObserver);\n this.routeBuilder.setStartupObserver(options.startupObserver);\n\n // App container is a child so app bindings see framework bindings while staying separate.\n // autobind:true lets a concrete @injectable(Singleton) app class self-bind on first resolve\n // (inject-by-type, no @provideSingleton). Framework singletons stay explicit in the parent\n // (provideFrameworkSingleton -> buildFrameworkModule), found before child-autobind kicks in.\n this.appContainer = new Container({ parent: webpiecesContainer, autobind: true });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n this.startupTiming.run(RuntimeStartupPhase.FIXED_FILTERS, 'fixed-filters', () =>\n this.installFixedFilters(),\n );\n }\n\n /**\n * Auto-install the two fixed framework filters on every route (apps add only their own\n * filters below these): LogApiFilter outermost (logs request + response/failure for every\n * call and stamps [Controller.method], then re-throws for the transport to translate), then\n * AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND\n * in-process — there is no transport tier. Because LogApiFilter is outermost, requests that\n * AuthFilter rejects (401) are still logged with their body + controller identity.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, LogApiFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\n this.addFilter(new FilterDefinition(800_000, AuthorizationFilter, '*'));\n }\n\n private async loadDIModules(options: WebpiecesRouterOptions): Promise<void> {\n // Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's\n // own @provideSingleton classes (binding-decorators global). A client's\n // buildProviderModule() only ever contains the client's classes — never framework internals.\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.FRAMEWORK_DI,\n 'application-framework-bindings',\n () => this.appContainer.load(buildFrameworkModule()),\n );\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n 'provider-bindings',\n () => this.appContainer.load(buildProviderModule()),\n );\n\n // Load all app modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const [index, module] of options.appBindings.entries()) {\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n `app-binding-${index}`,\n () => this.appContainer.load(module),\n );\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n const overrides = options.appOverrides;\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n 'app-overrides',\n () => this.appContainer.load(overrides),\n );\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * A class resolves eagerly. A LazyController retains metadata now and imports its implementation\n * only after the ordinary filter chain permits an invocation.\n */\n addRoutes<TApi extends object, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController> | LazyController<TController>,\n ): this {\n this.startupTiming.run(RuntimeStartupPhase.ROUTES, `${api.name}:${controller.name}`, () =>\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder),\n );\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * LogApiFilter + AuthFilter).\n */\n addFilter(filter: FilterDefinition): this {\n this.routeBuilder.addFilter(filter);\n return this;\n }\n\n /**\n * Create an in-process API client that runs the api-tier filter chain + controller\n * with NO express/HTTP. The primary path for tests and node-only callers.\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n return this.apiClientFactory.createApiClient(apiPrototype);\n }\n\n /** Transport-only credential bridge; ordinary endpoint filters remain mandatory. */\n // webpieces-disable no-any-unknown -- abstract API constructor signature\n createInvocationApiClient<T>(\n apiPrototype: abstract new (...args: any[]) => T,\n authentication: InvocationAuthentication,\n ): T {\n return this.apiClientFactory.createInvocationApiClient(apiPrototype, authentication);\n }\n\n /**\n * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy) via\n * the shared {@link ApiClientFactory}. This is the ONLY handoff to the express layer — the\n * internal RouteBuilder never leaves.\n */\n apiClients(): ApiClient[] {\n return this.apiClientFactory.apiClients();\n }\n\n authorizationService(): AuthorizationService {\n return this.appContainer.get(AuthorizationService);\n }\n\n /**\n * Warm and validate every optional implementation in a SEPARATE build/integration process with\n * fake external providers. Never invoke this in the production entry point or from a request.\n */\n async validateLazyRoutesForBuild(): Promise<void> {\n if (RequestContext.isActive())\n throw new Error('Lazy implementation build validation cannot run inside a request.');\n await this.routeBuilder.validateLazyRoutesForBuild();\n }\n\n /** The application DI container (child of the framework container). */\n getContainer(): Container {\n return this.appContainer;\n }\n}\n\n/**\n * Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors\n * WebpiecesServerFactory.create), RESOLVES the router from DI, then initializes its app child\n * container with the @provideSingleton auto-scan + appBindings + optional test overrides.\n */\nexport class WebpiecesRouterFactory {\n static async create(options: WebpiecesRouterOptions): Promise<WebpiecesRouter> {\n // Platform (framework) container — build via buildFrameworkModule so framework\n // singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,\n // NOT the client's global one.\n const timing = new RuntimeStartupTiming(options.startupObserver);\n const webpiecesContainer = new Container();\n webpiecesContainer\n .bind(WEBPIECES_CONFIG_TOKEN)\n .toConstantValue(options.config ?? new WebpiecesConfig());\n await timing.runAsync(RuntimeStartupPhase.FRAMEWORK_DI, 'platform-framework-bindings', () =>\n webpiecesContainer.load(buildFrameworkModule()),\n );\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = timing.run(RuntimeStartupPhase.FRAMEWORK_DI, 'router-resolution', () =>\n webpiecesContainer.get(WebpiecesRouter),\n );\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}
1
+ {"version":3,"file":"WebpiecesRouter.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/WebpiecesRouter.ts"],"names":[],"mappings":"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","sourcesContent":["import { LazyController } from './LazyController';\nimport { AuthorizationService } from './AuthorizationHook';\nimport { CredentialVerifiers } from './CredentialVerifiers';\nimport { InvocationAuthentication } from './InvocationAuthentication';\nimport { Container, ContainerModule, inject } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport {\n provideFrameworkSingleton,\n buildFrameworkModule,\n RequestContext,\n} from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiRoutingFactory } from './ApiRoutingFactory';\nimport type { ClassType } from './ClassType';\nimport { FilterDefinition } from './WebAppMeta';\nimport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\nimport { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { LogApiFilter } from './filters/LogApiFilter';\nimport { AuthFilter } from './filters/AuthFilter';\nimport { AuthorizationFilter } from './filters/AuthorizationFilter';\nimport {\n RuntimeStartupObserver,\n RuntimeStartupPhase,\n RuntimeStartupTiming,\n} from './RuntimeStartupTiming';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create} — one object (config lives inside it).\n *\n * appBindings - DI ContainerModules to load (framework + app). Loaded after the\n * @provideSingleton auto-scan so they can add/override bindings.\n * appOverrides - A single ContainerModule loaded LAST so tests can rebind real\n * controllers/clients to mocks (see @webpieces/core-mock createMock()).\n * config - Optional {@link WebpiecesConfig} (recording flags, etc.); defaults to a fresh one.\n */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\n config?: WebpiecesConfig;\n /** Optional startup spans; no observer means no clock or timing-output work. */\n startupObserver?: RuntimeStartupObserver;\n}\n\n/**\n * WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter\n * chain + an in-process API client. It has NO express dependency, so it runs anywhere\n * node runs and is fully testable with zero HTTP.\n *\n * DI-resolved from the platform container (like the old WebpiecesServerImpl):\n * `@provideSingleton @injectable`, RouteBuilderImpl injected, and the two containers set in\n * initialize(). Built by {@link WebpiecesRouterFactory.create} — never `new`ed by callers.\n *\n * Two-container pattern (mirrors Java WebPieces):\n * - webpiecesContainer : framework bindings (config token, @DocumentDesign design roots)\n * - appContainer : your controllers/filters/modules (a child of the framework one)\n *\n * Usage:\n * ```typescript\n * const router = await WebpiecesRouterFactory.create({ appBindings: [AppModule] });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, MyFilter, '*')); // your own filters\n * // (LogApiFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven.\n * // LogApiFilter logs request+response for EVERY call — do NOT install it yourself.)\n *\n * // test (no express): runs the SAME filter chain (incl. auth) -> controller\n * const api = router.createApiClient(SaveApi);\n * await api.save(new SaveRequest(...));\n * ```\n *\n * To serve real HTTP, hand this router to the express adapter in @webpieces/http-server\n * (bindExpress / bindAndStartExpress) — express lifecycle lives THERE, never here.\n *\n * @DocumentDesign marks it a design root so it appears in http-routing's designed-lib graph.\n */\n@DocumentDesign()\n@provideFrameworkSingleton()\nexport class WebpiecesRouter implements ApiFactory {\n private webpiecesContainer!: Container;\n private appContainer!: Container;\n private startupTiming = new RuntimeStartupTiming();\n\n constructor(\n @inject(RouteBuilderImpl) private readonly routeBuilder: RouteBuilderImpl,\n @inject(ApiClientFactory) private readonly apiClientFactory: ApiClientFactory,\n ) {}\n\n /**\n * Build the app container (child of the framework container), load the @provideSingleton\n * auto-scan + appBindings + appOverrides, and point the RouteBuilder at it. Called once by\n * the factory after this router is resolved from the framework container.\n */\n async initialize(\n webpiecesContainer: Container,\n options: WebpiecesRouterOptions,\n ): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n this.startupTiming = new RuntimeStartupTiming(options.startupObserver);\n this.routeBuilder.setStartupObserver(options.startupObserver);\n\n // App container is a child so app bindings see framework bindings while staying separate.\n // autobind:true lets a concrete @injectable(Singleton) app class self-bind on first resolve\n // (inject-by-type, no @provideSingleton). Framework singletons stay explicit in the parent\n // (provideFrameworkSingleton -> buildFrameworkModule), found before child-autobind kicks in.\n this.appContainer = new Container({ parent: webpiecesContainer, autobind: true });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n this.startupTiming.run(RuntimeStartupPhase.FIXED_FILTERS, 'fixed-filters', () =>\n this.installFixedFilters(),\n );\n }\n\n /**\n * Auto-install the two fixed framework filters on every route (apps add only their own\n * filters below these): LogApiFilter outermost (logs request + response/failure for every\n * call and stamps [Controller.method], then re-throws for the transport to translate), then\n * AuthFilter (enforces the endpoint's AuthMode off the HttpRequest). Both run over HTTP AND\n * in-process — there is no transport tier. Because LogApiFilter is outermost, requests that\n * AuthFilter rejects (401) are still logged with their body + controller identity.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, LogApiFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\n this.addFilter(new FilterDefinition(800_000, AuthorizationFilter, '*'));\n }\n\n private async loadDIModules(options: WebpiecesRouterOptions): Promise<void> {\n // Load BOTH registries: framework classes (provideFrameworkSingleton) + the client's\n // own @provideSingleton classes (binding-decorators global). A client's\n // buildProviderModule() only ever contains the client's classes — never framework internals.\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.FRAMEWORK_DI,\n 'application-framework-bindings',\n () => this.appContainer.load(buildFrameworkModule()),\n );\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n 'provider-bindings',\n () => this.appContainer.load(buildProviderModule()),\n );\n\n // Load all app modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const [index, module] of options.appBindings.entries()) {\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n `app-binding-${index}`,\n () => this.appContainer.load(module),\n );\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n const overrides = options.appOverrides;\n await this.startupTiming.runAsync(\n RuntimeStartupPhase.APPLICATION_DI,\n 'app-overrides',\n () => this.appContainer.load(overrides),\n );\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * A class resolves eagerly. A LazyController retains metadata now and imports its implementation\n * only after the ordinary filter chain permits an invocation.\n */\n addRoutes<TApi extends object, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController> | LazyController<TController>,\n ): this {\n this.startupTiming.run(RuntimeStartupPhase.ROUTES, `${api.name}:${controller.name}`, () =>\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder),\n );\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * LogApiFilter + AuthFilter).\n */\n addFilter(filter: FilterDefinition): this {\n this.routeBuilder.addFilter(filter);\n return this;\n }\n\n /**\n * Create an in-process API client that runs the api-tier filter chain + controller\n * with NO express/HTTP. The primary path for tests and node-only callers.\n */\n // webpieces-disable no-any-unknown -- abstract constructor signature requires any[] args\n createApiClient<T>(apiPrototype: abstract new (...args: any[]) => T): T {\n return this.apiClientFactory.createApiClient(apiPrototype);\n }\n\n /** Transport-only credential bridge; ordinary endpoint filters remain mandatory. */\n // webpieces-disable no-any-unknown -- abstract API constructor signature\n createInvocationApiClient<T>(\n apiPrototype: abstract new (...args: any[]) => T,\n authentication: InvocationAuthentication,\n ): T {\n return this.apiClientFactory.createInvocationApiClient(apiPrototype, authentication);\n }\n\n /**\n * Reify the registered APIs as {@link ApiClient}s (contract + the createApiClient proxy) via\n * the shared {@link ApiClientFactory}. This is the ONLY handoff to the express layer — the\n * internal RouteBuilder never leaves.\n */\n apiClients(): ApiClient[] {\n return this.apiClientFactory.apiClients();\n }\n\n authorizationService(): AuthorizationService {\n return this.appContainer.get(AuthorizationService);\n }\n\n /**\n * The credential verifiers this app bound (JwtHook, OidcHook / DefaultOidcVerifier, AuthConfig,\n * ApiKeyHook), for a door that is not a route — the MCP HTTP endpoint's `setAuth([...])` verifies\n * its `@WpAuth` modes with exactly these, so it cannot drift from {@link AuthFilter}.\n */\n credentialVerifiers(): CredentialVerifiers {\n return this.appContainer.get(CredentialVerifiers);\n }\n\n /**\n * Warm and validate every optional implementation in a SEPARATE build/integration process with\n * fake external providers. Never invoke this in the production entry point or from a request.\n */\n async validateLazyRoutesForBuild(): Promise<void> {\n if (RequestContext.isActive())\n throw new Error('Lazy implementation build validation cannot run inside a request.');\n await this.routeBuilder.validateLazyRoutesForBuild();\n }\n\n /** The application DI container (child of the framework container). */\n getContainer(): Container {\n return this.appContainer;\n }\n}\n\n/**\n * Builds a {@link WebpiecesRouter}: constructs the platform container (mirrors\n * WebpiecesServerFactory.create), RESOLVES the router from DI, then initializes its app child\n * container with the @provideSingleton auto-scan + appBindings + optional test overrides.\n */\nexport class WebpiecesRouterFactory {\n static async create(options: WebpiecesRouterOptions): Promise<WebpiecesRouter> {\n // Platform (framework) container — build via buildFrameworkModule so framework\n // singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,\n // NOT the client's global one.\n const timing = new RuntimeStartupTiming(options.startupObserver);\n const webpiecesContainer = new Container();\n webpiecesContainer\n .bind(WEBPIECES_CONFIG_TOKEN)\n .toConstantValue(options.config ?? new WebpiecesConfig());\n await timing.runAsync(RuntimeStartupPhase.FRAMEWORK_DI, 'platform-framework-bindings', () =>\n webpiecesContainer.load(buildFrameworkModule()),\n );\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = timing.run(RuntimeStartupPhase.FRAMEWORK_DI, 'router-resolution', () =>\n webpiecesContainer.get(WebpiecesRouter),\n );\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}
@@ -199,8 +199,6 @@ export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>>
199
199
  private enforceOidc;
200
200
  /** `provided` is the Authorization bearer value — the secret itself, same header as a JWT. */
201
201
  private enforceSharedSecret;
202
- /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */
203
- private matchesEither;
204
202
  /** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */
205
203
  private bestEffortJwt;
206
204
  /**
@@ -210,5 +208,4 @@ export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>>
210
208
  * where a JWT is expected), yields undefined and the caller 401s.
211
209
  */
212
210
  private credential;
213
- private constantTimeEquals;
214
211
  }
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.AuthFilter = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const inversify_1 = require("inversify");
7
- const crypto_1 = require("crypto");
8
7
  const core_context_1 = require("@webpieces/core-context");
9
8
  const core_util_1 = require("@webpieces/core-util");
10
9
  const core_util_2 = require("@webpieces/core-util");
@@ -13,6 +12,7 @@ const AuthHooks_1 = require("../AuthHooks");
13
12
  const AuthenticatedCallerContext_1 = require("../AuthenticatedCallerContext");
14
13
  const AuthorizationHook_1 = require("../AuthorizationHook");
15
14
  const DefaultOidcVerifier_1 = require("../DefaultOidcVerifier");
15
+ const CredentialVerifiers_1 = require("../CredentialVerifiers");
16
16
  const UnverifiableAuthModeError_1 = require("../UnverifiableAuthModeError");
17
17
  const log = core_util_1.LogManager.getLogger('AuthFilter');
18
18
  /**
@@ -205,7 +205,8 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
205
205
  await this.authenticateMethod(method, header, meta, oidc);
206
206
  return method;
207
207
  }
208
- catch (err) { // webpieces-disable no-any-unknown -- structured credential rejection at the boundary
208
+ catch (err) {
209
+ // webpieces-disable no-any-unknown -- structured credential rejection at the boundary
209
210
  const error = (0, core_util_1.toError)(err);
210
211
  if (!(error instanceof core_util_1.ApiUnauthorizedError) || alternatives.length === 1)
211
212
  throw error;
@@ -238,7 +239,8 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
238
239
  case 'apikey':
239
240
  await this.enforceApiKey(mode.regime, meta);
240
241
  return;
241
- case 'public': throw new core_util_1.ApiImplementationError('Public is not a protected auth alternative.');
242
+ case 'public':
243
+ throw new core_util_1.ApiImplementationError('Public is not a protected auth alternative.');
242
244
  }
243
245
  }
244
246
  /**
@@ -459,15 +461,10 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
459
461
  /** `provided` is the Authorization bearer value — the secret itself, same header as a JWT. */
460
462
  enforceSharedSecret(provided, secretKey, routeMeta) {
461
463
  const accepted = this.requireSharedSecrets(secretKey, routeMeta);
462
- if (!provided || !this.matchesEither(provided, accepted)) {
464
+ if (!provided || !CredentialVerifiers_1.SharedSecretComparison.matchesEither(provided, accepted)) {
463
465
  throw new core_util_1.ApiUnauthorizedError('Invalid shared secret for sharedSecret(...) endpoint');
464
466
  }
465
467
  }
466
- /** EITHER secret1 or secret2 passes — the rotation window. Constant-time on each non-empty slot. */
467
- matchesEither(provided, accepted) {
468
- return ((accepted.secret1 !== '' && this.constantTimeEquals(provided, accepted.secret1)) ||
469
- (accepted.secret2 !== '' && this.constantTimeEquals(provided, accepted.secret2)));
470
- }
471
468
  /** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */
472
469
  async bestEffortJwt(header) {
473
470
  const token = this.credential(header, BEARER_SCHEME);
@@ -498,14 +495,6 @@ let AuthFilter = AuthFilter_1 = class AuthFilter extends core_util_2.Filter {
498
495
  const prefix = `${scheme} `;
499
496
  return header.startsWith(prefix) ? header.substring(prefix.length) : undefined;
500
497
  }
501
- constantTimeEquals(a, b) {
502
- const bufA = Buffer.from(a, 'utf8');
503
- const bufB = Buffer.from(b, 'utf8');
504
- if (bufA.length !== bufB.length) {
505
- return false;
506
- }
507
- return (0, crypto_1.timingSafeEqual)(bufA, bufB);
508
- }
509
498
  };
510
499
  exports.AuthFilter = AuthFilter;
511
500
  exports.AuthFilter = AuthFilter = AuthFilter_1 = tslib_1.__decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"AuthFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-routing/src/filters/AuthFilter.ts"],"names":[],"mappings":";;;;;AAAA,yCAA6C;AAC7C,mCAAyC;AACzC,0DAOiC;AACjC,oDAa8B;AAC9B,oDAAuD;AAGvD,8CAAuE;AACvE,4CAOsB;AACtB,8EAA2E;AAC3E,4DAAsF;AACtF,gEAA6D;AAC7D,4EAAyE;AAEzE,MAAM,GAAG,GAAG,sBAAU,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;AAE/C;;;;;;GAMG;AACH,MAAM,oBAAoB,GAAG,eAAe,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAG,QAAQ,CAAC;AAC/B,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAGI,IAAM,UAAU,kBAAhB,MAAM,UAAW,SAAQ,kBAAuC;IAMjB;IAII;IAGH;IAGC;IAM/B;IAIkC;IAzBtC,aAAa,GAAG,IAAI,uDAA0B,EAAE,CAAC;IAElE,YAGkD,YAAiC,EAI7B,UAAuB,EAG1B,OAAwB,EAGvB,QAAmB,EAMlD,kBAAuC,EAIL,UAAuB;QAE1E,KAAK,EAAE,CAAC;QAtBsC,iBAAY,GAAZ,YAAY,CAAqB;QAI7B,eAAU,GAAV,UAAU,CAAa;QAG1B,YAAO,GAAP,OAAO,CAAiB;QAGvB,aAAQ,GAAR,QAAQ,CAAW;QAMlD,uBAAkB,GAAlB,kBAAkB,CAAqB;QAIL,eAAU,GAAV,UAAU,CAAa;IAG9E,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,qBAAqB,CAAC,SAAwB;QAC1C,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,kCAAsB,CAAC,mDAAmD,CAAC,CAAC;QACjG,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;gBAClB,KAAK,KAAK;oBAAE,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;oBAAC,MAAM;gBAClD,KAAK,eAAe;oBAAE,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;oBAAC,MAAM;gBACpF,KAAK,YAAY;oBAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;oBAAC,MAAM;gBAChI,KAAK,QAAQ;oBAAE,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;oBAAC,MAAM;gBACvE,KAAK,MAAM,CAAC;gBACZ,KAAK,QAAQ;oBACT,MAAM;YACd,CAAC;QACL,CAAC;IACL,CAAC;IAED,2HAA2H;IACnH,MAAM,CAAC,UAAU,CAAC,SAAwB;QAC9C,OAAO,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,mBAAmB,IAAI,YAAY,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,UAAU,IAAI,SAAS,CAAC,IAAI,GAAG,CAAC;IACvJ,CAAC;IAEO,YAAY,CAAC,SAAwB,EAAE,IAAY,EAAE,OAAe,EAAE,QAAgB;QAC1F,MAAM,QAAQ,GAAG,YAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAClD,OAAO,IAAI,qDAAyB,CAChC,YAAY,QAAQ,aAAa,IAAI,SAAS,OAAO,wCAAwC;YACzF,+BAA+B,QAAQ,IAAI;YAC3C,cAAc,IAAI,oEAAoE,EAC1F,QAAQ,EACR,IAAI,CACP,CAAC;IACN,CAAC;IAEO,cAAc,CAAC,SAAwB;QAC3C,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAC,OAAO,CAAC;QACtC,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,qBAAqB,EAC7D,2DAA2D,CAAC,CAAC;IACrE,CAAC;IAEO,oBAAoB,CAAC,SAAiB,EAAE,SAAwB;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC;QAC/B,MAAM,IAAI,GAAG,iBAAiB,SAAS,IAAI,CAAC;QAC5C,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,wBAAwB,EAC7D,wHAAwH,SAAS,0CAA0C,CAAC,CAAC;QACrL,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QACjD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC;QAC9B,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EACnC,yBAAyB,MAAM,CAAC,WAAW,CAAC,IAAI,iCAAiC,SAAS,GAAG,EAC7F,IAAI,SAAS,yEAAyE,MAAM,CAAC,WAAW,CAAC,IAAI,sDAAsD,CAAC,CAAC;IAC7K,CAAC;IAEO,qBAAqB,CAAC,IAAY,EAAE,SAAwB;QAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC;QACzC,MAAM,IAAI,GAAG,eAAe,IAAI,IAAI,CAAC;QACrC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EACnC,yFAAyF,EACzF,kFAAkF,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,QAAQ,CAAC;IACpB,CAAC;IAED,iGAAiG;IACzF,oBAAoB,CAAC,QAA4B,EAAE,IAAY,EAAE,SAAwB;QAC7F,MAAM,SAAS,GAAG,QAAQ,CAAC,oBAAoB,EAAE,CAAC;QAClD,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO;QACrC,MAAM,IAAI,GAAG,eAAe,IAAI,IAAI,CAAC;QACrC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EACnC,iCAAiC,QAAQ,CAAC,WAAW,CAAC,IAAI,cAAc,QAAQ,0DAA0D,IAAI,GAAG,EACjJ,IAAI,IAAI,QAAQ,QAAQ,CAAC,WAAW,CAAC,IAAI,oCAAoC,QAAQ,MAAM,IAAI,mBAAmB,IAAI,mBAAmB,CAAC,CAAC;IACnJ,CAAC;IAEO,iBAAiB,CAAC,MAAc,EAAE,SAAwB;QAC9D,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC,UAAU,CAAC;QAC5C,MAAM,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,WAAW,MAAM,iBAAiB,EAAE,wBAAwB,EAC3F,kEAAkE,CAAC,CAAC;IAC5E,CAAC;IAED,iGAAiG;IACxF,KAAK,CAAC,MAAM,CACjB,IAAgB,EAChB,UAAoD;QAEpD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QACrC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,kCAAsB,CAAC,mDAAmD,CAAC,CAAC;QACjG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS;YAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,wBAAwB,EAAE,CAAC;YAChC,IAAI,CAAC,wBAAwB,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC3D,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,MAAM,GAAG,6BAAc,CAAC,UAAU,EAAE,EAAE,SAAS,CAAC,oBAAoB,CAAC,CAAC;QAC5E,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACpC,MAAM,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YACjC,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC;YAC7C,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QACjF,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAC5B,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,YAAU,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3E,OAAO,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,wBAAwB,CAAC,OAA4B,EAAE,MAA0B,EAAE,IAAgB;QAC7G,MAAM,IAAI,GAAG,YAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1G,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;YAChC,iJAAiJ;YACjJ,IAAI,CAAC;gBACD,MAAM,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC1D,OAAO,MAAM,CAAC;YAClB,CAAC;YAAC,OAAO,GAAY,EAAE,CAAC,CAAC,sFAAsF;gBAC3G,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;gBAC3B,IAAI,CAAC,CAAC,KAAK,YAAY,gCAAoB,CAAC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;oBAAE,MAAM,KAAK,CAAC;YAC3F,CAAC;QACL,CAAC;QACD,MAAM,IAAI,gCAAoB,CAAC,8DAA8D,CAAC,CAAC;IACnG,CAAC;IAED,4FAA4F;IAC5F,sHAAsH;IAC9G,MAAM,CAAC,aAAa,CAAC,OAA4B;QACrD,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAgB,EAA4B,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;QACpG,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAEO,KAAK,CAAC,kBAAkB,CAAC,IAAc,EAAE,MAA0B,EAAE,IAAgB,EAAE,IAAiC;QAC5H,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,KAAK;gBAAE,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;gBAAC,OAAO;YAClE,KAAK,MAAM;gBAAE,MAAM,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAC,OAAO;YACpE,KAAK,eAAe;gBAChB,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,oBAAoB,CAAC,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;gBACxG,6BAAc,CAAC,UAAU,CAAC,2CAAuB,EAAE,IAAI,yCAAqB,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;gBAC/G,OAAO;YACX,KAAK,YAAY;gBAAE,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAAC,OAAO;YACzE,KAAK,QAAQ;gBAAE,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAAC,OAAO;YACnE,KAAK,QAAQ,CAAC,CAAC,MAAM,IAAI,kCAAsB,CAAC,6CAA6C,CAAC,CAAC;QACnG,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACK,YAAY,CAAC,IAAc;QAC/B,IAAI,6BAAc,CAAC,UAAU,CAAC,gCAAoB,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;YACxE,OAAO;QACX,CAAC;QACD,MAAM,OAAO,GAAG,YAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAC5C,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,6BAAc,CAAC,UAAU,CAAC,gCAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACrE,CAAC;IACL,CAAC;IAED;;;;;;;;;;;OAWG;IACH,mJAAmJ;IAC3I,MAAM,CAAC,UAAU,CAAC,IAAc;QACpC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,KAAK;gBACN,OAAO,KAAK,CAAC;YACjB,KAAK,QAAQ;gBACT,OAAO,YAAY,CAAC;YACxB,KAAK,MAAM,CAAC;YACZ,KAAK,eAAe,CAAC;YACrB,KAAK,YAAY,CAAC;YAClB,KAAK,QAAQ;gBACT,OAAO,SAAS,CAAC;QACzB,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACK,KAAK,CAAC,iBAAiB,CAAC,IAAY,EAAE,IAAgB;QAC1D,MAAM,QAAQ,GAAG,IAAI,CAAC,qBAAqB,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAClE,MAAM,OAAO,GAAG,6BAAc,CAAC,UAAU,EAAE,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,GAAG,CAAC,IAAI,CACJ,wBAAwB,IAAI,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,gCAAgC;gBAC1F,qFAAqF,IAAI,yBAAyB;gBAClH,yFAAyF,CAChG,CAAC;YACF,MAAM,IAAI,gCAAoB,CAC1B,mEAAmE,CACtE,CAAC;QACN,CAAC;QACD,6FAA6F;QAC7F,+DAA+D;QAC/D,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACpD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAED;;;;;OAKG;IACK,WAAW,CAAC,OAAgC;QAChD,OAAO,OAAO,EAAE,GAAG,KAAK,SAAS,CAAC;IACtC,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,KAAK,CAAC,aAAa,CAAC,MAAc,EAAE,IAAgB;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,6BAAc,CAAC,UAAU,EAAE,CAAC;QAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;YACX,GAAG,CAAC,IAAI,CACJ,oBAAoB,MAAM,eAAe,IAAI,CAAC,SAAS,CAAC,IAAI,8BAA8B;gBACtF,0FAA0F;gBAC1F,sDAAsD,CAC7D,CAAC;YACF,MAAM,IAAI,gCAAoB,CAC1B,8DAA8D,CACjE,CAAC;QACN,CAAC;QACD,6FAA6F;QAC7F,iFAAiF;QACjF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,iKAAiK;IACzJ,MAAM,CAAC,cAAc,CAAC,IAAc;QACxC,QAAQ,IAAI,CAAC,IAAI,EAAE,CAAC;YAChB,KAAK,MAAM,CAAC;YACZ,KAAK,eAAe;gBAChB,OAAO,IAAI,CAAC;YAChB,KAAK,KAAK,CAAC;YACX,KAAK,QAAQ,CAAC;YACd,yFAAyF;YACzF,yFAAyF;YACzF,wFAAwF;YACxF,uEAAuE;YACvE,2FAA2F;YAC3F,KAAK,YAAY,CAAC;YAClB,0FAA0F;YAC1F,qFAAqF;YACrF,2FAA2F;YAC3F,oGAAoG;YACpG,4FAA4F;YAC5F,KAAK,QAAQ;gBACT,OAAO,KAAK,CAAC;QACrB,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACK,gBAAgB,CAAC,IAAgB;QACrC,IAAI,2BAAe,CAAC,kBAAkB,EAAE,EAAE,CAAC;YACvC,OAAO;QACX,CAAC;QACD,GAAG,CAAC,IAAI,CACJ,kCAAkC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI;YACrD,CAAC,2BAAe,CAAC,UAAU,EAAE;gBACzB,CAAC,CAAC,wCAAwC;gBAC1C,CAAC,CAAC,iFAAiF;oBACjF,mFAAmF,CAAC,CACjG,CAAC;QACF,sFAAsF;QACtF,MAAM,IAAI,oCAAwB,CAAC,kBAAkB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAChF,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,MAA0B,EAAE,SAAwB;QACzE,MAAM,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,gCAAoB,CAAC,yBAAyB,CAAC,CAAC;QACtE,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,MAA0B,EAAE,OAAwB;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,gCAAoB,CAAC,2BAA2B,CAAC,CAAC;QACxE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ;YAC1B,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC;YAChD,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACrD,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,kCAAsB,CAAC,0DAA0D,CAAC,CAAC;QAC5G,6BAAc,CAAC,UAAU,CAAC,2CAAuB,EAAE,IAAI,yCAAqB,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzH,CAAC;IAED,8FAA8F;IACtF,mBAAmB,CAAC,QAA4B,EAAE,SAAiB,EAAE,SAAwB;QACjG,MAAM,QAAQ,GAAG,IAAI,CAAC,oBAAoB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACjE,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,gCAAoB,CAC1B,sDAAsD,CACzD,CAAC;QACN,CAAC;IACL,CAAC;IAED,oGAAoG;IAC5F,aAAa,CAAC,QAAgB,EAAE,QAAuB;QAC3D,OAAO,CACH,CAAC,QAAQ,CAAC,OAAO,KAAK,EAAE,IAAI,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;YAChF,CAAC,QAAQ,CAAC,OAAO,KAAK,EAAE,IAAI,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CACnF,CAAC;IACN,CAAC;IAED,4FAA4F;IACpF,KAAK,CAAC,aAAa,CAAC,MAA0B;QAClD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QACrD,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,CAAC;YAC1B,OAAO;QACX,CAAC;QACD,yKAAyK;QACzK,IAAI,CAAC;YACD,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACnE,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAA,mBAAO,EAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,CAAC,CAAC,KAAK,YAAY,gCAAoB,CAAC;gBAAE,MAAM,KAAK,CAAC;YAC1D,GAAG,CAAC,KAAK,CACL,6EAA6E,EAC7E,KAAK,CACR,CAAC;QACN,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACK,UAAU,CAAC,MAA0B,EAAE,MAAc;QACzD,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACrB,CAAC;QACD,MAAM,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC;QAC5B,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACnF,CAAC;IAEO,kBAAkB,CAAC,CAAS,EAAE,CAAS;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACpC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAA,wBAAe,EAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;CACJ,CAAA;AAleY,gCAAU;qBAAV,UAAU;IAFtB,IAAA,wCAAyB,GAAE;IAC5B,iGAAiG;;IAOxF,mBAAA,IAAA,kBAAM,EAAC,yCAAmB,CAAC,CAAA;IAI3B,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAAE,mBAAA,IAAA,kBAAM,EAAC,wBAAW,CAAC,CAAA;IAG/B,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAAE,mBAAA,IAAA,kBAAM,EAAC,oBAAQ,CAAC,CAAA;IAG5B,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAAE,mBAAA,IAAA,kBAAM,EAAC,qBAAS,CAAC,CAAA;IAI7B,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IACV,mBAAA,IAAA,kBAAM,EAAC,mCAAoB,CAAC,CAAA;IAK5B,mBAAA,IAAA,oBAAQ,GAAE,CAAA;IAAE,mBAAA,IAAA,kBAAM,EAAC,wBAAY,CAAC,CAAA;6CApB2B,yCAAmB;QAIhB,uBAAU;QAGhB,mBAAO;QAGL,oBAAQ;QAM7B,iCAAkB;QAIQ,sBAAU;GA1BrE,UAAU,CAketB","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"]}
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 {\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 { ApiKeyHook, API_KEY_HOOK, JwtHook, JWT_HOOK, OidcHook, OIDC_HOOK } from '../AuthHooks';\nimport { AuthenticatedCallerContext } from '../AuthenticatedCallerContext';\nimport { VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from '../AuthorizationHook';\nimport { DefaultOidcVerifier } from '../DefaultOidcVerifier';\nimport { SharedSecretComparison } from '../CredentialVerifiers';\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)\n throw new ApiImplementationError('Every endpoint requires @WpAuth or @WpAuthPublic.');\n for (const method of auth.methods) {\n switch (method.kind) {\n case 'jwt':\n this.requireJwtHook(routeMeta);\n break;\n case 'shared-secret':\n this.requireSharedSecrets(method.secretKey, routeMeta);\n break;\n case 'vendorAuth':\n this.assertVendorDeclared(\n this.requireVendorVerifier(method.name, routeMeta),\n method.name,\n routeMeta,\n );\n break;\n case 'apikey':\n this.requireApiKeyHook(method.regime, routeMeta);\n 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(\n routeMeta: RouteMetadata,\n mode: string,\n problem: string,\n bindLine: string,\n ): 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(\n routeMeta,\n 'jwt()',\n 'no JwtHook is bound',\n 'binder.bind(JWT_HOOK).to(YourJwtHook).inSingletonScope();',\n );\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(\n routeMeta,\n mode,\n 'no AuthConfig is bound',\n `binder.bind(AUTH_CONFIG).to(YourAuthConfig).inSingletonScope(); // YourAuthConfig extends AuthConfig and passes { '${secretKey}': new SharedSecrets(secret1, secret2) }`,\n );\n }\n const accepted = config.sharedSecrets[secretKey];\n if (accepted) return accepted;\n throw this.unverifiable(\n routeMeta,\n 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\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(\n routeMeta,\n 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 }\n return verifier;\n }\n\n /** Boot only: the bound verifier must DECLARE the route's vendor (#1362). Never calls verify. */\n private assertVendorDeclared(\n verifier: VendorAuthVerifier,\n name: string,\n routeMeta: RouteMetadata,\n ): 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(\n routeMeta,\n 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\n private requireApiKeyHook(regime: string, routeMeta: RouteMetadata): ApiKeyHook {\n if (this.apiKeyHook) return this.apiKeyHook;\n throw this.unverifiable(\n routeMeta,\n `apiKey('${regime}', credentials)`,\n 'no ApiKeyHook is bound',\n 'binder.bind(API_KEY_HOOK).to(YourApiKeyHook).inSingletonScope();',\n );\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)\n 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(\n methods: readonly AuthMode[],\n header: string | undefined,\n meta: MethodMeta,\n ): Promise<AuthMode> {\n const oidc = AuthFilter.oidcMethodsOf(methods);\n const alternatives = methods.filter(\n (method: AuthMode) => method.kind !== 'oidc' || method === oidc?.[0],\n );\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) {\n // webpieces-disable no-any-unknown -- structured credential rejection at the boundary\n const error = toError(err);\n if (!(error instanceof ApiUnauthorizedError) || alternatives.length === 1)\n throw error;\n }\n }\n throw new ApiUnauthorizedError(\n 'No declared credential mechanism authenticated this request.',\n );\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(\n (method: AuthMode): method is OidcAuthMethod => method.kind === 'oidc',\n );\n const first = oidc[0];\n return first === undefined ? undefined : [first, ...oidc.slice(1)];\n }\n\n private async authenticateMethod(\n mode: AuthMode,\n header: string | undefined,\n meta: MethodMeta,\n oidc: OidcAuthMethods | undefined,\n ): Promise<void> {\n switch (mode.kind) {\n case 'jwt':\n await this.enforceJwt(header, meta.routeMeta);\n return;\n case 'oidc':\n await this.enforceOidc(header, oidc ?? [mode]);\n return;\n case 'shared-secret':\n this.enforceSharedSecret(\n this.credential(header, SHARED_SECRET_SCHEME),\n mode.secretKey,\n meta.routeMeta,\n );\n RequestContext.putTrusted(\n VERIFIED_MACHINE_CALLER,\n new VerifiedMachineCaller('shared-secret', mode.secretKey),\n );\n return;\n case 'vendorAuth':\n await this.enforceVendorAuth(mode.name, meta);\n return;\n case 'apikey':\n await this.enforceApiKey(mode.regime, meta);\n return;\n case 'public':\n 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)\n throw new ApiImplementationError(\n 'OIDC verifier must return its verified machine identity.',\n );\n RequestContext.putTrusted(\n VERIFIED_MACHINE_CALLER,\n new VerifiedMachineCaller(identity.mechanism, identity.identity),\n );\n }\n\n /** `provided` is the Authorization bearer value — the secret itself, same header as a JWT. */\n private enforceSharedSecret(\n provided: string | undefined,\n secretKey: string,\n routeMeta: RouteMetadata,\n ): void {\n const accepted = this.requireSharedSecrets(secretKey, routeMeta);\n if (!provided || !SharedSecretComparison.matchesEither(provided, accepted)) {\n throw new ApiUnauthorizedError('Invalid shared secret for sharedSecret(...) endpoint');\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"]}
package/src/index.d.ts CHANGED
@@ -26,14 +26,15 @@ export { JwtHook, MintedJwt, JWT_HOOK, OidcHook, OIDC_HOOK, ApiKeyHook, API_KEY_
26
26
  export { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from '@webpieces/core-context';
27
27
  export { UnverifiableAuthModeError } from './UnverifiableAuthModeError';
28
28
  export { DefaultOidcVerifier } from './DefaultOidcVerifier';
29
+ export { CredentialVerifiers, SharedSecretComparison } from './CredentialVerifiers';
29
30
  export { DefaultJwtHook, DefaultJwtMintRequest } from './DefaultJwtHook';
30
31
  export { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';
31
32
  export { setupRuntime, RuntimeSetupOptions } from './setupRuntime';
32
33
  export { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';
33
- export { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZATION_HOOK, VERIFIED_MACHINE_CALLER, VerifiedMachineCaller } from './AuthorizationHook';
34
+ export { AuthorizationHook, AuthorizationService, CanonicalUserRoles, AUTHORIZATION_HOOK, VERIFIED_MACHINE_CALLER, VerifiedMachineCaller, } from './AuthorizationHook';
34
35
  export { AuthorizedApiDocument } from './AuthorizedApiDocument';
35
36
  export type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';
36
37
  export { WiringPolicy } from '@webpieces/http-client-core';
37
38
  export { RuntimeStartupPhase, RuntimeStartupOperation } from './RuntimeStartupTiming';
38
39
  export type { RuntimeStartupObserver, RuntimeStartupSpan } from './RuntimeStartupTiming';
39
- export { LazyController, LazyImplementation, ApplicationImplementation, LazyFeatureTimer, LazyFeatureDeadline } from './LazyController';
40
+ export { LazyController, LazyImplementation, ApplicationImplementation, LazyFeatureTimer, LazyFeatureDeadline, } from './LazyController';
package/src/index.js CHANGED
@@ -2,7 +2,7 @@
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
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.LazyFeatureDeadline = exports.LazyFeatureTimer = exports.ApplicationImplementation = exports.LazyImplementation = exports.LazyController = exports.RuntimeStartupOperation = exports.RuntimeStartupPhase = 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;
5
+ exports.LazyFeatureDeadline = exports.LazyFeatureTimer = exports.ApplicationImplementation = exports.LazyImplementation = exports.LazyController = exports.RuntimeStartupOperation = exports.RuntimeStartupPhase = 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 = exports.SharedSecretComparison = exports.CredentialVerifiers = 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; } });
@@ -151,6 +151,10 @@ var UnverifiableAuthModeError_1 = require("./UnverifiableAuthModeError");
151
151
  Object.defineProperty(exports, "UnverifiableAuthModeError", { enumerable: true, get: function () { return UnverifiableAuthModeError_1.UnverifiableAuthModeError; } });
152
152
  var DefaultOidcVerifier_1 = require("./DefaultOidcVerifier");
153
153
  Object.defineProperty(exports, "DefaultOidcVerifier", { enumerable: true, get: function () { return DefaultOidcVerifier_1.DefaultOidcVerifier; } });
154
+ // The same verifiers AuthFilter holds, for a door that is not a route (the MCP HTTP endpoint).
155
+ var CredentialVerifiers_1 = require("./CredentialVerifiers");
156
+ Object.defineProperty(exports, "CredentialVerifiers", { enumerable: true, get: function () { return CredentialVerifiers_1.CredentialVerifiers; } });
157
+ Object.defineProperty(exports, "SharedSecretComparison", { enumerable: true, get: function () { return CredentialVerifiers_1.SharedSecretComparison; } });
154
158
  // DefaultJwtHook: batteries-included HS256 JwtHook — `new DefaultJwtHook(secret)` and go.
155
159
  var DefaultJwtHook_1 = require("./DefaultJwtHook");
156
160
  Object.defineProperty(exports, "DefaultJwtHook", { enumerable: true, get: function () { return DefaultJwtHook_1.DefaultJwtHook; } });
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 } from './ApiRoutingFactory';\nexport type { ClassType } from './ClassType';\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, RouteModule } from './Wiring';\nexport type { BindModule } from './BindModule';\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\nexport { RuntimeStartupPhase, RuntimeStartupOperation } from './RuntimeStartupTiming';\nexport type { RuntimeStartupObserver, RuntimeStartupSpan } from './RuntimeStartupTiming';\n\nexport { LazyController, LazyImplementation, ApplicationImplementation, LazyFeatureTimer, LazyFeatureDeadline } from './LazyController';\n"]}
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,\n WpLocalOnly,\n isLocalOnly,\n jwt,\n oidcSameProject,\n oidcSvcs,\n sharedSecret,\n vendorAuth,\n apiKey,\n WpAuthorization,\n AuthorizationType,\n 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 } from './ApiRoutingFactory';\nexport type { ClassType } from './ClassType';\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, RouteModule } from './Wiring';\nexport type { BindModule } from './BindModule';\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// The same verifiers AuthFilter holds, for a door that is not a route (the MCP HTTP endpoint).\nexport { CredentialVerifiers, SharedSecretComparison } from './CredentialVerifiers';\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\nexport {\n AuthorizationHook,\n AuthorizationService,\n CanonicalUserRoles,\n AUTHORIZATION_HOOK,\n VERIFIED_MACHINE_CALLER,\n VerifiedMachineCaller,\n} from './AuthorizationHook';\nexport { AuthorizedApiDocument } from './AuthorizedApiDocument';\nexport type { ApiDocumentObject, ApiDocumentValue } from './AuthorizedApiDocument';\n\nexport { WiringPolicy } from '@webpieces/http-client-core';\n\nexport { RuntimeStartupPhase, RuntimeStartupOperation } from './RuntimeStartupTiming';\nexport type { RuntimeStartupObserver, RuntimeStartupSpan } from './RuntimeStartupTiming';\n\nexport {\n LazyController,\n LazyImplementation,\n ApplicationImplementation,\n LazyFeatureTimer,\n LazyFeatureDeadline,\n} from './LazyController';\n"]}