@webpieces/http-routing 0.3.304 → 0.3.306
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 +3 -3
- package/src/AuthConfig.d.ts +37 -19
- package/src/AuthConfig.js +41 -19
- package/src/AuthConfig.js.map +1 -1
- package/src/WebpiecesRouter.d.ts +6 -7
- package/src/WebpiecesRouter.js +4 -6
- package/src/WebpiecesRouter.js.map +1 -1
- package/src/filters/AuthFilter.d.ts +16 -7
- package/src/filters/AuthFilter.js +50 -16
- package/src/filters/AuthFilter.js.map +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +10 -4
- package/src/index.js.map +1 -1
- package/src/setupRuntime.d.ts +59 -0
- package/src/setupRuntime.js +78 -0
- package/src/setupRuntime.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/http-routing",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.306",
|
|
4
4
|
"description": "Decorator-based routing with auto-wiring for WebPieces",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@inversifyjs/binding-decorators": "1.1.5",
|
|
25
|
-
"@webpieces/core-context": "0.3.
|
|
26
|
-
"@webpieces/core-util": "0.3.
|
|
25
|
+
"@webpieces/core-context": "0.3.306",
|
|
26
|
+
"@webpieces/core-util": "0.3.306",
|
|
27
27
|
"inversify": "7.10.4",
|
|
28
28
|
"minimatch": "10.0.1"
|
|
29
29
|
}
|
package/src/AuthConfig.d.ts
CHANGED
|
@@ -1,30 +1,48 @@
|
|
|
1
|
+
import { ContextKey } from '@webpieces/core-util';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Data-only structure (a class, per the
|
|
3
|
+
* ContextValue - one (ContextKey, value) pair the JWT parse plugin wants stamped into the
|
|
4
|
+
* RequestContext (e.g. USER_ID, ORG_ID). Data-only structure (a class, per the guidelines).
|
|
4
5
|
*/
|
|
5
|
-
export declare class
|
|
6
|
+
export declare class ContextValue {
|
|
7
|
+
readonly key: ContextKey;
|
|
8
|
+
readonly value: unknown;
|
|
9
|
+
constructor(key: ContextKey, value: unknown);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* AuthValues - what {@link AuthConfig.parseJwt} returns: the authenticated user's id + roles (used
|
|
13
|
+
* by the framework to stamp a principal and enforce @AuthJwt(...roles)) plus any extra context
|
|
14
|
+
* entries the app wants set (orgId, tenant, ...). The framework sets `entries` into RequestContext
|
|
15
|
+
* via {@link RequestContext.putHeader}. Data-only structure (a class, per the guidelines).
|
|
16
|
+
*/
|
|
17
|
+
export declare class AuthValues {
|
|
6
18
|
readonly userId: string;
|
|
7
|
-
readonly
|
|
8
|
-
|
|
19
|
+
readonly roles: string[];
|
|
20
|
+
readonly entries: ContextValue[];
|
|
21
|
+
constructor(userId: string, roles?: string[], entries?: ContextValue[]);
|
|
9
22
|
}
|
|
10
23
|
/**
|
|
11
|
-
* AuthConfig - the app-provided
|
|
12
|
-
* each endpoint's AuthMode. It is
|
|
13
|
-
*
|
|
24
|
+
* AuthConfig - the ONE app-provided auth binding the framework {@link AuthFilter} injects to enforce
|
|
25
|
+
* each endpoint's AuthMode. It is a single abstract class (injected by type, per no-symbol-di-tokens)
|
|
26
|
+
* bound in the APP container and rebindable in tests. Each mechanism has its RIGHT shape:
|
|
14
27
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
28
|
+
* - `sharedSecrets` — STATE: the expected secret VALUE per name (from `@AuthSharedSecret(name)`).
|
|
29
|
+
* Prod fills it from env; a test binds `{ NAME: 'some-test-key' }` and can then
|
|
30
|
+
* exercise the shared-secret path (and a negative test with a wrong key).
|
|
31
|
+
* - `parseJwt` — PLUGIN: decode/verify a user JWT into {@link AuthValues} (userId, roles,
|
|
32
|
+
* context entries). Minting a JWT is a controller concern (login), not here.
|
|
33
|
+
* - `verifyOidc` — PLUGIN: verify a Google OIDC service-to-service token against the endpoint's
|
|
34
|
+
* caller allow-list. Fully generic — the company base wires it to
|
|
35
|
+
* @webpieces/gcp-identity once, so apps never customize OIDC.
|
|
19
36
|
*
|
|
20
|
-
*
|
|
21
|
-
* with no AuthConfig
|
|
37
|
+
* Keeping the plugins app-side means http-routing needs NO jsonwebtoken / gcp-identity. AuthFilter
|
|
38
|
+
* injects this `@optional`: a public-only server binds none; a non-public route with no AuthConfig
|
|
39
|
+
* (or no value/plugin for its mode) fails fast.
|
|
22
40
|
*/
|
|
23
41
|
export declare abstract class AuthConfig {
|
|
24
|
-
/**
|
|
25
|
-
abstract
|
|
26
|
-
/**
|
|
42
|
+
/** Expected shared-secret values keyed by the `@AuthSharedSecret(name)` name. STATE. */
|
|
43
|
+
abstract readonly sharedSecrets: Record<string, string>;
|
|
44
|
+
/** Parse a user JWT (kind:'jwt'); return the auth values or throw HttpUnauthorizedError. */
|
|
45
|
+
abstract parseJwt(token: string): AuthValues;
|
|
46
|
+
/** Verify a Google OIDC token from an allowed caller (kind:'oidc'); throw on failure. */
|
|
27
47
|
abstract verifyOidc(token: string, callers: string[]): Promise<void>;
|
|
28
|
-
/** The expected shared secret for the given env var name (kind:'shared-secret'). */
|
|
29
|
-
abstract sharedSecret(secretEnv: string): string | undefined;
|
|
30
48
|
}
|
package/src/AuthConfig.js
CHANGED
|
@@ -1,33 +1,55 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AuthConfig = exports.
|
|
3
|
+
exports.AuthConfig = exports.AuthValues = exports.ContextValue = void 0;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* Data-only structure (a class, per the
|
|
5
|
+
* ContextValue - one (ContextKey, value) pair the JWT parse plugin wants stamped into the
|
|
6
|
+
* RequestContext (e.g. USER_ID, ORG_ID). Data-only structure (a class, per the guidelines).
|
|
7
7
|
*/
|
|
8
|
-
class
|
|
8
|
+
class ContextValue {
|
|
9
|
+
key;
|
|
10
|
+
value;
|
|
11
|
+
constructor(key,
|
|
12
|
+
// webpieces-disable no-any-unknown -- context values are arbitrary app-defined data
|
|
13
|
+
value) {
|
|
14
|
+
this.key = key;
|
|
15
|
+
this.value = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.ContextValue = ContextValue;
|
|
19
|
+
/**
|
|
20
|
+
* AuthValues - what {@link AuthConfig.parseJwt} returns: the authenticated user's id + roles (used
|
|
21
|
+
* by the framework to stamp a principal and enforce @AuthJwt(...roles)) plus any extra context
|
|
22
|
+
* entries the app wants set (orgId, tenant, ...). The framework sets `entries` into RequestContext
|
|
23
|
+
* via {@link RequestContext.putHeader}. Data-only structure (a class, per the guidelines).
|
|
24
|
+
*/
|
|
25
|
+
class AuthValues {
|
|
9
26
|
userId;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
claims = {}) {
|
|
27
|
+
roles;
|
|
28
|
+
entries;
|
|
29
|
+
constructor(userId, roles = [], entries = []) {
|
|
14
30
|
this.userId = userId;
|
|
15
|
-
this.
|
|
31
|
+
this.roles = roles;
|
|
32
|
+
this.entries = entries;
|
|
16
33
|
}
|
|
17
34
|
}
|
|
18
|
-
exports.
|
|
35
|
+
exports.AuthValues = AuthValues;
|
|
19
36
|
/**
|
|
20
|
-
* AuthConfig - the app-provided
|
|
21
|
-
* each endpoint's AuthMode. It is
|
|
22
|
-
*
|
|
37
|
+
* AuthConfig - the ONE app-provided auth binding the framework {@link AuthFilter} injects to enforce
|
|
38
|
+
* each endpoint's AuthMode. It is a single abstract class (injected by type, per no-symbol-di-tokens)
|
|
39
|
+
* bound in the APP container and rebindable in tests. Each mechanism has its RIGHT shape:
|
|
23
40
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
41
|
+
* - `sharedSecrets` — STATE: the expected secret VALUE per name (from `@AuthSharedSecret(name)`).
|
|
42
|
+
* Prod fills it from env; a test binds `{ NAME: 'some-test-key' }` and can then
|
|
43
|
+
* exercise the shared-secret path (and a negative test with a wrong key).
|
|
44
|
+
* - `parseJwt` — PLUGIN: decode/verify a user JWT into {@link AuthValues} (userId, roles,
|
|
45
|
+
* context entries). Minting a JWT is a controller concern (login), not here.
|
|
46
|
+
* - `verifyOidc` — PLUGIN: verify a Google OIDC service-to-service token against the endpoint's
|
|
47
|
+
* caller allow-list. Fully generic — the company base wires it to
|
|
48
|
+
* @webpieces/gcp-identity once, so apps never customize OIDC.
|
|
28
49
|
*
|
|
29
|
-
*
|
|
30
|
-
* with no AuthConfig
|
|
50
|
+
* Keeping the plugins app-side means http-routing needs NO jsonwebtoken / gcp-identity. AuthFilter
|
|
51
|
+
* injects this `@optional`: a public-only server binds none; a non-public route with no AuthConfig
|
|
52
|
+
* (or no value/plugin for its mode) fails fast.
|
|
31
53
|
*/
|
|
32
54
|
class AuthConfig {
|
|
33
55
|
}
|
package/src/AuthConfig.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthConfig.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthConfig.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"AuthConfig.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/AuthConfig.ts"],"names":[],"mappings":";;;AAEA;;;GAGG;AACH,MAAa,YAAY;IAED;IAEA;IAHpB,YACoB,GAAe;IAC/B,oFAAoF;IACpE,KAAc;QAFd,QAAG,GAAH,GAAG,CAAY;QAEf,UAAK,GAAL,KAAK,CAAS;IAC/B,CAAC;CACP;AAND,oCAMC;AAED;;;;;GAKG;AACH,MAAa,UAAU;IAEC;IACA;IACA;IAHpB,YACoB,MAAc,EACd,QAAkB,EAAE,EACpB,UAA0B,EAAE;QAF5B,WAAM,GAAN,MAAM,CAAQ;QACd,UAAK,GAAL,KAAK,CAAe;QACpB,YAAO,GAAP,OAAO,CAAqB;IAC7C,CAAC;CACP;AAND,gCAMC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAsB,UAAU;CAS/B;AATD,gCASC","sourcesContent":["import { ContextKey } from '@webpieces/core-util';\n\n/**\n * ContextValue - one (ContextKey, value) pair the JWT parse plugin wants stamped into the\n * RequestContext (e.g. USER_ID, ORG_ID). Data-only structure (a class, per the guidelines).\n */\nexport class ContextValue {\n constructor(\n public readonly key: ContextKey,\n // webpieces-disable no-any-unknown -- context values are arbitrary app-defined data\n public readonly value: unknown,\n ) {}\n}\n\n/**\n * AuthValues - what {@link AuthConfig.parseJwt} returns: the authenticated user's id + roles (used\n * by the framework to stamp a principal and enforce @AuthJwt(...roles)) plus any extra context\n * entries the app wants set (orgId, tenant, ...). The framework sets `entries` into RequestContext\n * via {@link RequestContext.putHeader}. Data-only structure (a class, per the guidelines).\n */\nexport class AuthValues {\n constructor(\n public readonly userId: string,\n public readonly roles: string[] = [],\n public readonly entries: ContextValue[] = [],\n ) {}\n}\n\n/**\n * AuthConfig - the ONE app-provided auth binding the framework {@link AuthFilter} injects to enforce\n * each endpoint's AuthMode. It is a single abstract class (injected by type, per no-symbol-di-tokens)\n * bound in the APP container and rebindable in tests. Each mechanism has its RIGHT shape:\n *\n * - `sharedSecrets` — STATE: the expected secret VALUE per name (from `@AuthSharedSecret(name)`).\n * Prod fills it from env; a test binds `{ NAME: 'some-test-key' }` and can then\n * exercise the shared-secret path (and a negative test with a wrong key).\n * - `parseJwt` — PLUGIN: decode/verify a user JWT into {@link AuthValues} (userId, roles,\n * context entries). Minting a JWT is a controller concern (login), not here.\n * - `verifyOidc` — PLUGIN: verify a Google OIDC service-to-service token against the endpoint's\n * caller allow-list. Fully generic — the company base wires it to\n * @webpieces/gcp-identity once, so apps never customize OIDC.\n *\n * Keeping the plugins app-side means http-routing needs NO jsonwebtoken / gcp-identity. AuthFilter\n * injects this `@optional`: a public-only server binds none; a non-public route with no AuthConfig\n * (or no value/plugin for its mode) fails fast.\n */\nexport abstract class AuthConfig {\n /** Expected shared-secret values keyed by the `@AuthSharedSecret(name)` name. STATE. */\n abstract readonly sharedSecrets: Record<string, string>;\n\n /** Parse a user JWT (kind:'jwt'); return the auth values or throw HttpUnauthorizedError. */\n abstract parseJwt(token: string): AuthValues;\n\n /** Verify a Google OIDC token from an allowed caller (kind:'oidc'); throw on failure. */\n abstract verifyOidc(token: string, callers: string[]): Promise<void>;\n}\n"]}
|
package/src/WebpiecesRouter.d.ts
CHANGED
|
@@ -7,17 +7,18 @@ import { ApiClientFactory } from './ApiClientFactory';
|
|
|
7
7
|
import { ApiFactory } from './ApiFactory';
|
|
8
8
|
import { ApiClient } from './ApiClient';
|
|
9
9
|
/**
|
|
10
|
-
* Options for {@link WebpiecesRouterFactory.create}.
|
|
10
|
+
* Options for {@link WebpiecesRouterFactory.create} — one object (config lives inside it).
|
|
11
11
|
*
|
|
12
|
-
* appBindings -
|
|
13
|
-
* [WebpiecesModule, CompanyHeadersModule, AppModule]. Loaded after the
|
|
12
|
+
* appBindings - DI ContainerModules to load (framework + app). Loaded after the
|
|
14
13
|
* @provideSingleton auto-scan so they can add/override bindings.
|
|
15
14
|
* appOverrides - A single ContainerModule loaded LAST so tests can rebind real
|
|
16
15
|
* controllers/clients to mocks (see @webpieces/core-mock createMock()).
|
|
16
|
+
* config - Optional {@link WebpiecesConfig} (recording flags, etc.); defaults to a fresh one.
|
|
17
17
|
*/
|
|
18
18
|
export interface WebpiecesRouterOptions {
|
|
19
19
|
appBindings: ContainerModule[];
|
|
20
20
|
appOverrides?: ContainerModule;
|
|
21
|
+
config?: WebpiecesConfig;
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* WebpiecesRouter - the node-only heart of a webpieces app: a DI container + a filter
|
|
@@ -34,9 +35,7 @@ export interface WebpiecesRouterOptions {
|
|
|
34
35
|
*
|
|
35
36
|
* Usage:
|
|
36
37
|
* ```typescript
|
|
37
|
-
* const router = await WebpiecesRouterFactory.create(
|
|
38
|
-
* appBindings: [WebpiecesModule, CompanyHeadersModule],
|
|
39
|
-
* });
|
|
38
|
+
* const router = await WebpiecesRouterFactory.create({ appBindings: [AppModule] });
|
|
40
39
|
* router.addRoutes(SaveApi, SaveController);
|
|
41
40
|
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters
|
|
42
41
|
* // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)
|
|
@@ -101,5 +100,5 @@ export declare class WebpiecesRouter implements ApiFactory {
|
|
|
101
100
|
* container with the @provideSingleton auto-scan + appBindings + optional test overrides.
|
|
102
101
|
*/
|
|
103
102
|
export declare class WebpiecesRouterFactory {
|
|
104
|
-
static create(
|
|
103
|
+
static create(options: WebpiecesRouterOptions): Promise<WebpiecesRouter>;
|
|
105
104
|
}
|
package/src/WebpiecesRouter.js
CHANGED
|
@@ -28,9 +28,7 @@ const AuthFilter_1 = require("./filters/AuthFilter");
|
|
|
28
28
|
*
|
|
29
29
|
* Usage:
|
|
30
30
|
* ```typescript
|
|
31
|
-
* const router = await WebpiecesRouterFactory.create(
|
|
32
|
-
* appBindings: [WebpiecesModule, CompanyHeadersModule],
|
|
33
|
-
* });
|
|
31
|
+
* const router = await WebpiecesRouterFactory.create({ appBindings: [AppModule] });
|
|
34
32
|
* router.addRoutes(SaveApi, SaveController);
|
|
35
33
|
* router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters
|
|
36
34
|
* // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)
|
|
@@ -83,7 +81,7 @@ let WebpiecesRouter = class WebpiecesRouter {
|
|
|
83
81
|
// buildProviderModule() only ever contains the client's classes — never framework internals.
|
|
84
82
|
await this.appContainer.load((0, core_context_1.buildFrameworkModule)());
|
|
85
83
|
await this.appContainer.load((0, binding_decorators_1.buildProviderModule)());
|
|
86
|
-
// Load all modules into application container
|
|
84
|
+
// Load all app modules into application container
|
|
87
85
|
// (webpiecesContainer is currently empty, reserved for future framework bindings)
|
|
88
86
|
for (const module of options.appBindings) {
|
|
89
87
|
await this.appContainer.load(module);
|
|
@@ -145,12 +143,12 @@ exports.WebpiecesRouter = WebpiecesRouter = tslib_1.__decorate([
|
|
|
145
143
|
* container with the @provideSingleton auto-scan + appBindings + optional test overrides.
|
|
146
144
|
*/
|
|
147
145
|
class WebpiecesRouterFactory {
|
|
148
|
-
static async create(
|
|
146
|
+
static async create(options) {
|
|
149
147
|
// Platform (framework) container — build via buildFrameworkModule so framework
|
|
150
148
|
// singletons (WebpiecesRouter, RouteBuilderImpl) come from the webpieces registry,
|
|
151
149
|
// NOT the client's global one.
|
|
152
150
|
const webpiecesContainer = new inversify_1.Container();
|
|
153
|
-
webpiecesContainer.bind(WebpiecesConfig_1.WEBPIECES_CONFIG_TOKEN).toConstantValue(config);
|
|
151
|
+
webpiecesContainer.bind(WebpiecesConfig_1.WEBPIECES_CONFIG_TOKEN).toConstantValue(options.config ?? new WebpiecesConfig_1.WebpiecesConfig());
|
|
154
152
|
await webpiecesContainer.load((0, core_context_1.buildFrameworkModule)());
|
|
155
153
|
// Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.
|
|
156
154
|
const router = webpiecesContainer.get(WebpiecesRouter);
|
|
@@ -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 { Container, ContainerModule, inject } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { provideFrameworkSingleton, buildFrameworkModule } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\nimport { FilterDefinition } from './WebAppMeta';\nimport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\nimport { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { ErrorLogFilter } from './filters/ErrorLogFilter';\nimport { AuthFilter } from './filters/AuthFilter';\n\n/**\n * Options for {@link WebpiecesRouterFactory.create}.\n *\n * appBindings - REQUIRED DI ContainerModules to load (framework + app), e.g.\n * [WebpiecesModule, CompanyHeadersModule, AppModule]. 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 */\nexport interface WebpiecesRouterOptions {\n appBindings: ContainerModule[];\n appOverrides?: ContainerModule;\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(new WebpiecesConfig(), {\n * appBindings: [WebpiecesModule, CompanyHeadersModule],\n * });\n * router.addRoutes(SaveApi, SaveController);\n * router.addFilter(new FilterDefinition(1800, LogApiFilter, '*')); // your own filters\n * // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)\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\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(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n\n // App container is a child so app bindings see framework bindings while staying separate.\n this.appContainer = new Container({ parent: webpiecesContainer });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n this.installFixedFilters();\n }\n\n /**\n * Auto-install the two fixed framework filters on every route (apps add only their own\n * filters below these): ErrorLogFilter outermost (log + let the transport 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.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, ErrorLogFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\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.appContainer.load(buildFrameworkModule());\n await this.appContainer.load(buildProviderModule());\n\n // Load all modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of options.appBindings) {\n await this.appContainer.load(module);\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n await this.appContainer.load(options.appOverrides);\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * The controller is resolved from the container at request time.\n */\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this {\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder);\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * ErrorLogFilter + 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 /**\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 /** 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(\n config: WebpiecesConfig,\n options: WebpiecesRouterOptions,\n ): 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 webpiecesContainer = new Container();\n webpiecesContainer.bind(WEBPIECES_CONFIG_TOKEN).toConstantValue(config);\n await webpiecesContainer.load(buildFrameworkModule());\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = webpiecesContainer.get(WebpiecesRouter);\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 { Container, ContainerModule, inject } from 'inversify';\nimport { buildProviderModule } from '@inversifyjs/binding-decorators';\nimport { DocumentDesign } from '@webpieces/core-util';\nimport { provideFrameworkSingleton, buildFrameworkModule } from '@webpieces/core-context';\nimport { RouteBuilderImpl } from './RouteBuilderImpl';\nimport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\nimport { FilterDefinition } from './WebAppMeta';\nimport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\nimport { ApiClientFactory } from './ApiClientFactory';\nimport { ApiFactory } from './ApiFactory';\nimport { ApiClient } from './ApiClient';\nimport { ErrorLogFilter } from './filters/ErrorLogFilter';\nimport { AuthFilter } from './filters/AuthFilter';\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}\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, LogApiFilter, '*')); // your own filters\n * // (ErrorLogFilter + AuthFilter are auto-installed above yours; auth is AuthMode-driven)\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\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(webpiecesContainer: Container, options: WebpiecesRouterOptions): Promise<void> {\n this.webpiecesContainer = webpiecesContainer;\n\n // App container is a child so app bindings see framework bindings while staying separate.\n this.appContainer = new Container({ parent: webpiecesContainer });\n this.routeBuilder.setContainer(this.appContainer);\n\n await this.loadDIModules(options);\n this.installFixedFilters();\n }\n\n /**\n * Auto-install the two fixed framework filters on every route (apps add only their own\n * filters below these): ErrorLogFilter outermost (log + let the transport 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.\n */\n private installFixedFilters(): void {\n this.addFilter(new FilterDefinition(1_000_000, ErrorLogFilter, '*'));\n this.addFilter(new FilterDefinition(900_000, AuthFilter, '*'));\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.appContainer.load(buildFrameworkModule());\n await this.appContainer.load(buildProviderModule());\n\n // Load all app modules into application container\n // (webpiecesContainer is currently empty, reserved for future framework bindings)\n for (const module of options.appBindings) {\n await this.appContainer.load(module);\n }\n\n // Load appOverrides LAST so they can override existing bindings\n if (options.appOverrides) {\n await this.appContainer.load(options.appOverrides);\n }\n }\n\n /**\n * Wire an API prototype (with @ApiPath/@Endpoint decorators) to its controller.\n * The controller is resolved from the container at request time.\n */\n addRoutes<TApi, TController extends TApi>(\n api: ClassType<TApi>,\n controller: ClassType<TController>,\n ): this {\n new ApiRoutingFactory(api, controller).configure(this.routeBuilder);\n return this;\n }\n\n /**\n * Register a user filter (runs in-process AND over HTTP, below the auto-installed fixed\n * ErrorLogFilter + 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 /**\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 /** 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 webpiecesContainer = new Container();\n webpiecesContainer.bind(WEBPIECES_CONFIG_TOKEN).toConstantValue(options.config ?? new WebpiecesConfig());\n await webpiecesContainer.load(buildFrameworkModule());\n\n // Resolve the router from the container (NOT new'd) so @DocumentDesign + DI hold.\n const router = webpiecesContainer.get(WebpiecesRouter);\n await router.initialize(webpiecesContainer, options);\n return router;\n }\n}\n"]}
|
|
@@ -2,14 +2,19 @@ import { Filter, WpResponse, Service } from '../Filter';
|
|
|
2
2
|
import { MethodMeta } from '../MethodMeta';
|
|
3
3
|
import { AuthConfig } from '../AuthConfig';
|
|
4
4
|
/**
|
|
5
|
-
* AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on every
|
|
6
|
+
* route. It is TRANSPORT-NEUTRAL: it reads the raw credential from the {@link HttpRequest} in
|
|
7
|
+
* RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.
|
|
8
8
|
*
|
|
9
|
-
* It enforces the endpoint's AuthMode
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* It enforces the endpoint's AuthMode using the injected app-bound {@link AuthConfig}:
|
|
10
|
+
* - shared-secret → constant-time compare vs the bound secret VALUE (state).
|
|
11
|
+
* - jwt → `parseJwt` → stamp the user's context values + enforce @AuthJwt(...roles).
|
|
12
|
+
* - oidc → `verifyOidc` (delegates to gcp-identity in the company layer).
|
|
13
|
+
* - public → BEST-EFFORT jwt parse: if a token is present, stamp the user's context so a
|
|
14
|
+
* logged-out page still knows who is logged in; never fails.
|
|
15
|
+
*
|
|
16
|
+
* The verifiers/secrets are app-provided (rebindable in tests), so http-routing needs no
|
|
17
|
+
* jsonwebtoken / gcp-identity.
|
|
13
18
|
*/
|
|
14
19
|
export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {
|
|
15
20
|
private readonly authConfig?;
|
|
@@ -19,6 +24,10 @@ export declare class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>>
|
|
|
19
24
|
private enforceJwt;
|
|
20
25
|
private enforceOidc;
|
|
21
26
|
private enforceSharedSecret;
|
|
27
|
+
/** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */
|
|
28
|
+
private bestEffortJwt;
|
|
29
|
+
/** Stamp the parsed user's context entries + the principal into the RequestContext. */
|
|
30
|
+
private applyAuthValues;
|
|
22
31
|
private stripBearer;
|
|
23
32
|
private constantTimeEquals;
|
|
24
33
|
}
|
|
@@ -8,17 +8,23 @@ const core_context_1 = require("@webpieces/core-context");
|
|
|
8
8
|
const core_util_1 = require("@webpieces/core-util");
|
|
9
9
|
const Filter_1 = require("../Filter");
|
|
10
10
|
const AuthConfig_1 = require("../AuthConfig");
|
|
11
|
-
|
|
11
|
+
const log = core_util_1.LogManager.getLogger('AuthFilter');
|
|
12
|
+
/** Reserved context key holding the authenticated {@link AuthValues} (stamped after a jwt parse). */
|
|
12
13
|
const PRINCIPAL_KEY = '__webpieces_principal__';
|
|
13
14
|
/**
|
|
14
|
-
* AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on every
|
|
16
|
+
* route. It is TRANSPORT-NEUTRAL: it reads the raw credential from the {@link HttpRequest} in
|
|
17
|
+
* RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.
|
|
17
18
|
*
|
|
18
|
-
* It enforces the endpoint's AuthMode
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* It enforces the endpoint's AuthMode using the injected app-bound {@link AuthConfig}:
|
|
20
|
+
* - shared-secret → constant-time compare vs the bound secret VALUE (state).
|
|
21
|
+
* - jwt → `parseJwt` → stamp the user's context values + enforce @AuthJwt(...roles).
|
|
22
|
+
* - oidc → `verifyOidc` (delegates to gcp-identity in the company layer).
|
|
23
|
+
* - public → BEST-EFFORT jwt parse: if a token is present, stamp the user's context so a
|
|
24
|
+
* logged-out page still knows who is logged in; never fails.
|
|
25
|
+
*
|
|
26
|
+
* The verifiers/secrets are app-provided (rebindable in tests), so http-routing needs no
|
|
27
|
+
* jsonwebtoken / gcp-identity.
|
|
22
28
|
*/
|
|
23
29
|
let AuthFilter = class AuthFilter extends Filter_1.Filter {
|
|
24
30
|
authConfig;
|
|
@@ -29,19 +35,21 @@ let AuthFilter = class AuthFilter extends Filter_1.Filter {
|
|
|
29
35
|
// webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility
|
|
30
36
|
async filter(meta, nextFilter) {
|
|
31
37
|
const mode = meta.authMeta?.mode;
|
|
38
|
+
const authHeader = core_context_1.RequestContext.getRequest()?.getHeader(core_util_1.WebpiecesCoreHeaders.AUTHORIZATION);
|
|
32
39
|
if (!mode || mode.kind === 'public') {
|
|
40
|
+
// Public: best-effort parse so a logged-out page can still know the logged-in user.
|
|
41
|
+
this.bestEffortJwt(authHeader);
|
|
33
42
|
return nextFilter.invoke(meta);
|
|
34
43
|
}
|
|
35
|
-
const request = core_context_1.RequestContext.getRequest();
|
|
36
44
|
switch (mode.kind) {
|
|
37
45
|
case 'jwt':
|
|
38
|
-
this.enforceJwt(
|
|
46
|
+
this.enforceJwt(authHeader, mode.roles);
|
|
39
47
|
break;
|
|
40
48
|
case 'oidc':
|
|
41
|
-
await this.enforceOidc(
|
|
49
|
+
await this.enforceOidc(authHeader, mode.callers);
|
|
42
50
|
break;
|
|
43
51
|
case 'shared-secret':
|
|
44
|
-
this.enforceSharedSecret(
|
|
52
|
+
this.enforceSharedSecret(core_context_1.RequestContext.getRequest()?.getHeader(core_util_1.WebpiecesCoreHeaders.SHARED_SECRET), mode.secretEnv);
|
|
45
53
|
break;
|
|
46
54
|
}
|
|
47
55
|
return nextFilter.invoke(meta);
|
|
@@ -52,13 +60,17 @@ let AuthFilter = class AuthFilter extends Filter_1.Filter {
|
|
|
52
60
|
}
|
|
53
61
|
return this.authConfig;
|
|
54
62
|
}
|
|
55
|
-
enforceJwt(header) {
|
|
63
|
+
enforceJwt(header, roles) {
|
|
56
64
|
const token = this.stripBearer(header);
|
|
57
65
|
if (!token) {
|
|
58
66
|
throw new core_util_1.HttpUnauthorizedError('Authentication required');
|
|
59
67
|
}
|
|
60
|
-
const
|
|
61
|
-
|
|
68
|
+
const values = this.requireAuthConfig().parseJwt(token); // throws HttpUnauthorizedError if invalid
|
|
69
|
+
this.applyAuthValues(values);
|
|
70
|
+
// @AuthJwt(...roles): empty = any authenticated user; non-empty = must hold at least one.
|
|
71
|
+
if (roles.length > 0 && !roles.some((role) => values.roles.includes(role))) {
|
|
72
|
+
throw new core_util_1.HttpForbiddenError(`Endpoint requires one of roles: ${roles.join(', ')}`);
|
|
73
|
+
}
|
|
62
74
|
}
|
|
63
75
|
async enforceOidc(header, callers) {
|
|
64
76
|
const token = this.stripBearer(header);
|
|
@@ -68,11 +80,33 @@ let AuthFilter = class AuthFilter extends Filter_1.Filter {
|
|
|
68
80
|
await this.requireAuthConfig().verifyOidc(token, callers);
|
|
69
81
|
}
|
|
70
82
|
enforceSharedSecret(provided, secretEnv) {
|
|
71
|
-
const expected = this.requireAuthConfig().
|
|
83
|
+
const expected = this.requireAuthConfig().sharedSecrets[secretEnv];
|
|
72
84
|
if (!expected || !provided || !this.constantTimeEquals(provided, expected)) {
|
|
73
85
|
throw new core_util_1.HttpUnauthorizedError('Invalid shared secret for @AuthSharedSecret endpoint');
|
|
74
86
|
}
|
|
75
87
|
}
|
|
88
|
+
/** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */
|
|
89
|
+
bestEffortJwt(header) {
|
|
90
|
+
const token = this.stripBearer(header);
|
|
91
|
+
if (!this.authConfig || !token) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// 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
|
|
95
|
+
try {
|
|
96
|
+
this.applyAuthValues(this.authConfig.parseJwt(token));
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const error = (0, core_util_1.toError)(err);
|
|
100
|
+
log.debug('Best-effort JWT parse on a public endpoint failed (treating as anonymous): ', error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Stamp the parsed user's context entries + the principal into the RequestContext. */
|
|
104
|
+
applyAuthValues(values) {
|
|
105
|
+
for (const entry of values.entries) {
|
|
106
|
+
core_context_1.RequestContext.putHeader(entry.key, entry.value);
|
|
107
|
+
}
|
|
108
|
+
core_context_1.RequestContext.put(PRINCIPAL_KEY, values);
|
|
109
|
+
}
|
|
76
110
|
stripBearer(header) {
|
|
77
111
|
if (!header) {
|
|
78
112
|
return undefined;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuthFilter.js","sourceRoot":"","sources":["../../../../../../packages/http/http-routing/src/filters/AuthFilter.ts"],"names":[],"mappings":"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","sourcesContent":["import { inject, injectable, optional } from 'inversify';\nimport { timingSafeEqual } from 'crypto';\nimport { provideFrameworkSingleton, RequestContext } from '@webpieces/core-context';\nimport { WebpiecesCoreHeaders, HttpUnauthorizedError } from '@webpieces/core-util';\nimport { Filter, WpResponse, Service } from '../Filter';\nimport { MethodMeta } from '../MethodMeta';\nimport { AuthConfig } from '../AuthConfig';\n\n/** Reserved context key holding the authenticated Principal (stamped after a jwt verify). */\nconst PRINCIPAL_KEY = '__webpieces_principal__';\n\n/**\n * AuthFilter - the ONE framework auth filter, auto-installed just below the error filter on\n * every route. It is TRANSPORT-NEUTRAL: it reads the raw credential from the {@link HttpRequest}\n * in RequestContext (never express), so the SAME check runs over HTTP and via createApiClient.\n *\n * It enforces the endpoint's AuthMode (public/jwt/oidc/shared-secret) using the injected\n * {@link AuthConfig} — the concrete verifiers are app-provided and container-bound (rebindable\n * in tests), so http-routing needs no crypto / gcp-identity. Replaces the old app AuthFilter +\n * framework ServiceAuthFilter and removes the express/api filter tier.\n */\n@provideFrameworkSingleton()\n@injectable()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\nexport class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n constructor(\n // @optional: a public-only server need not bind an AuthConfig; a non-public route\n // then fails fast in requireAuthConfig().\n @optional() @inject(AuthConfig) private readonly authConfig?: AuthConfig,\n ) {\n super();\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\n override async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const mode = meta.authMeta?.mode;\n if (!mode || mode.kind === 'public') {\n return nextFilter.invoke(meta);\n }\n\n const request = RequestContext.getRequest();\n switch (mode.kind) {\n case 'jwt':\n this.enforceJwt(request?.getHeader(WebpiecesCoreHeaders.AUTHORIZATION));\n break;\n case 'oidc':\n await this.enforceOidc(request?.getHeader(WebpiecesCoreHeaders.AUTHORIZATION), mode.callers);\n break;\n case 'shared-secret':\n this.enforceSharedSecret(request?.getHeader(WebpiecesCoreHeaders.SHARED_SECRET), mode.secretEnv);\n break;\n }\n return nextFilter.invoke(meta);\n }\n\n private requireAuthConfig(): AuthConfig {\n if (!this.authConfig) {\n throw new HttpUnauthorizedError('No AuthConfig bound — cannot enforce a non-public endpoint');\n }\n return this.authConfig;\n }\n\n private enforceJwt(header: string | undefined): void {\n const token = this.stripBearer(header);\n if (!token) {\n throw new HttpUnauthorizedError('Authentication required');\n }\n const principal = this.requireAuthConfig().verifyJwt(token);\n RequestContext.put(PRINCIPAL_KEY, principal);\n }\n\n private async enforceOidc(header: string | undefined, callers: string[]): Promise<void> {\n const token = this.stripBearer(header);\n if (!token) {\n throw new HttpUnauthorizedError('Missing OIDC bearer token for @AuthOidc endpoint');\n }\n await this.requireAuthConfig().verifyOidc(token, callers);\n }\n\n private enforceSharedSecret(provided: string | undefined, secretEnv: string): void {\n const expected = this.requireAuthConfig().sharedSecret(secretEnv);\n if (!expected || !provided || !this.constantTimeEquals(provided, expected)) {\n throw new HttpUnauthorizedError('Invalid shared secret for @AuthSharedSecret endpoint');\n }\n }\n\n private stripBearer(header: string | undefined): string | undefined {\n if (!header) {\n return undefined;\n }\n const prefix = 'Bearer ';\n return header.startsWith(prefix) ? header.substring(prefix.length) : header;\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, injectable, optional } from 'inversify';\nimport { timingSafeEqual } from 'crypto';\nimport { provideFrameworkSingleton, RequestContext } from '@webpieces/core-context';\nimport { WebpiecesCoreHeaders, HttpUnauthorizedError, HttpForbiddenError, LogManager, toError } from '@webpieces/core-util';\nimport { Filter, WpResponse, Service } from '../Filter';\nimport { MethodMeta } from '../MethodMeta';\nimport { AuthConfig, AuthValues } from '../AuthConfig';\n\nconst log = LogManager.getLogger('AuthFilter');\n\n/** Reserved context key holding the authenticated {@link AuthValues} (stamped after a jwt parse). */\nconst PRINCIPAL_KEY = '__webpieces_principal__';\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 using the injected app-bound {@link AuthConfig}:\n * - shared-secret → constant-time compare vs the bound secret VALUE (state).\n * - jwt → `parseJwt` → stamp the user's context values + enforce @AuthJwt(...roles).\n * - oidc → `verifyOidc` (delegates to gcp-identity in the company layer).\n * - public → BEST-EFFORT jwt parse: if a token is present, stamp the user's context so a\n * logged-out page still knows who is logged in; never fails.\n *\n * The verifiers/secrets are app-provided (rebindable in tests), so http-routing needs no\n * jsonwebtoken / gcp-identity.\n */\n@provideFrameworkSingleton()\n@injectable()\n// webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\nexport class AuthFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n constructor(\n // @optional: a public-only server need not bind an AuthConfig; a non-public route then\n // fails fast in requireAuthConfig().\n @optional() @inject(AuthConfig) private readonly authConfig?: AuthConfig,\n ) {\n super();\n }\n\n // webpieces-disable no-any-unknown -- Filter generic params use unknown for response flexibility\n override async filter(\n meta: MethodMeta,\n nextFilter: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n const mode = meta.authMeta?.mode;\n const authHeader = RequestContext.getRequest()?.getHeader(WebpiecesCoreHeaders.AUTHORIZATION);\n\n if (!mode || mode.kind === 'public') {\n // Public: best-effort parse so a logged-out page can still know the logged-in user.\n this.bestEffortJwt(authHeader);\n return nextFilter.invoke(meta);\n }\n\n switch (mode.kind) {\n case 'jwt':\n this.enforceJwt(authHeader, mode.roles);\n break;\n case 'oidc':\n await this.enforceOidc(authHeader, mode.callers);\n break;\n case 'shared-secret':\n this.enforceSharedSecret(\n RequestContext.getRequest()?.getHeader(WebpiecesCoreHeaders.SHARED_SECRET),\n mode.secretEnv,\n );\n break;\n }\n return nextFilter.invoke(meta);\n }\n\n private requireAuthConfig(): AuthConfig {\n if (!this.authConfig) {\n throw new HttpUnauthorizedError('No AuthConfig bound — cannot enforce a non-public endpoint');\n }\n return this.authConfig;\n }\n\n private enforceJwt(header: string | undefined, roles: string[]): void {\n const token = this.stripBearer(header);\n if (!token) {\n throw new HttpUnauthorizedError('Authentication required');\n }\n const values = this.requireAuthConfig().parseJwt(token); // throws HttpUnauthorizedError if invalid\n this.applyAuthValues(values);\n // @AuthJwt(...roles): empty = any authenticated user; non-empty = must hold at least one.\n if (roles.length > 0 && !roles.some((role: string) => values.roles.includes(role))) {\n throw new HttpForbiddenError(`Endpoint requires one of roles: ${roles.join(', ')}`);\n }\n }\n\n private async enforceOidc(header: string | undefined, callers: string[]): Promise<void> {\n const token = this.stripBearer(header);\n if (!token) {\n throw new HttpUnauthorizedError('Missing OIDC bearer token for @AuthOidc endpoint');\n }\n await this.requireAuthConfig().verifyOidc(token, callers);\n }\n\n private enforceSharedSecret(provided: string | undefined, secretEnv: string): void {\n const expected = this.requireAuthConfig().sharedSecrets[secretEnv];\n if (!expected || !provided || !this.constantTimeEquals(provided, expected)) {\n throw new HttpUnauthorizedError('Invalid shared secret for @AuthSharedSecret endpoint');\n }\n }\n\n /** Parse a JWT if one is present, else do nothing — used on public routes; never throws. */\n private bestEffortJwt(header: string | undefined): void {\n const token = this.stripBearer(header);\n if (!this.authConfig || !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.applyAuthValues(this.authConfig.parseJwt(token));\n } catch (err: unknown) {\n const error = toError(err);\n log.debug('Best-effort JWT parse on a public endpoint failed (treating as anonymous): ', error);\n }\n }\n\n /** Stamp the parsed user's context entries + the principal into the RequestContext. */\n private applyAuthValues(values: AuthValues): void {\n for (const entry of values.entries) {\n RequestContext.putHeader(entry.key, entry.value);\n }\n RequestContext.put(PRINCIPAL_KEY, values);\n }\n\n private stripBearer(header: string | undefined): string | undefined {\n if (!header) {\n return undefined;\n }\n const prefix = 'Bearer ';\n return header.startsWith(prefix) ? header.substring(prefix.length) : header;\n }\n\n private constantTimeEquals(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) {\n return false;\n }\n return timingSafeEqual(bufA, bufB);\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -13,8 +13,9 @@ export { RouteHandler } from './RouteHandler';
|
|
|
13
13
|
export { FilterMatcher, HttpFilter } from './FilterMatcher';
|
|
14
14
|
export { ApiFactory } from './ApiFactory';
|
|
15
15
|
export { ApiClient, ApiClientProxy } from './ApiClient';
|
|
16
|
-
export { AuthConfig,
|
|
16
|
+
export { AuthConfig, AuthValues, ContextValue } from './AuthConfig';
|
|
17
17
|
export { fillContext } from './fillContext';
|
|
18
18
|
export { WebpiecesRouter, WebpiecesRouterFactory, WebpiecesRouterOptions } from './WebpiecesRouter';
|
|
19
|
+
export { setupRuntime, RuntimeSetupOptions } from './setupRuntime';
|
|
19
20
|
export { RequestContextReader } from '@webpieces/core-context';
|
|
20
21
|
export { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RequestContextReader = void 0;
|
|
3
|
+
exports.WebpiecesRouter = exports.fillContext = exports.ContextValue = exports.AuthValues = exports.AuthConfig = exports.ApiClient = exports.FilterMatcher = exports.RouteHandler = exports.MethodMeta = exports.FilterChain = exports.WpResponse = exports.Filter = exports.HttpRequest = exports.FilterDefinition = exports.RouteDefinition = exports.ApiRoutingFactory = exports.buildFrameworkModule = exports.provideFrameworkSingletonAs = exports.provideFrameworkSingleton = exports.provideTransient = exports.provideSingletonAs = exports.provideSingleton = exports.ROUTING_METADATA_KEYS = exports.SourceFile = exports.isDocumentDesign = exports.DocumentDesign = exports.METADATA_KEYS = exports.RouteMetadata = exports.AuthMeta = exports.getQueueName = exports.assertPubSubConventions = exports.assertApiKind = exports.getApiKind = exports.assertEveryEndpointHasAuthMode = exports.getAuthMode = exports.getAuthMeta = exports.isApiPath = exports.getEndpoints = exports.getApiPath = exports.Queue = exports.PubSub = exports.Rpc = exports.AuthSharedSecret = exports.AuthOidc = exports.AuthJwt = exports.Public = exports.AuthenticationConfig = exports.Authentication = exports.Endpoint = exports.ApiPath = void 0;
|
|
4
|
+
exports.WEBPIECES_CONFIG_TOKEN = exports.WebpiecesConfig = exports.RequestContextReader = exports.RuntimeSetupOptions = exports.setupRuntime = exports.WebpiecesRouterFactory = void 0;
|
|
5
5
|
// Re-export API decorators from core-util for convenience
|
|
6
6
|
var core_util_1 = require("@webpieces/core-util");
|
|
7
7
|
Object.defineProperty(exports, "ApiPath", { enumerable: true, get: function () { return core_util_1.ApiPath; } });
|
|
@@ -73,10 +73,11 @@ var FilterMatcher_1 = require("./FilterMatcher");
|
|
|
73
73
|
Object.defineProperty(exports, "FilterMatcher", { enumerable: true, get: function () { return FilterMatcher_1.FilterMatcher; } });
|
|
74
74
|
var ApiClient_1 = require("./ApiClient");
|
|
75
75
|
Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function () { return ApiClient_1.ApiClient; } });
|
|
76
|
-
// Auth: the app-provided, container-bound
|
|
76
|
+
// Auth: the app-provided, container-bound AuthConfig the framework AuthFilter injects.
|
|
77
77
|
var AuthConfig_1 = require("./AuthConfig");
|
|
78
78
|
Object.defineProperty(exports, "AuthConfig", { enumerable: true, get: function () { return AuthConfig_1.AuthConfig; } });
|
|
79
|
-
Object.defineProperty(exports, "
|
|
79
|
+
Object.defineProperty(exports, "AuthValues", { enumerable: true, get: function () { return AuthConfig_1.AuthValues; } });
|
|
80
|
+
Object.defineProperty(exports, "ContextValue", { enumerable: true, get: function () { return AuthConfig_1.ContextValue; } });
|
|
80
81
|
// Above-boundary context setup shared by every transport adapter.
|
|
81
82
|
var fillContext_1 = require("./fillContext");
|
|
82
83
|
Object.defineProperty(exports, "fillContext", { enumerable: true, get: function () { return fillContext_1.fillContext; } });
|
|
@@ -84,6 +85,11 @@ Object.defineProperty(exports, "fillContext", { enumerable: true, get: function
|
|
|
84
85
|
var WebpiecesRouter_1 = require("./WebpiecesRouter");
|
|
85
86
|
Object.defineProperty(exports, "WebpiecesRouter", { enumerable: true, get: function () { return WebpiecesRouter_1.WebpiecesRouter; } });
|
|
86
87
|
Object.defineProperty(exports, "WebpiecesRouterFactory", { enumerable: true, get: function () { return WebpiecesRouter_1.WebpiecesRouterFactory; } });
|
|
88
|
+
// The ONE transport-free startup sequence (headers → logging → router → routes) → ApiFactory.
|
|
89
|
+
// Reusable by any company/app and any framework adapter; a company wraps it with its own headers.
|
|
90
|
+
var setupRuntime_1 = require("./setupRuntime");
|
|
91
|
+
Object.defineProperty(exports, "setupRuntime", { enumerable: true, get: function () { return setupRuntime_1.setupRuntime; } });
|
|
92
|
+
Object.defineProperty(exports, "RuntimeSetupOptions", { enumerable: true, get: function () { return setupRuntime_1.RuntimeSetupOptions; } });
|
|
87
93
|
// Context readers (Node.js only) moved to core-context; re-exported for back-compat
|
|
88
94
|
var core_context_4 = require("@webpieces/core-context");
|
|
89
95
|
Object.defineProperty(exports, "RequestContextReader", { enumerable: true, get: function () { return core_context_4.RequestContextReader; } });
|
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":";;;;AAAA,0DAA0D;AAC1D,kDA8B8B;AA7B1B,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,2GAAA,cAAc,OAAA;AACd,iHAAA,oBAAoB,OAAA;AACpB,mGAAA,MAAM,OAAA;AACN,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,6GAAA,gBAAgB,OAAA;AAChB,gGAAA,GAAG,OAAA;AACH,mGAAA,MAAM,OAAA;AACN,kGAAA,KAAK,OAAA;AACL,uGAAA,UAAU,OAAA;AACV,yGAAA,YAAY,OAAA;AACZ,sGAAA,SAAS,OAAA;AACT,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AACX,2HAAA,8BAA8B,OAAA;AAC9B,uGAAA,UAAU,OAAA;AACV,0GAAA,aAAa,OAAA;AACb,oHAAA,uBAAuB,OAAA;AACvB,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,0GAAA,aAAa,OAAA;AACb,0GAAA,aAAa,OAAA;AAEb,2EAA2E;AAC3E,oCAAoC;AACpC,2GAAA,cAAc,OAAA;AACd,6GAAA,gBAAgB,OAAA;AAIpB,+CAA+C;AAC/C,2CAGsB;AAFlB,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAGzB,iFAAiF;AACjF,wDAAiG;AAAxF,gHAAA,gBAAgB,OAAA;AAAE,kHAAA,kBAAkB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAC/D,gGAAgG;AAChG,wDAIiC;AAH7B,yHAAA,yBAAyB,OAAA;AACzB,2HAAA,2BAA2B,OAAA;AAC3B,oHAAA,oBAAoB,OAAA;AAGxB,yDAAmE;AAA1D,sHAAA,iBAAiB,OAAA;AAE1B,qBAAqB;AACrB,2CAKsB;AAFlB,6GAAA,eAAe,OAAA;AACf,8GAAA,gBAAgB,OAAA;AAGpB,sFAAsF;AACtF,+FAA+F;AAC/F,wDAAsD;AAA7C,2GAAA,WAAW,OAAA;AAEpB,qFAAqF;AACrF,mCAAuD;AAA9C,gGAAA,MAAM,OAAA;AAAE,oGAAA,UAAU,OAAA;AAC3B,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,oFAAoF;AACpF,sFAAsF;AAEtF,kBAAkB;AAClB,iDAA4D;AAAnD,8GAAA,aAAa,OAAA;AAItB,yCAAwD;AAA/C,sGAAA,SAAS,OAAA;AAElB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/index.ts"],"names":[],"mappings":";;;;AAAA,0DAA0D;AAC1D,kDA8B8B;AA7B1B,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,2GAAA,cAAc,OAAA;AACd,iHAAA,oBAAoB,OAAA;AACpB,mGAAA,MAAM,OAAA;AACN,oGAAA,OAAO,OAAA;AACP,qGAAA,QAAQ,OAAA;AACR,6GAAA,gBAAgB,OAAA;AAChB,gGAAA,GAAG,OAAA;AACH,mGAAA,MAAM,OAAA;AACN,kGAAA,KAAK,OAAA;AACL,uGAAA,UAAU,OAAA;AACV,yGAAA,YAAY,OAAA;AACZ,sGAAA,SAAS,OAAA;AACT,wGAAA,WAAW,OAAA;AACX,wGAAA,WAAW,OAAA;AACX,2HAAA,8BAA8B,OAAA;AAC9B,uGAAA,UAAU,OAAA;AACV,0GAAA,aAAa,OAAA;AACb,oHAAA,uBAAuB,OAAA;AACvB,yGAAA,YAAY,OAAA;AACZ,qGAAA,QAAQ,OAAA;AACR,0GAAA,aAAa,OAAA;AACb,0GAAA,aAAa,OAAA;AAEb,2EAA2E;AAC3E,oCAAoC;AACpC,2GAAA,cAAc,OAAA;AACd,6GAAA,gBAAgB,OAAA;AAIpB,+CAA+C;AAC/C,2CAGsB;AAFlB,wGAAA,UAAU,OAAA;AACV,mHAAA,qBAAqB,OAAA;AAGzB,iFAAiF;AACjF,wDAAiG;AAAxF,gHAAA,gBAAgB,OAAA;AAAE,kHAAA,kBAAkB,OAAA;AAAE,gHAAA,gBAAgB,OAAA;AAC/D,gGAAgG;AAChG,wDAIiC;AAH7B,yHAAA,yBAAyB,OAAA;AACzB,2HAAA,2BAA2B,OAAA;AAC3B,oHAAA,oBAAoB,OAAA;AAGxB,yDAAmE;AAA1D,sHAAA,iBAAiB,OAAA;AAE1B,qBAAqB;AACrB,2CAKsB;AAFlB,6GAAA,eAAe,OAAA;AACf,8GAAA,gBAAgB,OAAA;AAGpB,sFAAsF;AACtF,+FAA+F;AAC/F,wDAAsD;AAA7C,2GAAA,WAAW,OAAA;AAEpB,qFAAqF;AACrF,mCAAuD;AAA9C,gGAAA,MAAM,OAAA;AAAE,oGAAA,UAAU,OAAA;AAC3B,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,2CAA0C;AAAjC,wGAAA,UAAU,OAAA;AACnB,+CAA8C;AAArC,4GAAA,YAAY,OAAA;AAErB,oFAAoF;AACpF,sFAAsF;AAEtF,kBAAkB;AAClB,iDAA4D;AAAnD,8GAAA,aAAa,OAAA;AAItB,yCAAwD;AAA/C,sGAAA,SAAS,OAAA;AAElB,uFAAuF;AACvF,2CAAoE;AAA3D,wGAAA,UAAU,OAAA;AAAE,wGAAA,UAAU,OAAA;AAAE,0GAAA,YAAY,OAAA;AAE7C,kEAAkE;AAClE,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AAEpB,0FAA0F;AAC1F,qDAAoG;AAA3F,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA;AAEhD,8FAA8F;AAC9F,kGAAkG;AAClG,+CAAmE;AAA1D,4GAAA,YAAY,OAAA;AAAE,mHAAA,mBAAmB,OAAA;AAE1C,oFAAoF;AACpF,wDAA+D;AAAtD,oHAAA,oBAAoB,OAAA;AAE7B,uBAAuB;AACvB,qDAA4E;AAAnE,kHAAA,eAAe,OAAA;AAAE,yHAAA,sBAAsB,OAAA","sourcesContent":["// Re-export API decorators from core-util for convenience\nexport {\n ApiPath,\n Endpoint,\n Authentication,\n AuthenticationConfig,\n Public,\n AuthJwt,\n AuthOidc,\n AuthSharedSecret,\n Rpc,\n PubSub,\n Queue,\n getApiPath,\n getEndpoints,\n isApiPath,\n getAuthMeta,\n getAuthMode,\n assertEveryEndpointHasAuthMode,\n getApiKind,\n assertApiKind,\n assertPubSubConventions,\n getQueueName,\n AuthMeta,\n RouteMetadata,\n METADATA_KEYS,\n ValidateImplementation,\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 { AuthMode, ApiKind } from '@webpieces/core-util';\n\n// Server-side routing decorators and utilities\nexport {\n SourceFile,\n ROUTING_METADATA_KEYS,\n} from './decorators';\n\n// DI provider decorators moved to core-context; re-exported here for back-compat\nexport { provideSingleton, provideSingletonAs, provideTransient } from '@webpieces/core-context';\n// Framework-only DI registry (packages/** framework classes use these; see frameworkProvide.ts)\nexport {\n provideFrameworkSingleton,\n provideFrameworkSingletonAs,\n buildFrameworkModule,\n} from '@webpieces/core-context';\n\nexport { ApiRoutingFactory, ClassType } from './ApiRoutingFactory';\n\n// Core routing types\nexport {\n Routes,\n RouteBuilder,\n RouteDefinition,\n FilterDefinition,\n} 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 } from '@webpieces/core-context';\n\n// Filter-chain primitives (absorbed from the former @webpieces/http-filters package)\nexport { Filter, WpResponse, Service } from './Filter';\nexport { FilterChain } from './FilterChain';\nexport { MethodMeta } from './MethodMeta';\nexport { RouteHandler } from './RouteHandler';\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 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 AuthConfig the framework AuthFilter injects.\nexport { AuthConfig, AuthValues, ContextValue } from './AuthConfig';\n\n// Above-boundary context setup shared by every transport adapter.\nexport { fillContext } from './fillContext';\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// Context readers (Node.js only) moved to core-context; re-exported for back-compat\nexport { RequestContextReader } from '@webpieces/core-context';\n\n// Server configuration\nexport { WebpiecesConfig, WEBPIECES_CONFIG_TOKEN } from './WebpiecesConfig';\n"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { ContainerModule } from 'inversify';
|
|
2
|
+
import { ContextKey, LoggerFactory } from '@webpieces/core-util';
|
|
3
|
+
import { WebpiecesConfig } from './WebpiecesConfig';
|
|
4
|
+
import { WebpiecesRouter } from './WebpiecesRouter';
|
|
5
|
+
import { ApiFactory } from './ApiFactory';
|
|
6
|
+
/**
|
|
7
|
+
* RuntimeSetupOptions - inputs to {@link setupRuntime}. Data-only structure (a class, per the
|
|
8
|
+
* webpieces guidelines). A company/app layer supplies its own header tiers + logger + modules;
|
|
9
|
+
* the framework runs the canonical startup sequence and hands back a transport-free ApiFactory.
|
|
10
|
+
*
|
|
11
|
+
* Header tiers mirror {@link HeaderRegistry.configure}: platform defaults + org/company keys +
|
|
12
|
+
* this-service keys.
|
|
13
|
+
*/
|
|
14
|
+
export declare class RuntimeSetupOptions {
|
|
15
|
+
/** Logging backend to install (LogManager.setFactory). */
|
|
16
|
+
readonly loggerFactory: LoggerFactory;
|
|
17
|
+
/** This service's own context keys. */
|
|
18
|
+
readonly svrHeaders: ContextKey[];
|
|
19
|
+
/** Org/company-wide shared context keys (the company layer passes these in). */
|
|
20
|
+
readonly companyHeaders: ContextKey[];
|
|
21
|
+
/** Include the webpieces platform default headers. */
|
|
22
|
+
readonly platformHeaders: boolean;
|
|
23
|
+
/** App DI ContainerModules to load. */
|
|
24
|
+
readonly modules: ContainerModule[];
|
|
25
|
+
/** A single DI module loaded LAST so tests can rebind bindings to mocks. */
|
|
26
|
+
readonly appOverrides?: ContainerModule | undefined;
|
|
27
|
+
/** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */
|
|
28
|
+
readonly config?: WebpiecesConfig | undefined;
|
|
29
|
+
constructor(
|
|
30
|
+
/** Logging backend to install (LogManager.setFactory). */
|
|
31
|
+
loggerFactory: LoggerFactory,
|
|
32
|
+
/** This service's own context keys. */
|
|
33
|
+
svrHeaders?: ContextKey[],
|
|
34
|
+
/** Org/company-wide shared context keys (the company layer passes these in). */
|
|
35
|
+
companyHeaders?: ContextKey[],
|
|
36
|
+
/** Include the webpieces platform default headers. */
|
|
37
|
+
platformHeaders?: boolean,
|
|
38
|
+
/** App DI ContainerModules to load. */
|
|
39
|
+
modules?: ContainerModule[],
|
|
40
|
+
/** A single DI module loaded LAST so tests can rebind bindings to mocks. */
|
|
41
|
+
appOverrides?: ContainerModule | undefined,
|
|
42
|
+
/** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */
|
|
43
|
+
config?: WebpiecesConfig | undefined);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* setupRuntime - the ONE canonical, TRANSPORT-FREE startup sequence, reusable by any company/app
|
|
47
|
+
* AND any framework adapter (express, fastify, a serverless handler, ...). It runs, in the correct
|
|
48
|
+
* fail-fast order:
|
|
49
|
+
*
|
|
50
|
+
* 1. HeaderRegistry.configure (filters read it at construction; logging masks off it)
|
|
51
|
+
* 2. LogManager.setFactory (fails fast unless the registry is configured first)
|
|
52
|
+
* 3. build the router + DI container
|
|
53
|
+
* 4. run the caller's `configureRoutes(router)` block (addRoutes/addFilter)
|
|
54
|
+
*
|
|
55
|
+
* and returns the built {@link ApiFactory} — `apiClients()` for a transport to bind, or
|
|
56
|
+
* `createApiClient()` for in-process tests. There is NO express (or any transport) here; a
|
|
57
|
+
* transport adapter (e.g. WebpiecesExpressRouter in @webpieces/http-server) serves the result.
|
|
58
|
+
*/
|
|
59
|
+
export declare function setupRuntime(options: RuntimeSetupOptions, configureRoutes: (router: WebpiecesRouter) => void): Promise<ApiFactory>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RuntimeSetupOptions = void 0;
|
|
4
|
+
exports.setupRuntime = setupRuntime;
|
|
5
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
6
|
+
const WebpiecesConfig_1 = require("./WebpiecesConfig");
|
|
7
|
+
const WebpiecesRouter_1 = require("./WebpiecesRouter");
|
|
8
|
+
/**
|
|
9
|
+
* RuntimeSetupOptions - inputs to {@link setupRuntime}. Data-only structure (a class, per the
|
|
10
|
+
* webpieces guidelines). A company/app layer supplies its own header tiers + logger + modules;
|
|
11
|
+
* the framework runs the canonical startup sequence and hands back a transport-free ApiFactory.
|
|
12
|
+
*
|
|
13
|
+
* Header tiers mirror {@link HeaderRegistry.configure}: platform defaults + org/company keys +
|
|
14
|
+
* this-service keys.
|
|
15
|
+
*/
|
|
16
|
+
class RuntimeSetupOptions {
|
|
17
|
+
loggerFactory;
|
|
18
|
+
svrHeaders;
|
|
19
|
+
companyHeaders;
|
|
20
|
+
platformHeaders;
|
|
21
|
+
modules;
|
|
22
|
+
appOverrides;
|
|
23
|
+
config;
|
|
24
|
+
constructor(
|
|
25
|
+
/** Logging backend to install (LogManager.setFactory). */
|
|
26
|
+
loggerFactory,
|
|
27
|
+
/** This service's own context keys. */
|
|
28
|
+
svrHeaders = [],
|
|
29
|
+
/** Org/company-wide shared context keys (the company layer passes these in). */
|
|
30
|
+
companyHeaders = [],
|
|
31
|
+
/** Include the webpieces platform default headers. */
|
|
32
|
+
platformHeaders = true,
|
|
33
|
+
/** App DI ContainerModules to load. */
|
|
34
|
+
modules = [],
|
|
35
|
+
/** A single DI module loaded LAST so tests can rebind bindings to mocks. */
|
|
36
|
+
appOverrides,
|
|
37
|
+
/** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */
|
|
38
|
+
config) {
|
|
39
|
+
this.loggerFactory = loggerFactory;
|
|
40
|
+
this.svrHeaders = svrHeaders;
|
|
41
|
+
this.companyHeaders = companyHeaders;
|
|
42
|
+
this.platformHeaders = platformHeaders;
|
|
43
|
+
this.modules = modules;
|
|
44
|
+
this.appOverrides = appOverrides;
|
|
45
|
+
this.config = config;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.RuntimeSetupOptions = RuntimeSetupOptions;
|
|
49
|
+
/**
|
|
50
|
+
* setupRuntime - the ONE canonical, TRANSPORT-FREE startup sequence, reusable by any company/app
|
|
51
|
+
* AND any framework adapter (express, fastify, a serverless handler, ...). It runs, in the correct
|
|
52
|
+
* fail-fast order:
|
|
53
|
+
*
|
|
54
|
+
* 1. HeaderRegistry.configure (filters read it at construction; logging masks off it)
|
|
55
|
+
* 2. LogManager.setFactory (fails fast unless the registry is configured first)
|
|
56
|
+
* 3. build the router + DI container
|
|
57
|
+
* 4. run the caller's `configureRoutes(router)` block (addRoutes/addFilter)
|
|
58
|
+
*
|
|
59
|
+
* and returns the built {@link ApiFactory} — `apiClients()` for a transport to bind, or
|
|
60
|
+
* `createApiClient()` for in-process tests. There is NO express (or any transport) here; a
|
|
61
|
+
* transport adapter (e.g. WebpiecesExpressRouter in @webpieces/http-server) serves the result.
|
|
62
|
+
*/
|
|
63
|
+
async function setupRuntime(options, configureRoutes) {
|
|
64
|
+
// 1. Register the global HeaderRegistry FIRST.
|
|
65
|
+
core_util_1.HeaderRegistry.configure(options.svrHeaders, options.companyHeaders, options.platformHeaders);
|
|
66
|
+
// 2. Install the logging backend ONCE, before anything else logs.
|
|
67
|
+
core_util_1.LogManager.setFactory(options.loggerFactory);
|
|
68
|
+
// 3. Build the node-only router + DI container.
|
|
69
|
+
const router = await WebpiecesRouter_1.WebpiecesRouterFactory.create({
|
|
70
|
+
appBindings: [...options.modules],
|
|
71
|
+
appOverrides: options.appOverrides,
|
|
72
|
+
config: options.config ?? new WebpiecesConfig_1.WebpiecesConfig(),
|
|
73
|
+
});
|
|
74
|
+
// 4. Let the caller declare its routes + filters, then hand back the consumer surface.
|
|
75
|
+
configureRoutes(router);
|
|
76
|
+
return router;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=setupRuntime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupRuntime.js","sourceRoot":"","sources":["../../../../../packages/http/http-routing/src/setupRuntime.ts"],"names":[],"mappings":";;;AA+CA,oCAoBC;AAlED,oDAA6F;AAC7F,uDAAoD;AACpD,uDAA4E;AAG5E;;;;;;;GAOG;AACH,MAAa,mBAAmB;IAGR;IAEA;IAEA;IAEA;IAEA;IAEA;IAEA;IAdpB;IACI,0DAA0D;IAC1C,aAA4B;IAC5C,uCAAuC;IACvB,aAA2B,EAAE;IAC7C,gFAAgF;IAChE,iBAA+B,EAAE;IACjD,sDAAsD;IACtC,kBAA2B,IAAI;IAC/C,uCAAuC;IACvB,UAA6B,EAAE;IAC/C,4EAA4E;IAC5D,YAA8B;IAC9C,gFAAgF;IAChE,MAAwB;QAZxB,kBAAa,GAAb,aAAa,CAAe;QAE5B,eAAU,GAAV,UAAU,CAAmB;QAE7B,mBAAc,GAAd,cAAc,CAAmB;QAEjC,oBAAe,GAAf,eAAe,CAAgB;QAE/B,YAAO,GAAP,OAAO,CAAwB;QAE/B,iBAAY,GAAZ,YAAY,CAAkB;QAE9B,WAAM,GAAN,MAAM,CAAkB;IACzC,CAAC;CACP;AAjBD,kDAiBC;AAED;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,YAAY,CAC9B,OAA4B,EAC5B,eAAkD;IAElD,+CAA+C;IAC/C,0BAAc,CAAC,SAAS,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,cAAc,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAE9F,kEAAkE;IAClE,sBAAU,CAAC,UAAU,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAE7C,gDAAgD;IAChD,MAAM,MAAM,GAAG,MAAM,wCAAsB,CAAC,MAAM,CAAC;QAC/C,WAAW,EAAE,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;QACjC,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI,iCAAe,EAAE;KAClD,CAAC,CAAC;IAEH,uFAAuF;IACvF,eAAe,CAAC,MAAM,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["import { ContainerModule } from 'inversify';\nimport { ContextKey, HeaderRegistry, LoggerFactory, LogManager } from '@webpieces/core-util';\nimport { WebpiecesConfig } from './WebpiecesConfig';\nimport { WebpiecesRouter, WebpiecesRouterFactory } from './WebpiecesRouter';\nimport { ApiFactory } from './ApiFactory';\n\n/**\n * RuntimeSetupOptions - inputs to {@link setupRuntime}. Data-only structure (a class, per the\n * webpieces guidelines). A company/app layer supplies its own header tiers + logger + modules;\n * the framework runs the canonical startup sequence and hands back a transport-free ApiFactory.\n *\n * Header tiers mirror {@link HeaderRegistry.configure}: platform defaults + org/company keys +\n * this-service keys.\n */\nexport class RuntimeSetupOptions {\n constructor(\n /** Logging backend to install (LogManager.setFactory). */\n public readonly loggerFactory: LoggerFactory,\n /** This service's own context keys. */\n public readonly svrHeaders: ContextKey[] = [],\n /** Org/company-wide shared context keys (the company layer passes these in). */\n public readonly companyHeaders: ContextKey[] = [],\n /** Include the webpieces platform default headers. */\n public readonly platformHeaders: boolean = true,\n /** App DI ContainerModules to load. */\n public readonly modules: ContainerModule[] = [],\n /** A single DI module loaded LAST so tests can rebind bindings to mocks. */\n public readonly appOverrides?: ContainerModule,\n /** Optional WebpiecesConfig (e.g. recording flags); defaults to a fresh one. */\n public readonly config?: WebpiecesConfig,\n ) {}\n}\n\n/**\n * setupRuntime - the ONE canonical, TRANSPORT-FREE startup sequence, reusable by any company/app\n * AND any framework adapter (express, fastify, a serverless handler, ...). It runs, in the correct\n * fail-fast order:\n *\n * 1. HeaderRegistry.configure (filters read it at construction; logging masks off it)\n * 2. LogManager.setFactory (fails fast unless the registry is configured first)\n * 3. build the router + DI container\n * 4. run the caller's `configureRoutes(router)` block (addRoutes/addFilter)\n *\n * and returns the built {@link ApiFactory} — `apiClients()` for a transport to bind, or\n * `createApiClient()` for in-process tests. There is NO express (or any transport) here; a\n * transport adapter (e.g. WebpiecesExpressRouter in @webpieces/http-server) serves the result.\n */\nexport async function setupRuntime(\n options: RuntimeSetupOptions,\n configureRoutes: (router: WebpiecesRouter) => void,\n): Promise<ApiFactory> {\n // 1. Register the global HeaderRegistry FIRST.\n HeaderRegistry.configure(options.svrHeaders, options.companyHeaders, options.platformHeaders);\n\n // 2. Install the logging backend ONCE, before anything else logs.\n LogManager.setFactory(options.loggerFactory);\n\n // 3. Build the node-only router + DI container.\n const router = await WebpiecesRouterFactory.create({\n appBindings: [...options.modules],\n appOverrides: options.appOverrides,\n config: options.config ?? new WebpiecesConfig(),\n });\n\n // 4. Let the caller declare its routes + filters, then hand back the consumer surface.\n configureRoutes(router);\n return router;\n}\n"]}
|