@webpieces/core-util 0.4.957 → 0.4.959
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/http/ApiCallInfo.d.ts +71 -11
- package/src/http/ApiCallInfo.js +81 -7
- package/src/http/ApiCallInfo.js.map +1 -1
- package/src/http/ContextMgr.js +12 -2
- package/src/http/ContextMgr.js.map +1 -1
- package/src/http/DeletedAuthCompileAssertions.js +3 -1
- package/src/http/DeletedAuthCompileAssertions.js.map +1 -1
- package/src/http/DestinationTrust.d.ts +33 -4
- package/src/http/DestinationTrust.js +54 -11
- package/src/http/DestinationTrust.js.map +1 -1
- package/src/http/EndpointCompileAssertions.js +12 -6
- package/src/http/EndpointCompileAssertions.js.map +1 -1
- package/src/http/ExternalContextKeys.d.ts +31 -0
- package/src/http/ExternalContextKeys.js +77 -0
- package/src/http/ExternalContextKeys.js.map +1 -0
- package/src/http/HttpContract.d.ts +45 -7
- package/src/http/HttpContract.js +84 -6
- package/src/http/HttpContract.js.map +1 -1
- package/src/http/HttpEndpointOptions.d.ts +22 -3
- package/src/http/HttpEndpointOptions.js +8 -1
- package/src/http/HttpEndpointOptions.js.map +1 -1
- package/src/http/LogApiCall.d.ts +4 -1
- package/src/http/LogApiCall.js +7 -5
- package/src/http/LogApiCall.js.map +1 -1
- package/src/http/QueryParams.d.ts +47 -0
- package/src/http/QueryParams.js +89 -0
- package/src/http/QueryParams.js.map +1 -0
- package/src/http/RouteMetadata.d.ts +16 -2
- package/src/http/RouteMetadata.js +19 -3
- package/src/http/RouteMetadata.js.map +1 -1
- package/src/http/RouteMetadataFactory.d.ts +25 -0
- package/src/http/RouteMetadataFactory.js +97 -12
- package/src/http/RouteMetadataFactory.js.map +1 -1
- package/src/http/WpAuthApiKeyCompileAssertions.js +2 -2
- package/src/http/WpAuthApiKeyCompileAssertions.js.map +1 -1
- package/src/http/api-kind.d.ts +3 -2
- package/src/http/api-kind.js +5 -4
- package/src/http/api-kind.js.map +1 -1
- package/src/http/auth-mode.d.ts +8 -2
- package/src/http/auth-mode.js +11 -5
- package/src/http/auth-mode.js.map +1 -1
- package/src/http/decorators.d.ts +39 -21
- package/src/http/decorators.js +69 -37
- package/src/http/decorators.js.map +1 -1
- package/src/http/external-caller.d.ts +12 -17
- package/src/http/external-caller.js +12 -17
- package/src/http/external-caller.js.map +1 -1
- package/src/http/http-parameter-decorators.d.ts +17 -3
- package/src/http/http-parameter-decorators.js +27 -6
- package/src/http/http-parameter-decorators.js.map +1 -1
- package/src/index.d.ts +11 -7
- package/src/index.js +30 -12
- package/src/index.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-mode.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/auth-mode.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AnyContextKey } from '../ContextKey';\n\n/**\n * WHERE one api-key credential rides.\n *\n * A header credential names the {@link AnyContextKey ContextKey} that carries it — NEVER a header-name\n * string. The key's `httpHeader` is the wire name, so the ONE definition of each header (the\n * ContextKey) is reused on every surface: `RequestContext` put/get, the client/server header\n * transfer, this declaration, and the published OpenAPI document. A string here was a second copy a\n * rename left stale (#1249). The key MUST declare an `httpHeader` — {@link AuthDeclaration} refuses\n * one that does not, and the OpenAPI generator reads the header name off the key's\n * `ContextKey.trusted(...)` / `ContextKey.untrusted(...)` definition.\n */\nexport type ApiKeyCredential =\n | { in: 'header'; key: AnyContextKey; description?: string }\n | { in: 'bearer'; key?: never; description?: string };\n\nexport type ApiKeyCredentials = readonly [ApiKeyCredential, ...ApiKeyCredential[]];\n\n/**\n * The service accounts an `oidcSvcs([...])` endpoint names, at least one: an empty list is not a\n * policy anybody could mean, so it does not compile. Each entry resolves as `'self'` (this service's\n * runtime SA), a bare id (`<id>@<project>.iam.gserviceaccount.com`) or a full email (verbatim).\n */\nexport type OidcCallers = readonly [string, ...string[]];\n\n/**\n * WHICH Google-signed callers an OIDC endpoint accepts, named by `callerScope`:\n * - `'same-project'` (`oidcSameProject()`): any service account of THIS GCP project. Off GCP there is\n * no project, so the check is open.\n * - `'services'` (`oidcSvcs([...])`): exactly the listed service accounts, which may belong to other\n * projects.\n * One endpoint may declare one of each; see {@link OidcAuthMethods}.\n */\nexport type OidcAuthMethod =\n | { kind: 'oidc'; callerScope: 'same-project' }\n | { kind: 'oidc'; callerScope: 'services'; callers: OidcCallers };\n\n/**\n * The OIDC methods ONE endpoint declares, in declaration order: one, or one per caller scope. They are\n * ONE bearer credential, so a verifier receives them together, checks the token's signature once, and\n * admits the call when ANY of them admits the caller.\n */\nexport type OidcAuthMethods = readonly [OidcAuthMethod, ...OidcAuthMethod[]];\n\n/** One concrete mechanism; user policy belongs exclusively to WpAuthorization. */\nexport type AuthMethod =\n | { kind: 'jwt' }\n | OidcAuthMethod\n | { kind: 'shared-secret'; secretKey: string }\n | { kind: 'webhook'; name: string }\n | { kind: 'apikey'; regime: string; credentials: ApiKeyCredentials };\n\nexport type AuthMethods = readonly [AuthMethod, ...AuthMethod[]];\nexport type AuthMode = AuthMethod | { kind: 'public' };\n\n/** Alternatives remain unresolved until a concrete mechanism is chosen for this hop. */\nexport class AuthMeta {\n readonly methods: readonly [AuthMode, ...AuthMode[]];\n\n constructor(\n methods: readonly [AuthMode, ...AuthMode[]],\n public readonly publicReason: string | undefined,\n ) {\n this.methods = methods;\n AuthDeclaration.validate(this);\n this.methods = Object.freeze(methods.map((method: AuthMode) => AuthMeta.snapshot(method))) as readonly [AuthMode, ...AuthMode[]];\n Object.freeze(this);\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static snapshot(method: AuthMode): AuthMode {\n switch (method.kind) {\n case 'oidc': return method.callerScope === 'services'\n ? Object.freeze({ ...method, callers: Object.freeze([...method.callers]) as OidcCallers })\n : Object.freeze({ ...method });\n case 'apikey': return Object.freeze({ ...method, credentials: Object.freeze(method.credentials.map((credential: ApiKeyCredential) => Object.freeze({ ...credential }))) as ApiKeyCredentials });\n default: return Object.freeze({ ...method });\n }\n }\n}\n\nexport class AuthDeclaration {\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n static validate(meta: AuthMeta): void {\n if (!Array.isArray(meta.methods) || meta.methods.length === 0) throw new Error('@WpAuth requires a nonempty methods tuple.');\n const kinds = new Set<string>();\n for (const method of meta.methods) {\n if (!method || typeof method !== 'object' || kinds.has(this.uniquenessKey(method))) throw new Error('@WpAuth rejects duplicate or invalid credential methods.');\n kinds.add(this.uniquenessKey(method));\n this.validateMethod(method);\n }\n if (kinds.has('public')) {\n if (meta.methods.length !== 1 || typeof meta.publicReason !== 'string' || !meta.publicReason.trim()) {\n throw new Error('Public auth must be declared separately with @WpAuthPublic(reason).');\n }\n } else if (meta.publicReason !== undefined) throw new Error('Protected @WpAuth rejects public reasons.');\n }\n\n /**\n * One method per credential kind, except OIDC: one per `callerScope`, so `oidcSameProject()` and\n * `oidcSvcs([...])` may pair on one endpoint (this project's accounts OR the listed ones). They are\n * still ONE bearer credential, which AuthFilter verifies once for both.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static uniquenessKey(method: AuthMode): string {\n return method.kind === 'oidc' ? `oidc:${method.callerScope}` : method.kind;\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static validateMethod(method: AuthMode): void {\n let keys: readonly string[];\n switch (method.kind) {\n case 'jwt':\n case 'public': keys = ['kind']; break;\n case 'oidc': keys = this.oidcKeys(method); break;\n case 'shared-secret':\n keys = ['kind', 'secretKey'];\n this.requireText(method.secretKey, 'sharedSecret'); break;\n case 'webhook':\n keys = ['kind', 'name'];\n this.requireText(method.name, 'webhook'); break;\n case 'apikey':\n keys = ['kind', 'regime', 'credentials'];\n this.requireText(method.regime, 'apiKey');\n if (!Array.isArray(method.credentials) || !method.credentials.length) throw new Error('apiKey(...) requires a nonempty credentials tuple.');\n for (const credential of method.credentials) {\n if (credential.in === 'header') this.requireHeaderKey(credential.key);\n else if (credential.in !== 'bearer' || Object.hasOwn(credential, 'key')) throw new Error('Invalid API-key credential location.');\n }\n break;\n default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/webhook/apiKey descriptors.');\n }\n for (const key of Object.keys(method)) {\n if (!keys.includes(key)) throw new Error(`Auth method rejects '${key}'; operation policy belongs in @WpAuthorization.`);\n }\n }\n\n /**\n * The keys an OIDC descriptor may carry, after checking its scope. `OidcCallers` already makes an\n * empty `oidcSvcs([])` a compile error; this is the backstop for a list that reached the decorator\n * around the type (a cast, a spread of a runtime array, plain JS), so it fails when the decorator\n * is evaluated instead of reaching a verifier.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static oidcKeys(method: OidcAuthMethod): readonly string[] {\n if (method.callerScope === 'same-project') return ['kind', 'callerScope'];\n if (method.callerScope !== 'services') throw new Error(\"Unknown OIDC callerScope; use oidcSameProject() or oidcSvcs(['svc-a']).\");\n if (!Array.isArray(method.callers) || method.callers.length === 0 || !method.callers.every((caller: string) => typeof caller === 'string' && caller.trim().length > 0)) {\n throw new Error(\"oidcSvcs([...]) requires at least one nonblank service account; use oidcSameProject() to accept this GCP project's service accounts.\");\n }\n return ['kind', 'callerScope', 'callers'];\n }\n\n /**\n * A header credential's key must be a ContextKey that rides the wire. `httpHeader` is optional on\n * ContextKey itself (most keys never leave the process), and the factories' explicit value-type\n * argument (`ContextKey.untrusted<string>(...)`) stops TypeScript inferring whether one was\n * passed, so the type cannot say \"a key WITH a header\". It is checked once, when the contract is\n * decorated, and the OpenAPI generator refuses the same key statically.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireHeaderKey(key: AnyContextKey | undefined): void {\n if (!key || typeof key !== 'object' || typeof key.name !== 'string') {\n throw new Error(\"apiKey header credential requires `key`: the ContextKey carrying it, e.g. { in: 'header', key: MyKeys.API_KEY }.\");\n }\n if (typeof key.httpHeader !== 'string' || !key.httpHeader.trim()) {\n throw new Error(`apiKey header credential key '${key.name}' declares no httpHeader; define it with ContextKey.trusted(name, provenance, '<header>') or ContextKey.untrusted(name, '<header>').`);\n }\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireText(value: string, label: string): void {\n if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} requires a nonempty name.`);\n }\n}\n\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function jwt(): AuthMethod { return { kind: 'jwt' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSameProject(): OidcAuthMethod { return { kind: 'oidc', callerScope: 'same-project' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSvcs(callers: OidcCallers): OidcAuthMethod { return { kind: 'oidc', callerScope: 'services', callers }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function sharedSecret(secretKey: string): AuthMethod { return { kind: 'shared-secret', secretKey }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function webhook(name: string): AuthMethod { return { kind: 'webhook', name }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function apiKey(regime: string, credentials: ApiKeyCredentials): AuthMethod { return { kind: 'apikey', regime, credentials }; }\n"]}
|
|
1
|
+
{"version":3,"file":"auth-mode.js","sourceRoot":"","sources":["../../../../../../packages/core/core-util/src/http/auth-mode.ts"],"names":[],"mappings":";;;AAkLA,kBAA6D;AAE7D,0CAA2G;AAE3G,4BAA6H;AAE7H,oCAA4G;AAQ5G,gCAA6F;AAE7F,wBAAsI;AA1ItI,wFAAwF;AACxF,MAAa,QAAQ;IAKG;IAJX,OAAO,CAAqC;IAErD,YACI,OAA2C,EAC3B,YAAgC;QAAhC,iBAAY,GAAZ,YAAY,CAAoB;QAEhD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAuC,CAAC;QACjI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,oIAAoI;IAC5H,MAAM,CAAC,QAAQ,CAAC,MAAgB;QACpC,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,MAAM,CAAC,CAAC,OAAO,MAAM,CAAC,WAAW,KAAK,UAAU;gBACjD,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAgB,EAAE,CAAC;gBAC1F,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;YACnC,KAAK,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAA4B,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,UAAU,EAAE,CAAC,CAAC,CAAsB,EAAE,CAAC,CAAC;YAChM,OAAO,CAAC,CAAC,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACjD,CAAC;IACL,CAAC;CACJ;AAvBD,4BAuBC;AAED,MAAa,eAAe;IACxB,oIAAoI;IACpI,MAAM,CAAC,QAAQ,CAAC,IAAc;QAC1B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC7H,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAChC,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;YAChK,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;YACtC,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACtB,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;gBAClG,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;YAC3F,CAAC;QACL,CAAC;aAAM,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC7G,CAAC;IAED;;;;OAIG;IACH,oIAAoI;IAC5H,MAAM,CAAC,aAAa,CAAC,MAAgB;QACzC,OAAO,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC;IAC/E,CAAC;IAED,oIAAoI;IAC5H,MAAM,CAAC,cAAc,CAAC,MAAgB;QAC1C,IAAI,IAAuB,CAAC;QAC5B,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;YAClB,KAAK,KAAK,CAAC;YACX,KAAK,QAAQ;gBAAE,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;gBAAC,MAAM;YACtC,KAAK,MAAM;gBAAE,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;gBAAC,MAAM;YACjD,KAAK,eAAe;gBAChB,IAAI,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;gBAC7B,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;gBAAC,MAAM;YAC9D,KAAK,YAAY;gBACb,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBACxB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;gBAAC,MAAM;YACvD,KAAK,QAAQ;gBACT,IAAI,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,aAAa,CAAC,CAAC;gBACzC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;gBAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM;oBAAE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;gBAC5I,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;oBAC1C,IAAI,UAAU,CAAC,EAAE,KAAK,QAAQ;wBAAE,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;yBACjE,IAAI,UAAU,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC;wBAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;gBACrI,CAAC;gBACD,MAAM;YACV,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,sGAAsG,CAAC,CAAC;QACrI,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,kDAAkD,CAAC,CAAC;QAC5H,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,oIAAoI;IAC5H,MAAM,CAAC,QAAQ,CAAC,MAAsB;QAC1C,IAAI,MAAM,CAAC,WAAW,KAAK,cAAc;YAAE,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;QAC1E,IAAI,MAAM,CAAC,WAAW,KAAK,UAAU;YAAE,MAAM,IAAI,KAAK,CAAC,yEAAyE,CAAC,CAAC;QAClI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAc,EAAE,EAAE,CAAC,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;YACrK,MAAM,IAAI,KAAK,CAAC,sIAAsI,CAAC,CAAC;QAC5J,CAAC;QACD,OAAO,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC;IAC9C,CAAC;IAED;;;;;;OAMG;IACH,oIAAoI;IAC5H,MAAM,CAAC,gBAAgB,CAAC,GAA8B;QAC1D,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,kHAAkH,CAAC,CAAC;QACxI,CAAC;QACD,IAAI,OAAO,GAAG,CAAC,UAAU,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,CAAC;YAC/D,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,IAAI,sIAAsI,CAAC,CAAC;QACrM,CAAC;IACL,CAAC;IAED,oIAAoI;IAC5H,MAAM,CAAC,WAAW,CAAC,KAAa,EAAE,KAAa;QACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,4BAA4B,CAAC,CAAC;IAC1G,CAAC;CACJ;AA7FD,0CA6FC;AAED,yFAAyF;AACzF,SAAgB,GAAG,KAAiB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC7D,yFAAyF;AACzF,SAAgB,eAAe,KAAqB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;AAC3G,yFAAyF;AACzF,SAAgB,QAAQ,CAAC,OAAoB,IAAoB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AAC7H,yFAAyF;AACzF,SAAgB,YAAY,CAAC,SAAiB,IAAgB,OAAO,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;AAC5G;;;;;GAKG;AACH,yFAAyF;AACzF,SAAgB,UAAU,CAAC,IAAY,IAAgB,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7F,yFAAyF;AACzF,SAAgB,MAAM,CAAC,MAAc,EAAE,WAA8B,IAAgB,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC","sourcesContent":["import type { AnyContextKey } from '../ContextKey';\n\n/**\n * WHERE one api-key credential rides.\n *\n * A header credential names the {@link AnyContextKey ContextKey} that carries it — NEVER a header-name\n * string. The key's `httpHeader` is the wire name, so the ONE definition of each header (the\n * ContextKey) is reused on every surface: `RequestContext` put/get, the client/server header\n * transfer, this declaration, and the published OpenAPI document. A string here was a second copy a\n * rename left stale (#1249). The key MUST declare an `httpHeader` — {@link AuthDeclaration} refuses\n * one that does not, and the OpenAPI generator reads the header name off the key's\n * `ContextKey.trusted(...)` / `ContextKey.untrusted(...)` definition.\n */\nexport type ApiKeyCredential =\n | { in: 'header'; key: AnyContextKey; description?: string }\n | { in: 'bearer'; key?: never; description?: string };\n\nexport type ApiKeyCredentials = readonly [ApiKeyCredential, ...ApiKeyCredential[]];\n\n/**\n * The service accounts an `oidcSvcs([...])` endpoint names, at least one: an empty list is not a\n * policy anybody could mean, so it does not compile. Each entry resolves as `'self'` (this service's\n * runtime SA), a bare id (`<id>@<project>.iam.gserviceaccount.com`) or a full email (verbatim).\n */\nexport type OidcCallers = readonly [string, ...string[]];\n\n/**\n * WHICH Google-signed callers an OIDC endpoint accepts, named by `callerScope`:\n * - `'same-project'` (`oidcSameProject()`): any service account of THIS GCP project. Off GCP there is\n * no project, so the check is open.\n * - `'services'` (`oidcSvcs([...])`): exactly the listed service accounts, which may belong to other\n * projects.\n * One endpoint may declare one of each; see {@link OidcAuthMethods}.\n */\nexport type OidcAuthMethod =\n | { kind: 'oidc'; callerScope: 'same-project' }\n | { kind: 'oidc'; callerScope: 'services'; callers: OidcCallers };\n\n/**\n * The OIDC methods ONE endpoint declares, in declaration order: one, or one per caller scope. They are\n * ONE bearer credential, so a verifier receives them together, checks the token's signature once, and\n * admits the call when ANY of them admits the caller.\n */\nexport type OidcAuthMethods = readonly [OidcAuthMethod, ...OidcAuthMethod[]];\n\n/** One concrete mechanism; user policy belongs exclusively to WpAuthorization. */\nexport type AuthMethod =\n | { kind: 'jwt' }\n | OidcAuthMethod\n | { kind: 'shared-secret'; secretKey: string }\n | { kind: 'vendorAuth'; name: string }\n | { kind: 'apikey'; regime: string; credentials: ApiKeyCredentials };\n\nexport type AuthMethods = readonly [AuthMethod, ...AuthMethod[]];\nexport type AuthMode = AuthMethod | { kind: 'public' };\n\n/** Alternatives remain unresolved until a concrete mechanism is chosen for this hop. */\nexport class AuthMeta {\n readonly methods: readonly [AuthMode, ...AuthMode[]];\n\n constructor(\n methods: readonly [AuthMode, ...AuthMode[]],\n public readonly publicReason: string | undefined,\n ) {\n this.methods = methods;\n AuthDeclaration.validate(this);\n this.methods = Object.freeze(methods.map((method: AuthMode) => AuthMeta.snapshot(method))) as readonly [AuthMode, ...AuthMode[]];\n Object.freeze(this);\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static snapshot(method: AuthMode): AuthMode {\n switch (method.kind) {\n case 'oidc': return method.callerScope === 'services'\n ? Object.freeze({ ...method, callers: Object.freeze([...method.callers]) as OidcCallers })\n : Object.freeze({ ...method });\n case 'apikey': return Object.freeze({ ...method, credentials: Object.freeze(method.credentials.map((credential: ApiKeyCredential) => Object.freeze({ ...credential }))) as ApiKeyCredentials });\n default: return Object.freeze({ ...method });\n }\n }\n}\n\nexport class AuthDeclaration {\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n static validate(meta: AuthMeta): void {\n if (!Array.isArray(meta.methods) || meta.methods.length === 0) throw new Error('@WpAuth requires a nonempty methods tuple.');\n const kinds = new Set<string>();\n for (const method of meta.methods) {\n if (!method || typeof method !== 'object' || kinds.has(this.uniquenessKey(method))) throw new Error('@WpAuth rejects duplicate or invalid credential methods.');\n kinds.add(this.uniquenessKey(method));\n this.validateMethod(method);\n }\n if (kinds.has('public')) {\n if (meta.methods.length !== 1 || typeof meta.publicReason !== 'string' || !meta.publicReason.trim()) {\n throw new Error('Public auth must be declared separately with @WpAuthPublic(reason).');\n }\n } else if (meta.publicReason !== undefined) throw new Error('Protected @WpAuth rejects public reasons.');\n }\n\n /**\n * One method per credential kind, except OIDC: one per `callerScope`, so `oidcSameProject()` and\n * `oidcSvcs([...])` may pair on one endpoint (this project's accounts OR the listed ones). They are\n * still ONE bearer credential, which AuthFilter verifies once for both.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static uniquenessKey(method: AuthMode): string {\n return method.kind === 'oidc' ? `oidc:${method.callerScope}` : method.kind;\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static validateMethod(method: AuthMode): void {\n let keys: readonly string[];\n switch (method.kind) {\n case 'jwt':\n case 'public': keys = ['kind']; break;\n case 'oidc': keys = this.oidcKeys(method); break;\n case 'shared-secret':\n keys = ['kind', 'secretKey'];\n this.requireText(method.secretKey, 'sharedSecret'); break;\n case 'vendorAuth':\n keys = ['kind', 'name'];\n this.requireText(method.name, 'vendorAuth'); break;\n case 'apikey':\n keys = ['kind', 'regime', 'credentials'];\n this.requireText(method.regime, 'apiKey');\n if (!Array.isArray(method.credentials) || !method.credentials.length) throw new Error('apiKey(...) requires a nonempty credentials tuple.');\n for (const credential of method.credentials) {\n if (credential.in === 'header') this.requireHeaderKey(credential.key);\n else if (credential.in !== 'bearer' || Object.hasOwn(credential, 'key')) throw new Error('Invalid API-key credential location.');\n }\n break;\n default: throw new Error('Unknown @WpAuth method; use jwt/oidcSameProject/oidcSvcs/sharedSecret/vendorAuth/apiKey descriptors.');\n }\n for (const key of Object.keys(method)) {\n if (!keys.includes(key)) throw new Error(`Auth method rejects '${key}'; operation policy belongs in @WpAuthorization.`);\n }\n }\n\n /**\n * The keys an OIDC descriptor may carry, after checking its scope. `OidcCallers` already makes an\n * empty `oidcSvcs([])` a compile error; this is the backstop for a list that reached the decorator\n * around the type (a cast, a spread of a runtime array, plain JS), so it fails when the decorator\n * is evaluated instead of reaching a verifier.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static oidcKeys(method: OidcAuthMethod): readonly string[] {\n if (method.callerScope === 'same-project') return ['kind', 'callerScope'];\n if (method.callerScope !== 'services') throw new Error(\"Unknown OIDC callerScope; use oidcSameProject() or oidcSvcs(['svc-a']).\");\n if (!Array.isArray(method.callers) || method.callers.length === 0 || !method.callers.every((caller: string) => typeof caller === 'string' && caller.trim().length > 0)) {\n throw new Error(\"oidcSvcs([...]) requires at least one nonblank service account; use oidcSameProject() to accept this GCP project's service accounts.\");\n }\n return ['kind', 'callerScope', 'callers'];\n }\n\n /**\n * A header credential's key must be a ContextKey that rides the wire. `httpHeader` is optional on\n * ContextKey itself (most keys never leave the process), and the factories' explicit value-type\n * argument (`ContextKey.untrusted<string>(...)`) stops TypeScript inferring whether one was\n * passed, so the type cannot say \"a key WITH a header\". It is checked once, when the contract is\n * decorated, and the OpenAPI generator refuses the same key statically.\n */\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireHeaderKey(key: AnyContextKey | undefined): void {\n if (!key || typeof key !== 'object' || typeof key.name !== 'string') {\n throw new Error(\"apiKey header credential requires `key`: the ContextKey carrying it, e.g. { in: 'header', key: MyKeys.API_KEY }.\");\n }\n if (typeof key.httpHeader !== 'string' || !key.httpHeader.trim()) {\n throw new Error(`apiKey header credential key '${key.name}' declares no httpHeader; define it with ContextKey.trusted(name, provenance, '<header>') or ContextKey.untrusted(name, '<header>').`);\n }\n }\n\n // webpieces-disable no-function-outside-class -- stateless contract validation or scope metadata reader used before DI registration\n private static requireText(value: string, label: string): void {\n if (typeof value !== 'string' || !value.trim()) throw new Error(`${label} requires a nonempty name.`);\n }\n}\n\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function jwt(): AuthMethod { return { kind: 'jwt' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSameProject(): OidcAuthMethod { return { kind: 'oidc', callerScope: 'same-project' }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function oidcSvcs(callers: OidcCallers): OidcAuthMethod { return { kind: 'oidc', callerScope: 'services', callers }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function sharedSecret(secretKey: string): AuthMethod { return { kind: 'shared-secret', secretKey }; }\n/**\n * A credential in a VENDOR's scheme, on both ends of a hop: the app-bound `VendorAuthHook` (core-context)\n * mints it when WE call the vendor (and refreshes it once on a 401), and verifies it when the vendor\n * calls US. `name` selects the vendor. A served `vendorAuth(...)` endpoint requires\n * `@Endpoint(..., { rawBody: true })` so the hook has the exact bytes to verify.\n */\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function vendorAuth(name: string): AuthMethod { return { kind: 'vendorAuth', name }; }\n// webpieces-disable no-function-outside-class -- canonical credential descriptor factory\nexport function apiKey(regime: string, credentials: ApiKeyCredentials): AuthMethod { return { kind: 'apikey', regime, credentials }; }\n"]}
|
package/src/http/decorators.d.ts
CHANGED
|
@@ -5,10 +5,11 @@ import { EndpointOperation, EndpointOptions, ExternalEndpointOptions } from './H
|
|
|
5
5
|
import { HttpMethod } from './HttpContract';
|
|
6
6
|
export type { EndpointOptions, ExternalEndpointOptions } from './HttpEndpointOptions';
|
|
7
7
|
export type { EndpointOperation } from './HttpEndpointOptions';
|
|
8
|
-
export { READ, WRITE_IDEMPOTENT, WRITE } from './HttpEndpointOptions';
|
|
8
|
+
export { READ, WRITE_IDEMPOTENT, WRITE, NONE } from './HttpEndpointOptions';
|
|
9
|
+
export type { ResponseBodyKind } from './HttpEndpointOptions';
|
|
9
10
|
export type { HttpMethod } from './HttpContract';
|
|
10
|
-
export { GET, POST } from './HttpContract';
|
|
11
|
-
export { PathParam, QueryParam, getHttpParameterDeclarations } from './http-parameter-decorators';
|
|
11
|
+
export { GET, POST, PUT, PATCH, DELETE } from './HttpContract';
|
|
12
|
+
export { PathParam, QueryParam, QueryString, getHttpParameterDeclarations } from './http-parameter-decorators';
|
|
12
13
|
/**
|
|
13
14
|
* Metadata keys for storing API routing information.
|
|
14
15
|
* These keys are used by both server-side (routing) and client-side (client generation).
|
|
@@ -29,9 +30,9 @@ export declare const METADATA_KEYS: {
|
|
|
29
30
|
ENDPOINT_HTTP_METHOD: string;
|
|
30
31
|
/** Per-method required read/write semantics, parallel to ENDPOINTS. */
|
|
31
32
|
ENDPOINT_OPERATION: string;
|
|
32
|
-
/** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */
|
|
33
|
+
/** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external-inbound | external-outbound), parallel to ENDPOINTS. */
|
|
33
34
|
ENDPOINT_KIND: string;
|
|
34
|
-
/** Per-method declared external CALLER (only for kind 'external'), parallel to ENDPOINTS. */
|
|
35
|
+
/** Per-method declared external CALLER (only for kind 'external-inbound'), parallel to ENDPOINTS. */
|
|
35
36
|
ENDPOINT_CALLER: string;
|
|
36
37
|
/** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */
|
|
37
38
|
MASK_LOG: string;
|
|
@@ -51,24 +52,35 @@ declare enum EndpointKindValue {
|
|
|
51
52
|
RPC = "rpc",
|
|
52
53
|
CLOUDTASKS = "cloudtasks",
|
|
53
54
|
CRON = "cron",
|
|
54
|
-
|
|
55
|
+
EXTERNAL_INBOUND = "external-inbound",
|
|
56
|
+
EXTERNAL_OUTBOUND = "external-outbound"
|
|
55
57
|
}
|
|
56
58
|
/** Short, statically importable decorator arguments. */
|
|
57
59
|
export declare const RPC = EndpointKindValue.RPC;
|
|
58
60
|
export declare const CLOUDTASKS = EndpointKindValue.CLOUDTASKS;
|
|
59
61
|
export declare const CRON = EndpointKindValue.CRON;
|
|
60
|
-
|
|
62
|
+
/**
|
|
63
|
+
* An OUTSIDE system calls US (a vendor webhook, a push subscription). Requires `calledBy`, which names
|
|
64
|
+
* that system on the runtime architecture graph.
|
|
65
|
+
*/
|
|
66
|
+
export declare const EXTERNAL_INBOUND = EndpointKindValue.EXTERNAL_INBOUND;
|
|
67
|
+
/**
|
|
68
|
+
* WE call an OUTSIDE system (a vendor API, a partner's webhook receiver). No server in this repo
|
|
69
|
+
* implements it, every method of the contract must be this kind, and the client sends ONLY the
|
|
70
|
+
* context keys listed with `@ExternalContextKeys(...)` and merges no response header into our context.
|
|
71
|
+
*/
|
|
72
|
+
export declare const EXTERNAL_OUTBOUND = EndpointKindValue.EXTERNAL_OUTBOUND;
|
|
61
73
|
/** Runtime trigger/delivery owner used by validation, architecture, and infrastructure tooling. */
|
|
62
|
-
export type EndpointKind = typeof RPC | typeof CLOUDTASKS | typeof CRON | typeof
|
|
74
|
+
export type EndpointKind = typeof RPC | typeof CLOUDTASKS | typeof CRON | typeof EXTERNAL_INBOUND | typeof EXTERNAL_OUTBOUND;
|
|
63
75
|
/** Class decorator that sets the shared base path for all endpoints. */
|
|
64
76
|
export declare function ApiPath(basePath: string): ClassDecorator;
|
|
65
77
|
/**
|
|
66
78
|
* Registers `@Endpoint(POST, path, WRITE, RPC)`. Method, operation, and trigger are required enum
|
|
67
|
-
* values. Operation is independent of the HTTP verb.
|
|
79
|
+
* values. Operation is independent of the HTTP verb. EXTERNAL_INBOUND additionally requires `calledBy`.
|
|
68
80
|
* Each classification rides a parallel metadata map while ENDPOINTS remains method-name -> path.
|
|
69
81
|
*/
|
|
70
|
-
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: typeof
|
|
71
|
-
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: Exclude<EndpointKind, typeof
|
|
82
|
+
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: typeof EXTERNAL_INBOUND, options: ExternalEndpointOptions): MethodDecorator;
|
|
83
|
+
export declare function Endpoint(httpMethod: HttpMethod, path: string, operation: EndpointOperation, kind: Exclude<EndpointKind, typeof EXTERNAL_INBOUND>, options?: EndpointOptions): MethodDecorator;
|
|
72
84
|
/** Declares request/response DTO field names that API logging must mask at any depth. */
|
|
73
85
|
export declare function MaskLog(fields: Record<string, MaskMode>): MethodDecorator;
|
|
74
86
|
/**
|
|
@@ -118,12 +130,18 @@ export declare function getEndpointOptions(apiClass: Function, methodName: strin
|
|
|
118
130
|
/** Read one endpoint's required side-effect semantics. */
|
|
119
131
|
export declare function getEndpointOperation(apiClass: Function, methodName: string): EndpointOperation;
|
|
120
132
|
/**
|
|
121
|
-
* Fail-fast at wiring time
|
|
122
|
-
*
|
|
123
|
-
* a
|
|
124
|
-
*
|
|
133
|
+
* Fail-fast at wiring time on the two EXTERNAL_* rules, called by `RouteMetadataFactory` for every
|
|
134
|
+
* transport (server routes and every client). The {@link Endpoint} overloads already make a missing
|
|
135
|
+
* `calledBy` a COMPILE error; this is the backstop for the ways TS is bypassed — a JS caller, an
|
|
136
|
+
* `as any` options object, a hand-rolled Reflect.defineMetadata. The second rule cannot be a type:
|
|
137
|
+
* "every method of this class" spans several decorators.
|
|
138
|
+
*
|
|
139
|
+
* @throws Error naming the first EXTERNAL_INBOUND endpoint with no `calledBy`, or the first method of
|
|
140
|
+
* a contract that mixes EXTERNAL_OUTBOUND with any other kind.
|
|
125
141
|
*/
|
|
126
|
-
export declare function
|
|
142
|
+
export declare function assertExternalEndpointKinds(apiClass: Function): void;
|
|
143
|
+
/** True when the contract is an outside system's API: every method is EXTERNAL_OUTBOUND. */
|
|
144
|
+
export declare function isExternalOutboundApi(apiClass: Function): boolean;
|
|
127
145
|
/**
|
|
128
146
|
* True when the method's @Endpoint declared `{ formPost: true }` — its body is
|
|
129
147
|
* application/x-www-form-urlencoded (flat), not JSON.
|
|
@@ -131,22 +149,22 @@ export declare function assertEveryExternalEndpointDeclaresCaller(apiClass: Func
|
|
|
131
149
|
export declare function isFormPost(apiClass: Function, methodName: string): boolean;
|
|
132
150
|
/**
|
|
133
151
|
* True when the method's @Endpoint declared `{ rawBody: true }` — the transport must retain the
|
|
134
|
-
* verbatim bytes + absolute url for
|
|
152
|
+
* verbatim bytes + absolute url for a `VendorAuthHook.verify` to check.
|
|
135
153
|
*/
|
|
136
154
|
export declare function isRawBody(apiClass: Function, methodName: string): boolean;
|
|
137
155
|
/**
|
|
138
|
-
* Fail-fast at wiring time when
|
|
156
|
+
* Fail-fast at wiring time when a SERVED `vendorAuth(...)` endpoint did not ask the transport to keep the
|
|
139
157
|
* bytes it is supposed to verify. A hook with nothing to verify is a MISCONFIGURATION, and it must
|
|
140
158
|
* surface at startup, naming the fix — not as a 401 in production on exactly the traffic the endpoint
|
|
141
159
|
* exists for.
|
|
142
160
|
*
|
|
143
161
|
* This pairing is a runtime assert rather than a type because the two halves live on DIFFERENT
|
|
144
|
-
* decorators (`
|
|
162
|
+
* decorators (`vendorAuth(...)` and `@Endpoint`), and no union over one decorator's argument can say
|
|
145
163
|
* anything about the other's.
|
|
146
164
|
*
|
|
147
|
-
* @throws Error naming the first `
|
|
165
|
+
* @throws Error naming the first `vendorAuth(...)` endpoint missing `{ rawBody: true }`.
|
|
148
166
|
*/
|
|
149
|
-
export declare function
|
|
167
|
+
export declare function assertEveryVendorAuthEndpointRetainsRawBody(apiClass: Function): void;
|
|
150
168
|
/**
|
|
151
169
|
* Check if a class has @ApiPath decorator.
|
|
152
170
|
*/
|
package/src/http/decorators.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MISSING_AUTH_DECORATOR_FIX = exports.
|
|
3
|
+
exports.MISSING_AUTH_DECORATOR_FIX = exports.EXTERNAL_OUTBOUND = exports.EXTERNAL_INBOUND = exports.CRON = exports.CLOUDTASKS = exports.RPC = exports.METADATA_KEYS = exports.getHttpParameterDeclarations = exports.QueryString = exports.QueryParam = exports.PathParam = exports.DELETE = exports.PATCH = exports.PUT = exports.POST = exports.GET = exports.NONE = exports.WRITE = exports.WRITE_IDEMPOTENT = exports.READ = void 0;
|
|
4
4
|
exports.ApiPath = ApiPath;
|
|
5
5
|
exports.Endpoint = Endpoint;
|
|
6
6
|
exports.MaskLog = MaskLog;
|
|
@@ -16,10 +16,11 @@ exports.getEndpointKind = getEndpointKind;
|
|
|
16
16
|
exports.getEndpointHttpMethod = getEndpointHttpMethod;
|
|
17
17
|
exports.getEndpointOptions = getEndpointOptions;
|
|
18
18
|
exports.getEndpointOperation = getEndpointOperation;
|
|
19
|
-
exports.
|
|
19
|
+
exports.assertExternalEndpointKinds = assertExternalEndpointKinds;
|
|
20
|
+
exports.isExternalOutboundApi = isExternalOutboundApi;
|
|
20
21
|
exports.isFormPost = isFormPost;
|
|
21
22
|
exports.isRawBody = isRawBody;
|
|
22
|
-
exports.
|
|
23
|
+
exports.assertEveryVendorAuthEndpointRetainsRawBody = assertEveryVendorAuthEndpointRetainsRawBody;
|
|
23
24
|
exports.isApiPath = isApiPath;
|
|
24
25
|
exports.getAuthMeta = getAuthMeta;
|
|
25
26
|
exports.assertEveryEndpointHasAuthMode = assertEveryEndpointHasAuthMode;
|
|
@@ -37,12 +38,17 @@ var HttpEndpointOptions_2 = require("./HttpEndpointOptions");
|
|
|
37
38
|
Object.defineProperty(exports, "READ", { enumerable: true, get: function () { return HttpEndpointOptions_2.READ; } });
|
|
38
39
|
Object.defineProperty(exports, "WRITE_IDEMPOTENT", { enumerable: true, get: function () { return HttpEndpointOptions_2.WRITE_IDEMPOTENT; } });
|
|
39
40
|
Object.defineProperty(exports, "WRITE", { enumerable: true, get: function () { return HttpEndpointOptions_2.WRITE; } });
|
|
41
|
+
Object.defineProperty(exports, "NONE", { enumerable: true, get: function () { return HttpEndpointOptions_2.NONE; } });
|
|
40
42
|
var HttpContract_2 = require("./HttpContract");
|
|
41
43
|
Object.defineProperty(exports, "GET", { enumerable: true, get: function () { return HttpContract_2.GET; } });
|
|
42
44
|
Object.defineProperty(exports, "POST", { enumerable: true, get: function () { return HttpContract_2.POST; } });
|
|
45
|
+
Object.defineProperty(exports, "PUT", { enumerable: true, get: function () { return HttpContract_2.PUT; } });
|
|
46
|
+
Object.defineProperty(exports, "PATCH", { enumerable: true, get: function () { return HttpContract_2.PATCH; } });
|
|
47
|
+
Object.defineProperty(exports, "DELETE", { enumerable: true, get: function () { return HttpContract_2.DELETE; } });
|
|
43
48
|
var http_parameter_decorators_2 = require("./http-parameter-decorators");
|
|
44
49
|
Object.defineProperty(exports, "PathParam", { enumerable: true, get: function () { return http_parameter_decorators_2.PathParam; } });
|
|
45
50
|
Object.defineProperty(exports, "QueryParam", { enumerable: true, get: function () { return http_parameter_decorators_2.QueryParam; } });
|
|
51
|
+
Object.defineProperty(exports, "QueryString", { enumerable: true, get: function () { return http_parameter_decorators_2.QueryString; } });
|
|
46
52
|
Object.defineProperty(exports, "getHttpParameterDeclarations", { enumerable: true, get: function () { return http_parameter_decorators_2.getHttpParameterDeclarations; } });
|
|
47
53
|
/**
|
|
48
54
|
* Metadata keys for storing API routing information.
|
|
@@ -64,9 +70,9 @@ exports.METADATA_KEYS = {
|
|
|
64
70
|
ENDPOINT_HTTP_METHOD: 'webpieces:endpoint-http-method',
|
|
65
71
|
/** Per-method required read/write semantics, parallel to ENDPOINTS. */
|
|
66
72
|
ENDPOINT_OPERATION: 'webpieces:endpoint-operation',
|
|
67
|
-
/** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external), parallel to ENDPOINTS. */
|
|
73
|
+
/** Per-method @Endpoint trigger kind (rpc | cloudtasks | cron | external-inbound | external-outbound), parallel to ENDPOINTS. */
|
|
68
74
|
ENDPOINT_KIND: 'webpieces:endpoint-kind',
|
|
69
|
-
/** Per-method declared external CALLER (only for kind 'external'), parallel to ENDPOINTS. */
|
|
75
|
+
/** Per-method declared external CALLER (only for kind 'external-inbound'), parallel to ENDPOINTS. */
|
|
70
76
|
ENDPOINT_CALLER: external_caller_1.ENDPOINT_CALLER_KEY,
|
|
71
77
|
/** Per-method @MaskLog spec (which DTO fields the LogApiCall path masks). */
|
|
72
78
|
MASK_LOG: 'webpieces:mask-log',
|
|
@@ -87,13 +93,24 @@ var EndpointKindValue;
|
|
|
87
93
|
EndpointKindValue["RPC"] = "rpc";
|
|
88
94
|
EndpointKindValue["CLOUDTASKS"] = "cloudtasks";
|
|
89
95
|
EndpointKindValue["CRON"] = "cron";
|
|
90
|
-
EndpointKindValue["
|
|
96
|
+
EndpointKindValue["EXTERNAL_INBOUND"] = "external-inbound";
|
|
97
|
+
EndpointKindValue["EXTERNAL_OUTBOUND"] = "external-outbound";
|
|
91
98
|
})(EndpointKindValue || (EndpointKindValue = {}));
|
|
92
99
|
/** Short, statically importable decorator arguments. */
|
|
93
100
|
exports.RPC = EndpointKindValue.RPC;
|
|
94
101
|
exports.CLOUDTASKS = EndpointKindValue.CLOUDTASKS;
|
|
95
102
|
exports.CRON = EndpointKindValue.CRON;
|
|
96
|
-
|
|
103
|
+
/**
|
|
104
|
+
* An OUTSIDE system calls US (a vendor webhook, a push subscription). Requires `calledBy`, which names
|
|
105
|
+
* that system on the runtime architecture graph.
|
|
106
|
+
*/
|
|
107
|
+
exports.EXTERNAL_INBOUND = EndpointKindValue.EXTERNAL_INBOUND;
|
|
108
|
+
/**
|
|
109
|
+
* WE call an OUTSIDE system (a vendor API, a partner's webhook receiver). No server in this repo
|
|
110
|
+
* implements it, every method of the contract must be this kind, and the client sends ONLY the
|
|
111
|
+
* context keys listed with `@ExternalContextKeys(...)` and merges no response header into our context.
|
|
112
|
+
*/
|
|
113
|
+
exports.EXTERNAL_OUTBOUND = EndpointKindValue.EXTERNAL_OUTBOUND;
|
|
97
114
|
/** Class decorator that sets the shared base path for all endpoints. */
|
|
98
115
|
function ApiPath(basePath) {
|
|
99
116
|
return (target) => {
|
|
@@ -118,14 +135,14 @@ function Endpoint(httpMethod, path, operation, kind, options = {}) {
|
|
|
118
135
|
}
|
|
119
136
|
// webpieces-disable no-function-outside-class -- runtime validation backs up nominal TS enums
|
|
120
137
|
function validateEndpointClassification(httpMethod, operation, kind) {
|
|
121
|
-
if (!isHttpMethod(httpMethod)) {
|
|
122
|
-
throw new Error(`@Endpoint httpMethod must be
|
|
138
|
+
if (!(0, HttpContract_1.isHttpMethod)(httpMethod)) {
|
|
139
|
+
throw new Error(`@Endpoint httpMethod must be one of ${HttpContract_1.HTTP_METHODS.join(', ')}, received '${String(httpMethod)}'.`);
|
|
123
140
|
}
|
|
124
141
|
if (!isEndpointOperation(operation)) {
|
|
125
142
|
throw new Error(`@Endpoint operation must be READ, WRITE_IDEMPOTENT, or WRITE, received '${String(operation)}'.`);
|
|
126
143
|
}
|
|
127
144
|
if (!isEndpointKind(kind)) {
|
|
128
|
-
throw new Error(`@Endpoint trigger must be RPC, CLOUDTASKS, CRON, or
|
|
145
|
+
throw new Error(`@Endpoint trigger must be RPC, CLOUDTASKS, CRON, EXTERNAL_INBOUND, or EXTERNAL_OUTBOUND, received '${String(kind)}'.`);
|
|
129
146
|
}
|
|
130
147
|
}
|
|
131
148
|
// webpieces-disable no-function-outside-class -- one metadata write shared by the decorator overloads
|
|
@@ -146,7 +163,7 @@ function recordEndpointMetadata(metadataTarget, propertyKey, httpMethod, path, o
|
|
|
146
163
|
opts[propertyKey] = options;
|
|
147
164
|
Reflect.defineMetadata(exports.METADATA_KEYS.ENDPOINT_OPTIONS, opts, metadataTarget);
|
|
148
165
|
const declared = options;
|
|
149
|
-
if (kind !== exports.
|
|
166
|
+
if (kind !== exports.EXTERNAL_INBOUND ||
|
|
150
167
|
typeof declared.calledBy !== 'string' ||
|
|
151
168
|
declared.calledBy === '')
|
|
152
169
|
return;
|
|
@@ -267,8 +284,8 @@ function getEndpointKind(apiClass, methodName) {
|
|
|
267
284
|
function getEndpointHttpMethod(apiClass, methodName) {
|
|
268
285
|
const methods = Reflect.getMetadata(exports.METADATA_KEYS.ENDPOINT_HTTP_METHOD, apiClass) || {};
|
|
269
286
|
const method = methods[methodName];
|
|
270
|
-
if (!isHttpMethod(method)) {
|
|
271
|
-
throw new Error(`@Endpoint ${apiClass.name || 'Unknown'}.${methodName} must declare
|
|
287
|
+
if (!(0, HttpContract_1.isHttpMethod)(method)) {
|
|
288
|
+
throw new Error(`@Endpoint ${apiClass.name || 'Unknown'}.${methodName} must declare one of ${HttpContract_1.HTTP_METHODS.join(', ')}.`);
|
|
272
289
|
}
|
|
273
290
|
return method;
|
|
274
291
|
}
|
|
@@ -302,35 +319,50 @@ function isEndpointOperation(value) {
|
|
|
302
319
|
value === HttpEndpointOptions_1.WRITE_IDEMPOTENT ||
|
|
303
320
|
value === HttpEndpointOptions_1.WRITE);
|
|
304
321
|
}
|
|
305
|
-
// webpieces-disable no-function-outside-class -- runtime backstop for JavaScript and `as any` callers; webpieces-disable no-any-unknown -- untrusted runtime value is narrowed to an HTTP method
|
|
306
|
-
function isHttpMethod(value) {
|
|
307
|
-
return value === HttpContract_1.GET || value === HttpContract_1.POST;
|
|
308
|
-
}
|
|
309
322
|
// webpieces-disable no-function-outside-class -- runtime backstop for JavaScript and `as any` callers; webpieces-disable no-any-unknown -- untrusted runtime value is narrowed to an endpoint kind
|
|
310
323
|
function isEndpointKind(value) {
|
|
311
324
|
return (value === exports.RPC ||
|
|
312
325
|
value === exports.CLOUDTASKS ||
|
|
313
326
|
value === exports.CRON ||
|
|
314
|
-
value === exports.
|
|
327
|
+
value === exports.EXTERNAL_INBOUND ||
|
|
328
|
+
value === exports.EXTERNAL_OUTBOUND);
|
|
315
329
|
}
|
|
316
330
|
/**
|
|
317
|
-
* Fail-fast at wiring time
|
|
318
|
-
*
|
|
319
|
-
* a
|
|
320
|
-
*
|
|
331
|
+
* Fail-fast at wiring time on the two EXTERNAL_* rules, called by `RouteMetadataFactory` for every
|
|
332
|
+
* transport (server routes and every client). The {@link Endpoint} overloads already make a missing
|
|
333
|
+
* `calledBy` a COMPILE error; this is the backstop for the ways TS is bypassed — a JS caller, an
|
|
334
|
+
* `as any` options object, a hand-rolled Reflect.defineMetadata. The second rule cannot be a type:
|
|
335
|
+
* "every method of this class" spans several decorators.
|
|
336
|
+
*
|
|
337
|
+
* @throws Error naming the first EXTERNAL_INBOUND endpoint with no `calledBy`, or the first method of
|
|
338
|
+
* a contract that mixes EXTERNAL_OUTBOUND with any other kind.
|
|
321
339
|
*/
|
|
322
340
|
// webpieces-disable no-function-outside-class -- wiring-time assert, sibling of assertEveryEndpointHasAuthMode
|
|
323
|
-
function
|
|
341
|
+
function assertExternalEndpointKinds(apiClass) {
|
|
324
342
|
const kinds = getEndpointKinds(apiClass);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
343
|
+
const apiName = apiClass.name || 'Unknown';
|
|
344
|
+
const methods = Object.keys(kinds);
|
|
345
|
+
const outbound = methods.filter((methodName) => kinds[methodName] === exports.EXTERNAL_OUTBOUND);
|
|
346
|
+
if (outbound.length > 0 && outbound.length !== methods.length) {
|
|
347
|
+
const other = methods.find((methodName) => kinds[methodName] !== exports.EXTERNAL_OUTBOUND);
|
|
348
|
+
throw new Error(`${apiName} mixes EXTERNAL_OUTBOUND (${outbound[0]}) with ${String(kinds[other]).toUpperCase()} (${other}). ` +
|
|
349
|
+
`An EXTERNAL_OUTBOUND contract is an outside system's API that no server in this repo implements, so ` +
|
|
350
|
+
`EVERY method of it must be EXTERNAL_OUTBOUND; move the other methods to their own contract.`);
|
|
351
|
+
}
|
|
352
|
+
for (const methodName of methods) {
|
|
353
|
+
if (kinds[methodName] !== exports.EXTERNAL_INBOUND || (0, external_caller_1.getEndpointCaller)(apiClass, methodName) !== undefined)
|
|
328
354
|
continue;
|
|
329
|
-
throw new Error(`External endpoint '${methodName}' in ${
|
|
330
|
-
`posts to it: @Endpoint(POST, path, WRITE,
|
|
355
|
+
throw new Error(`External endpoint '${methodName}' in ${apiName} declares no caller. Say WHO ` +
|
|
356
|
+
`posts to it: @Endpoint(POST, path, WRITE, EXTERNAL_INBOUND, { calledBy: '<vendor>' }) — the runtime architecture ` +
|
|
331
357
|
`graph cannot name an inbound caller it was never told about.`);
|
|
332
358
|
}
|
|
333
359
|
}
|
|
360
|
+
/** True when the contract is an outside system's API: every method is EXTERNAL_OUTBOUND. */
|
|
361
|
+
// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of getEndpointKinds
|
|
362
|
+
function isExternalOutboundApi(apiClass) {
|
|
363
|
+
const kinds = Object.values(getEndpointKinds(apiClass));
|
|
364
|
+
return kinds.length > 0 && kinds.every((kind) => kind === exports.EXTERNAL_OUTBOUND);
|
|
365
|
+
}
|
|
334
366
|
/**
|
|
335
367
|
* True when the method's @Endpoint declared `{ formPost: true }` — its body is
|
|
336
368
|
* application/x-www-form-urlencoded (flat), not JSON.
|
|
@@ -341,33 +373,33 @@ function isFormPost(apiClass, methodName) {
|
|
|
341
373
|
}
|
|
342
374
|
/**
|
|
343
375
|
* True when the method's @Endpoint declared `{ rawBody: true }` — the transport must retain the
|
|
344
|
-
* verbatim bytes + absolute url for
|
|
376
|
+
* verbatim bytes + absolute url for a `VendorAuthHook.verify` to check.
|
|
345
377
|
*/
|
|
346
378
|
// webpieces-disable no-function-outside-class -- reflect-metadata reader, sibling of isFormPost
|
|
347
379
|
function isRawBody(apiClass, methodName) {
|
|
348
380
|
return getEndpointOptions(apiClass, methodName).rawBody === true;
|
|
349
381
|
}
|
|
350
382
|
/**
|
|
351
|
-
* Fail-fast at wiring time when
|
|
383
|
+
* Fail-fast at wiring time when a SERVED `vendorAuth(...)` endpoint did not ask the transport to keep the
|
|
352
384
|
* bytes it is supposed to verify. A hook with nothing to verify is a MISCONFIGURATION, and it must
|
|
353
385
|
* surface at startup, naming the fix — not as a 401 in production on exactly the traffic the endpoint
|
|
354
386
|
* exists for.
|
|
355
387
|
*
|
|
356
388
|
* This pairing is a runtime assert rather than a type because the two halves live on DIFFERENT
|
|
357
|
-
* decorators (`
|
|
389
|
+
* decorators (`vendorAuth(...)` and `@Endpoint`), and no union over one decorator's argument can say
|
|
358
390
|
* anything about the other's.
|
|
359
391
|
*
|
|
360
|
-
* @throws Error naming the first `
|
|
392
|
+
* @throws Error naming the first `vendorAuth(...)` endpoint missing `{ rawBody: true }`.
|
|
361
393
|
*/
|
|
362
394
|
// webpieces-disable no-function-outside-class -- wiring-time assert, sibling of assertEveryEndpointHasAuthMode
|
|
363
|
-
function
|
|
395
|
+
function assertEveryVendorAuthEndpointRetainsRawBody(apiClass) {
|
|
364
396
|
const endpoints = getEndpoints(apiClass) || {};
|
|
365
397
|
for (const methodName of Object.keys(endpoints)) {
|
|
366
|
-
if (!getAuthMeta(apiClass, methodName)?.methods.some((method) => method.kind === '
|
|
398
|
+
if (!getAuthMeta(apiClass, methodName)?.methods.some((method) => method.kind === 'vendorAuth') ||
|
|
367
399
|
isRawBody(apiClass, methodName))
|
|
368
400
|
continue;
|
|
369
|
-
throw new Error(`Endpoint '${methodName}' in ${apiClass.name || 'Unknown'} is
|
|
370
|
-
`does not declare { rawBody: true }.
|
|
401
|
+
throw new Error(`Endpoint '${methodName}' in ${apiClass.name || 'Unknown'} is vendorAuth(...) but its @Endpoint ` +
|
|
402
|
+
`does not declare { rawBody: true }. VendorAuthHook.verify checks a credential over the bytes and ` +
|
|
371
403
|
`the url the SENDER transmitted, and without that option the transport parses the body and ` +
|
|
372
404
|
`throws them away — leaving the hook nothing to check.`);
|
|
373
405
|
}
|
|
@@ -392,7 +424,7 @@ function getAuthMeta(apiClass, methodName) {
|
|
|
392
424
|
* the caller hits becomes the API they believe exists. It leads with the ROLE-GATED member on purpose —
|
|
393
425
|
* the first thing offered should not be the widest grant.
|
|
394
426
|
*/
|
|
395
|
-
exports.MISSING_AUTH_DECORATOR_FIX = "Declare @WpAuth([jwt(), oidcSameProject() or oidcSvcs(['svc-a']), sharedSecret(key),
|
|
427
|
+
exports.MISSING_AUTH_DECORATOR_FIX = "Declare @WpAuth([jwt(), oidcSameProject() or oidcSvcs(['svc-a']), sharedSecret(key), vendorAuth(name), apiKey('regime', [{in: 'header', key: MyKeys.API_KEY}])]) " +
|
|
396
428
|
"or @WpAuthPublic('reason'), and exactly one @WpAuthorization on every endpoint. Add @WpLocalOnly() separately when locality is required.";
|
|
397
429
|
/**
|
|
398
430
|
* Fail-fast at wiring time if any endpoint lacks an auth mode. Both the server
|