@webpieces/core-context 0.4.965 → 0.4.967

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/core-context",
3
- "version": "0.4.965",
3
+ "version": "0.4.967",
4
4
  "description": "AsyncLocalStorage-based context management for request-scoped data",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -22,7 +22,7 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@webpieces/core-util": "0.4.965",
25
+ "@webpieces/core-util": "0.4.967",
26
26
  "@inversifyjs/binding-decorators": "1.1.5",
27
27
  "inversify": "7.10.4",
28
28
  "reflect-metadata": "0.2.2"
@@ -79,15 +79,25 @@ export declare class SignableRequest {
79
79
  * itself inject a typed RPC client. Validating a credential server-side is the verifier's job, on
80
80
  * the server.
81
81
  *
82
- * ## It FAILS CLOSED
82
+ * ## It FAILS AT CLIENT CREATION
83
83
  *
84
- * With no minter bound, every outbound `vendorAuth(...)` call THROWS `MissingVendorAuthMinterError`
85
- * before anything is sent, rather than going out unauthenticated. There is no framework default and
86
- * there never will be one. OIDC, shared-secret and public calls never need it.
84
+ * `createRpcClient` refuses a contract with an endpoint whose only declared credential this client
85
+ * could send is `vendorAuth(name)` when no minter is bound, or the bound minter's {@link supportedVendorNames}
86
+ * does not list `name` — it throws `MissingClientCredentialError` naming the contract, the method and
87
+ * the binding, before any call is made (#1362). An endpoint that also declares a credential this
88
+ * client CAN send (OIDC, a held shared secret) needs no minter. There is no framework default and
89
+ * there never will be one.
87
90
  *
88
91
  * ```typescript
89
92
  * // the CLI / client process's BindModule
90
93
  * binder.bind(VENDOR_AUTH_MINTER).to(MyVendorAuthMinter).inSingletonScope();
94
+ *
95
+ * class MyVendorAuthMinter extends VendorAuthMinter {
96
+ * override supportedVendorNames(): readonly string[] {
97
+ * return ['clover']; // every name mint()/refresh() below handle
98
+ * }
99
+ * // mint(...) and refresh(...)
100
+ * }
91
101
  * ```
92
102
  *
93
103
  * ## Refresh-once, and who owns serializing it
@@ -100,6 +110,16 @@ export declare class SignableRequest {
100
110
  * credential source; it never authenticates through the call it is refreshing for.
101
111
  */
102
112
  export declare abstract class VendorAuthMinter {
113
+ /**
114
+ * Every vendor name {@link mint} and {@link refresh} handle — each `vendorAuth(name)` this process
115
+ * can call. A METHOD beside `mint` so the list sits next to the code that dispatches on those
116
+ * names: add a vendor to both in one edit. REQUIRED, no default.
117
+ *
118
+ * Called at CLIENT CREATION by `createRpcClient` (#1362), to refuse a contract it cannot call. It
119
+ * MUST therefore be PURE — return a fixed list, with no I/O, no network, no credential work and no
120
+ * side effect. {@link mint} and {@link refresh} are never called then.
121
+ */
122
+ abstract supportedVendorNames(): readonly string[];
103
123
  /**
104
124
  * Produce the headers that authenticate ONE outbound request, or throw to refuse to send it. An
105
125
  * empty map is legal and means "this vendor needs no header".
@@ -119,6 +139,6 @@ export declare abstract class VendorAuthMinter {
119
139
  * DI identifier for the optional {@link VendorAuthMinter} binding — the OUTBOUND role only. It is a
120
140
  * Symbol (not the class) so the app container's inversify autobind never auto-constructs this token,
121
141
  * keeping `@optional() @inject(VENDOR_AUTH_MINTER)` correct — undefined when unbound, which is what
122
- * makes the fail-closed refusal reachable.
142
+ * lets `createRpcClient` refuse a contract this process cannot call.
123
143
  */
124
144
  export declare const VENDOR_AUTH_MINTER: unique symbol;
@@ -79,15 +79,25 @@ exports.SignableRequest = SignableRequest;
79
79
  * itself inject a typed RPC client. Validating a credential server-side is the verifier's job, on
80
80
  * the server.
81
81
  *
82
- * ## It FAILS CLOSED
82
+ * ## It FAILS AT CLIENT CREATION
83
83
  *
84
- * With no minter bound, every outbound `vendorAuth(...)` call THROWS `MissingVendorAuthMinterError`
85
- * before anything is sent, rather than going out unauthenticated. There is no framework default and
86
- * there never will be one. OIDC, shared-secret and public calls never need it.
84
+ * `createRpcClient` refuses a contract with an endpoint whose only declared credential this client
85
+ * could send is `vendorAuth(name)` when no minter is bound, or the bound minter's {@link supportedVendorNames}
86
+ * does not list `name` — it throws `MissingClientCredentialError` naming the contract, the method and
87
+ * the binding, before any call is made (#1362). An endpoint that also declares a credential this
88
+ * client CAN send (OIDC, a held shared secret) needs no minter. There is no framework default and
89
+ * there never will be one.
87
90
  *
88
91
  * ```typescript
89
92
  * // the CLI / client process's BindModule
90
93
  * binder.bind(VENDOR_AUTH_MINTER).to(MyVendorAuthMinter).inSingletonScope();
94
+ *
95
+ * class MyVendorAuthMinter extends VendorAuthMinter {
96
+ * override supportedVendorNames(): readonly string[] {
97
+ * return ['clover']; // every name mint()/refresh() below handle
98
+ * }
99
+ * // mint(...) and refresh(...)
100
+ * }
91
101
  * ```
92
102
  *
93
103
  * ## Refresh-once, and who owns serializing it
@@ -106,7 +116,7 @@ exports.VendorAuthMinter = VendorAuthMinter;
106
116
  * DI identifier for the optional {@link VendorAuthMinter} binding — the OUTBOUND role only. It is a
107
117
  * Symbol (not the class) so the app container's inversify autobind never auto-constructs this token,
108
118
  * keeping `@optional() @inject(VENDOR_AUTH_MINTER)` correct — undefined when unbound, which is what
109
- * makes the fail-closed refusal reachable.
119
+ * lets `createRpcClient` refuse a contract this process cannot call.
110
120
  */
111
121
  // webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)
112
122
  exports.VENDOR_AUTH_MINTER = Symbol.for('VendorAuthMinter');
@@ -1 +1 @@
1
- {"version":3,"file":"VendorAuthMinter.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/VendorAuthMinter.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAa,eAAe;IAGJ;IAEA;IAEA;IAOA;IAEA;IAEA;IAjBpB;IACI,kEAAkE;IAClD,GAAW;IAC3B,oCAAoC;IACpB,UAAkB;IAClC,uEAAuE;IACvD,IAAwB;IACxC;;;;;OAKG;IACa,OAAoC;IACpD,+DAA+D;IAC/C,YAAoB;IACpC,wDAAwD;IACxC,UAAkB;QAflB,QAAG,GAAH,GAAG,CAAQ;QAEX,eAAU,GAAV,UAAU,CAAQ;QAElB,SAAI,GAAJ,IAAI,CAAoB;QAOxB,YAAO,GAAP,OAAO,CAA6B;QAEpC,iBAAY,GAAZ,YAAY,CAAQ;QAEpB,eAAU,GAAV,UAAU,CAAQ;IACnC,CAAC;CACP;AApBD,0CAoBC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAsB,gBAAgB;CAgBrC;AAhBD,4CAgBC;AAED;;;;;GAKG;AACH,mNAAmN;AACtM,QAAA,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["/**\n * ONE outbound request, as the thing being authenticated — what {@link VendorAuthMinter.mint}\n * receives. Data only.\n *\n * Every field a real vendor scheme needs is here, and the two that make it correct are {@link url}\n * and {@link body}:\n *\n * - {@link url} is the FINAL absolute URL, after every app filter has had its say. A signature\n * computed over the pre-filter URL authenticates a request nobody sent — and for a partner-webhook\n * client the pre-filter URL is the client's own service name, which is not a destination at all.\n * - {@link body} is the EXACT serialized bytes the transport will send, not the DTO. Serialization\n * happens before the filter chain runs and the transport sends this same string verbatim, so the\n * bytes signed and the bytes sent cannot differ. A library that re-serializes internally signs one\n * byte sequence and sends another, and the failure is silent.\n */\nexport class SignableRequest {\n constructor(\n /** The FINAL absolute URL this request is about to be sent to. */\n public readonly url: string,\n /** The HTTP method, e.g. 'POST'. */\n public readonly httpMethod: string,\n /** The EXACT serialized body, or undefined for a call with no body. */\n public readonly body: string | undefined,\n /**\n * The headers as they stand. READ-ONLY here: a scheme that signs existing headers (a\n * timestamp another filter set, a content-type) reads them, and the headers the minter\n * returns are merged by the caller — so there is exactly one place headers are added and it\n * is the return value.\n */\n public readonly headers: ReadonlyMap<string, string>,\n /** The API contract's class name, e.g. 'PartnerWebhookApi'. */\n public readonly contractName: string,\n /** The contract method being called, e.g. 'deliver'. */\n public readonly methodName: string,\n ) {}\n}\n\n/**\n * VendorAuthMinter - the OUTBOUND half of `vendorAuth(name)`: the app-bound mechanism that presents a\n * credential in a scheme the FRAMEWORK does not know when THIS process calls a vendor / partner.\n * `name` selects the vendor, so one minter serves every vendor this process calls.\n *\n * It is one of TWO independent, optional DI roles behind the one contract declaration\n * `vendorAuth(name)` — the direction is decided by whether this runtime is SENDING or RECEIVING:\n *\n * | | who presents the credential | who checks it | the role this process binds |\n * |---|---|---|---|\n * | WE call a vendor / partner | us | them | `VendorAuthMinter` ({@link mint}, {@link refresh}) |\n * | a vendor calls US | them | us | `VendorAuthVerifier` (`verify`) |\n *\n * ## Why two roles and not one hook (#1358)\n *\n * The outbound RPC client (`NodeProxyClient`) is constructed for EVERY typed client, including the\n * ordinary `oidcSameProject()` client an inbound verifier uses to look a credential up. When one\n * binding served both directions, every outbound proxy constructed the app's inbound verifier — and\n * a verifier that depends on an RPC proxy closed a construction cycle (`Circular dependency found`)\n * before the process could listen. The outbound client therefore depends on THIS role only, and the\n * server's `AuthFilter` on the verifier only, so the graph is acyclic whatever order the bindings\n * were registered in. A client-only process implements no meaningless `verify`, and a server-only\n * one no meaningless `mint` / `refresh`.\n *\n * The converse holds for THIS role: every RPC client depends on the minter, so a minter reads a\n * credential this process already holds (a paired worker token, an OAuth token store) and must not\n * itself inject a typed RPC client. Validating a credential server-side is the verifier's job, on\n * the server.\n *\n * ## It FAILS CLOSED\n *\n * With no minter bound, every outbound `vendorAuth(...)` call THROWS `MissingVendorAuthMinterError`\n * before anything is sent, rather than going out unauthenticated. There is no framework default and\n * there never will be one. OIDC, shared-secret and public calls never need it.\n *\n * ```typescript\n * // the CLI / client process's BindModule\n * binder.bind(VENDOR_AUTH_MINTER).to(MyVendorAuthMinter).inSingletonScope();\n * ```\n *\n * ## Refresh-once, and who owns serializing it\n *\n * The node client's `OutboundAuthFilter` calls {@link refresh} ONCE when the vendor answers a raw\n * `401`, mints again and re-sends ONCE (never for a streaming upload, whose body cannot be replayed).\n * A second 401 is a failure of THIS service. Serializing a refresh across instances — Clover's\n * refresh tokens are single-use across Cloud Run instances — is the minter's job: webpieces calls it\n * once per logical call and cannot see the other instances. A refresh renews from its OWN valid\n * credential source; it never authenticates through the call it is refreshing for.\n */\nexport abstract class VendorAuthMinter {\n /**\n * Produce the headers that authenticate ONE outbound request, or throw to refuse to send it. An\n * empty map is legal and means \"this vendor needs no header\".\n *\n * @param name the string on the contract's `vendorAuth(name)` — which vendor this is.\n * @param request the FINAL request: the absolute url and the exact bytes about to go on the wire.\n */\n abstract mint(name: string, request: SignableRequest): Promise<Map<string, string>>;\n\n /**\n * The vendor answered `401` to a credential {@link mint} produced. Renew whatever the credential\n * is minted from (refresh an OAuth token, log in again). Called at most ONCE per logical call;\n * {@link mint} runs again afterwards and the request is re-sent once.\n */\n abstract refresh(name: string): Promise<void>;\n}\n\n/**\n * DI identifier for the optional {@link VendorAuthMinter} binding — the OUTBOUND role only. It is a\n * Symbol (not the class) so the app container's inversify autobind never auto-constructs this token,\n * keeping `@optional() @inject(VENDOR_AUTH_MINTER)` correct — undefined when unbound, which is what\n * makes the fail-closed refusal reachable.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const VENDOR_AUTH_MINTER = Symbol.for('VendorAuthMinter');\n"]}
1
+ {"version":3,"file":"VendorAuthMinter.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/VendorAuthMinter.ts"],"names":[],"mappings":";;;AACA;;;;;;;;;;;;;;GAcG;AACH,MAAa,eAAe;IAGJ;IAEA;IAEA;IAOA;IAEA;IAEA;IAjBpB;IACI,kEAAkE;IAClD,GAAW;IAC3B,oCAAoC;IACpB,UAAkB;IAClC,uEAAuE;IACvD,IAAwB;IACxC;;;;;OAKG;IACa,OAAoC;IACpD,+DAA+D;IAC/C,YAAoB;IACpC,wDAAwD;IACxC,UAAkB;QAflB,QAAG,GAAH,GAAG,CAAQ;QAEX,eAAU,GAAV,UAAU,CAAQ;QAElB,SAAI,GAAJ,IAAI,CAAoB;QAOxB,YAAO,GAAP,OAAO,CAA6B;QAEpC,iBAAY,GAAZ,YAAY,CAAQ;QAEpB,eAAU,GAAV,UAAU,CAAQ;IACnC,CAAC;CACP;AApBD,0CAoBC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAsB,gBAAgB;CA2BrC;AA3BD,4CA2BC;AAED;;;;;GAKG;AACH,mNAAmN;AACtM,QAAA,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC","sourcesContent":["\n/**\n * ONE outbound request, as the thing being authenticated — what {@link VendorAuthMinter.mint}\n * receives. Data only.\n *\n * Every field a real vendor scheme needs is here, and the two that make it correct are {@link url}\n * and {@link body}:\n *\n * - {@link url} is the FINAL absolute URL, after every app filter has had its say. A signature\n * computed over the pre-filter URL authenticates a request nobody sent — and for a partner-webhook\n * client the pre-filter URL is the client's own service name, which is not a destination at all.\n * - {@link body} is the EXACT serialized bytes the transport will send, not the DTO. Serialization\n * happens before the filter chain runs and the transport sends this same string verbatim, so the\n * bytes signed and the bytes sent cannot differ. A library that re-serializes internally signs one\n * byte sequence and sends another, and the failure is silent.\n */\nexport class SignableRequest {\n constructor(\n /** The FINAL absolute URL this request is about to be sent to. */\n public readonly url: string,\n /** The HTTP method, e.g. 'POST'. */\n public readonly httpMethod: string,\n /** The EXACT serialized body, or undefined for a call with no body. */\n public readonly body: string | undefined,\n /**\n * The headers as they stand. READ-ONLY here: a scheme that signs existing headers (a\n * timestamp another filter set, a content-type) reads them, and the headers the minter\n * returns are merged by the caller — so there is exactly one place headers are added and it\n * is the return value.\n */\n public readonly headers: ReadonlyMap<string, string>,\n /** The API contract's class name, e.g. 'PartnerWebhookApi'. */\n public readonly contractName: string,\n /** The contract method being called, e.g. 'deliver'. */\n public readonly methodName: string,\n ) {}\n}\n\n/**\n * VendorAuthMinter - the OUTBOUND half of `vendorAuth(name)`: the app-bound mechanism that presents a\n * credential in a scheme the FRAMEWORK does not know when THIS process calls a vendor / partner.\n * `name` selects the vendor, so one minter serves every vendor this process calls.\n *\n * It is one of TWO independent, optional DI roles behind the one contract declaration\n * `vendorAuth(name)` — the direction is decided by whether this runtime is SENDING or RECEIVING:\n *\n * | | who presents the credential | who checks it | the role this process binds |\n * |---|---|---|---|\n * | WE call a vendor / partner | us | them | `VendorAuthMinter` ({@link mint}, {@link refresh}) |\n * | a vendor calls US | them | us | `VendorAuthVerifier` (`verify`) |\n *\n * ## Why two roles and not one hook (#1358)\n *\n * The outbound RPC client (`NodeProxyClient`) is constructed for EVERY typed client, including the\n * ordinary `oidcSameProject()` client an inbound verifier uses to look a credential up. When one\n * binding served both directions, every outbound proxy constructed the app's inbound verifier — and\n * a verifier that depends on an RPC proxy closed a construction cycle (`Circular dependency found`)\n * before the process could listen. The outbound client therefore depends on THIS role only, and the\n * server's `AuthFilter` on the verifier only, so the graph is acyclic whatever order the bindings\n * were registered in. A client-only process implements no meaningless `verify`, and a server-only\n * one no meaningless `mint` / `refresh`.\n *\n * The converse holds for THIS role: every RPC client depends on the minter, so a minter reads a\n * credential this process already holds (a paired worker token, an OAuth token store) and must not\n * itself inject a typed RPC client. Validating a credential server-side is the verifier's job, on\n * the server.\n *\n * ## It FAILS AT CLIENT CREATION\n *\n * `createRpcClient` refuses a contract with an endpoint whose only declared credential this client\n * could send is `vendorAuth(name)` when no minter is bound, or the bound minter's {@link supportedVendorNames}\n * does not list `name` — it throws `MissingClientCredentialError` naming the contract, the method and\n * the binding, before any call is made (#1362). An endpoint that also declares a credential this\n * client CAN send (OIDC, a held shared secret) needs no minter. There is no framework default and\n * there never will be one.\n *\n * ```typescript\n * // the CLI / client process's BindModule\n * binder.bind(VENDOR_AUTH_MINTER).to(MyVendorAuthMinter).inSingletonScope();\n *\n * class MyVendorAuthMinter extends VendorAuthMinter {\n * override supportedVendorNames(): readonly string[] {\n * return ['clover']; // every name mint()/refresh() below handle\n * }\n * // mint(...) and refresh(...)\n * }\n * ```\n *\n * ## Refresh-once, and who owns serializing it\n *\n * The node client's `OutboundAuthFilter` calls {@link refresh} ONCE when the vendor answers a raw\n * `401`, mints again and re-sends ONCE (never for a streaming upload, whose body cannot be replayed).\n * A second 401 is a failure of THIS service. Serializing a refresh across instances — Clover's\n * refresh tokens are single-use across Cloud Run instances — is the minter's job: webpieces calls it\n * once per logical call and cannot see the other instances. A refresh renews from its OWN valid\n * credential source; it never authenticates through the call it is refreshing for.\n */\nexport abstract class VendorAuthMinter {\n /**\n * Every vendor name {@link mint} and {@link refresh} handle — each `vendorAuth(name)` this process\n * can call. A METHOD beside `mint` so the list sits next to the code that dispatches on those\n * names: add a vendor to both in one edit. REQUIRED, no default.\n *\n * Called at CLIENT CREATION by `createRpcClient` (#1362), to refuse a contract it cannot call. It\n * MUST therefore be PURE — return a fixed list, with no I/O, no network, no credential work and no\n * side effect. {@link mint} and {@link refresh} are never called then.\n */\n abstract supportedVendorNames(): readonly string[];\n\n /**\n * Produce the headers that authenticate ONE outbound request, or throw to refuse to send it. An\n * empty map is legal and means \"this vendor needs no header\".\n *\n * @param name the string on the contract's `vendorAuth(name)` — which vendor this is.\n * @param request the FINAL request: the absolute url and the exact bytes about to go on the wire.\n */\n abstract mint(name: string, request: SignableRequest): Promise<Map<string, string>>;\n\n /**\n * The vendor answered `401` to a credential {@link mint} produced. Renew whatever the credential\n * is minted from (refresh an OAuth token, log in again). Called at most ONCE per logical call;\n * {@link mint} runs again afterwards and the request is re-sent once.\n */\n abstract refresh(name: string): Promise<void>;\n}\n\n/**\n * DI identifier for the optional {@link VendorAuthMinter} binding — the OUTBOUND role only. It is a\n * Symbol (not the class) so the app container's inversify autobind never auto-constructs this token,\n * keeping `@optional() @inject(VENDOR_AUTH_MINTER)` correct — undefined when unbound, which is what\n * lets `createRpcClient` refuse a contract this process cannot call.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const VENDOR_AUTH_MINTER = Symbol.for('VendorAuthMinter');\n"]}
@@ -21,6 +21,11 @@ export declare class VendorAuthRolesCompileAssertions {
21
21
  minterIsNotAVerifier(): void;
22
22
  /** A verifier-only class does not implement minting or refresh, so it cannot fill the outbound role. */
23
23
  verifierIsNotAMinter(): void;
24
+ /**
25
+ * Each role DECLARES the vendor names it handles (#1362): a required abstract method with no
26
+ * default, because startup and `createRpcClient` call it to refuse a route nothing can serve.
27
+ */
28
+ supportedVendorNamesIsRequired(): void;
24
29
  /** The two binding identities are distinct unique symbols: one cannot be passed as the other. */
25
30
  tokensAreNotInterchangeable(): void;
26
31
  }
@@ -8,6 +8,9 @@ const VendorAuthMinter_1 = require("./VendorAuthMinter");
8
8
  const VendorAuthVerifier_1 = require("./VendorAuthVerifier");
9
9
  /** A CLIENT-only process's minter: mint + refresh, and NOTHING meaningless beside them. */
10
10
  class MinterOnly extends VendorAuthMinter_1.VendorAuthMinter {
11
+ supportedVendorNames() {
12
+ return ['bugfixer-worker'];
13
+ }
11
14
  async mint(_name, _request) {
12
15
  return new Map([['Authorization', 'Bearer held-worker-token']]);
13
16
  }
@@ -17,6 +20,9 @@ class MinterOnly extends VendorAuthMinter_1.VendorAuthMinter {
17
20
  }
18
21
  /** A SERVER-only process's verifier: verify, and NOTHING meaningless beside it. */
19
22
  class VerifierOnly extends VendorAuthVerifier_1.VendorAuthVerifier {
23
+ supportedVendorNames() {
24
+ return ['bugfixer-sentry'];
25
+ }
20
26
  async verify(_name, request) {
21
27
  return new AuthenticatedCaller_1.AuthenticatedCaller(`vendor:${request.raw.rawBody.length}`);
22
28
  }
@@ -56,6 +62,9 @@ class VendorAuthRolesCompileAssertions {
56
62
  const verifier = new MinterOnly();
57
63
  void verifier;
58
64
  void class extends VendorAuthMinter_1.VendorAuthMinter {
65
+ supportedVendorNames() {
66
+ return ['x'];
67
+ }
59
68
  async mint() {
60
69
  return new Map();
61
70
  }
@@ -74,6 +83,9 @@ class VendorAuthRolesCompileAssertions {
74
83
  const minter = new VerifierOnly();
75
84
  void minter;
76
85
  void class extends VendorAuthVerifier_1.VendorAuthVerifier {
86
+ supportedVendorNames() {
87
+ return ['x'];
88
+ }
77
89
  async verify() {
78
90
  return new AuthenticatedCaller_1.AuthenticatedCaller('vendor');
79
91
  }
@@ -83,6 +95,34 @@ class VendorAuthRolesCompileAssertions {
83
95
  }
84
96
  };
85
97
  }
98
+ /**
99
+ * Each role DECLARES the vendor names it handles (#1362): a required abstract method with no
100
+ * default, because startup and `createRpcClient` call it to refuse a route nothing can serve.
101
+ */
102
+ supportedVendorNamesIsRequired() {
103
+ // @ts-expect-error a minter must implement supportedVendorNames() — there is no default
104
+ void class extends VendorAuthMinter_1.VendorAuthMinter {
105
+ async mint() {
106
+ return new Map();
107
+ }
108
+ async refresh() {
109
+ // renew
110
+ }
111
+ };
112
+ // @ts-expect-error a verifier must implement supportedVendorNames() — there is no default
113
+ void class extends VendorAuthVerifier_1.VendorAuthVerifier {
114
+ async verify() {
115
+ return new AuthenticatedCaller_1.AuthenticatedCaller('vendor');
116
+ }
117
+ };
118
+ void class extends VendorAuthVerifier_1.VendorAuthVerifier {
119
+ // @ts-expect-error it is a METHOD beside verify(), not a field
120
+ supportedVendorNames = ['x'];
121
+ async verify() {
122
+ return new AuthenticatedCaller_1.AuthenticatedCaller('vendor');
123
+ }
124
+ };
125
+ }
86
126
  /** The two binding identities are distinct unique symbols: one cannot be passed as the other. */
87
127
  tokensAreNotInterchangeable() {
88
128
  // @ts-expect-error the inbound token is not the outbound token
@@ -1 +1 @@
1
- {"version":3,"file":"VendorAuthRolesCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/VendorAuthRolesCompileAssertions.ts"],"names":[],"mappings":";;;;AAAA,+DAA4D;AAE5D,6DAAuC;AACvC,yDAA2F;AAC3F,6DAAgF;AAEhF,2FAA2F;AAC3F,MAAM,UAAW,SAAQ,mCAAgB;IAC5B,KAAK,CAAC,IAAI,CAAC,KAAa,EAAE,QAAyB;QACxD,OAAO,IAAI,GAAG,CAAC,CAAC,CAAC,eAAe,EAAE,0BAA0B,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC;IAEQ,KAAK,CAAC,OAAO,CAAC,KAAa;QAChC,kDAAkD;IACtD,CAAC;CACJ;AAED,mFAAmF;AACnF,MAAM,YAAa,SAAQ,uCAAkB;IAChC,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,OAAuB;QACxD,OAAO,IAAI,yCAAmB,CAAC,UAAU,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3E,CAAC;CACJ;AAED;;;;;;;;;;;;;GAaG;AACH,MAAa,gCAAgC;IACzC,uEAAuE;IACvE,oBAAoB;QAChB,qHAAqH;QACrH,KAAK,WAAW,CAAC,cAAc,CAAC;QAChC,uHAAuH;QACvH,KAAK,WAAW,CAAC,gBAAgB,CAAC;IACtC,CAAC;IAED,mGAAmG;IACnG,mBAAmB;QACf,MAAM,MAAM,GAAqB,IAAI,UAAU,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAuB,IAAI,YAAY,EAAE,CAAC;QACxD,KAAK,MAAM,CAAC;QACZ,KAAK,QAAQ,CAAC;IAClB,CAAC;IAED,+FAA+F;IAC/F,oBAAoB;QAChB,4EAA4E;QAC5E,MAAM,QAAQ,GAAuB,IAAI,UAAU,EAAE,CAAC;QACtD,KAAK,QAAQ,CAAC;QACd,KAAK,KAAM,SAAQ,mCAAgB;YACtB,KAAK,CAAC,IAAI;gBACf,OAAO,IAAI,GAAG,EAAE,CAAC;YACrB,CAAC;YAEQ,KAAK,CAAC,OAAO;gBAClB,QAAQ;YACZ,CAAC;YAED,iGAAiG;YACxF,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,QAAwB;gBACzD,OAAO,IAAI,yCAAmB,CAAC,OAAO,CAAC,CAAC;YAC5C,CAAC;SACJ,CAAC;IACN,CAAC;IAED,wGAAwG;IACxG,oBAAoB;QAChB,oFAAoF;QACpF,MAAM,MAAM,GAAqB,IAAI,YAAY,EAAE,CAAC;QACpD,KAAK,MAAM,CAAC;QACZ,KAAK,KAAM,SAAQ,uCAAkB;YACxB,KAAK,CAAC,MAAM;gBACjB,OAAO,IAAI,yCAAmB,CAAC,QAAQ,CAAC,CAAC;YAC7C,CAAC;YAED,8FAA8F;YACrF,KAAK,CAAC,IAAI;gBACf,OAAO,IAAI,GAAG,EAAE,CAAC;YACrB,CAAC;SACJ,CAAC;IACN,CAAC;IAED,iGAAiG;IACjG,2BAA2B;QACvB,+DAA+D;QAC/D,MAAM,QAAQ,GAA8B,yCAAoB,CAAC;QACjE,+DAA+D;QAC/D,MAAM,OAAO,GAAgC,qCAAkB,CAAC;QAChE,KAAK,QAAQ,CAAC;QACd,KAAK,OAAO,CAAC;IACjB,CAAC;CACJ;AAhED,4EAgEC","sourcesContent":["import { AuthenticatedCaller } from './AuthenticatedCaller';\nimport { RawHttpRequest } from './HttpRequest';\nimport * as CoreContext from './index';\nimport { SignableRequest, VendorAuthMinter, VENDOR_AUTH_MINTER } from './VendorAuthMinter';\nimport { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from './VendorAuthVerifier';\n\n/** A CLIENT-only process's minter: mint + refresh, and NOTHING meaningless beside them. */\nclass MinterOnly extends VendorAuthMinter {\n override async mint(_name: string, _request: SignableRequest): Promise<Map<string, string>> {\n return new Map([['Authorization', 'Bearer held-worker-token']]);\n }\n\n override async refresh(_name: string): Promise<void> {\n // renew from this process's own credential source\n }\n}\n\n/** A SERVER-only process's verifier: verify, and NOTHING meaningless beside it. */\nclass VerifierOnly extends VendorAuthVerifier {\n override async verify(_name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller(`vendor:${request.raw.rawBody.length}`);\n }\n}\n\n/**\n * COMPILE-TIME proof of the #1358 role split. `vendorAuth(name)` is served by TWO independent DI\n * roles — the outbound {@link VendorAuthMinter} and the inbound {@link VendorAuthVerifier} — and the\n * unified `VendorAuthHook` / `VENDOR_AUTH_HOOK` they replace is DELETED, not aliased. Each\n * `@ts-expect-error` FAILS THE BUILD (TS2578) the moment the thing it guards starts compiling again.\n *\n * A compiled file, never a `.spec.ts`: tsconfig.lib.json excludes specs and vitest strips types with\n * esbuild, so a `@ts-expect-error` in a spec is inert.\n *\n * Why the split must be a TYPE fact and not a convention: the unified hook forced a client-only\n * process to implement a meaningless `verify` and a server-only one a meaningless `mint`/`refresh`,\n * and — far worse — made every outbound RPC proxy construct the server's inbound verifier, which\n * closed a DI cycle the moment the verifier depended on an ordinary OIDC RPC client.\n */\nexport class VendorAuthRolesCompileAssertions {\n /** The unified hook is gone: no alias, no `@deprecated`, by policy. */\n unifiedHookIsDeleted(): void {\n // @ts-expect-error VendorAuthHook is DELETED — split into VendorAuthMinter (outbound) + VendorAuthVerifier (inbound)\n void CoreContext.VendorAuthHook;\n // @ts-expect-error VENDOR_AUTH_HOOK is DELETED — bind VENDOR_AUTH_MINTER (client) and/or VENDOR_AUTH_VERIFIER (server)\n void CoreContext.VENDOR_AUTH_HOOK;\n }\n\n /** Each role is implementable ALONE — asserted by the absence of an error on the classes above. */\n eachRoleStandsAlone(): void {\n const minter: VendorAuthMinter = new MinterOnly();\n const verifier: VendorAuthVerifier = new VerifierOnly();\n void minter;\n void verifier;\n }\n\n /** A minter-only class does not implement verification, so it cannot fill the inbound role. */\n minterIsNotAVerifier(): void {\n // @ts-expect-error a minter has no verify(): it is not a VendorAuthVerifier\n const verifier: VendorAuthVerifier = new MinterOnly();\n void verifier;\n void class extends VendorAuthMinter {\n override async mint(): Promise<Map<string, string>> {\n return new Map();\n }\n\n override async refresh(): Promise<void> {\n // renew\n }\n\n // @ts-expect-error verify is not a member of the outbound role, so it cannot be overridden there\n override async verify(_name: string, _request: RawHttpRequest): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('never');\n }\n };\n }\n\n /** A verifier-only class does not implement minting or refresh, so it cannot fill the outbound role. */\n verifierIsNotAMinter(): void {\n // @ts-expect-error a verifier has no mint()/refresh(): it is not a VendorAuthMinter\n const minter: VendorAuthMinter = new VerifierOnly();\n void minter;\n void class extends VendorAuthVerifier {\n override async verify(): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('vendor');\n }\n\n // @ts-expect-error mint is not a member of the inbound role, so it cannot be overridden there\n override async mint(): Promise<Map<string, string>> {\n return new Map();\n }\n };\n }\n\n /** The two binding identities are distinct unique symbols: one cannot be passed as the other. */\n tokensAreNotInterchangeable(): void {\n // @ts-expect-error the inbound token is not the outbound token\n const outbound: typeof VENDOR_AUTH_MINTER = VENDOR_AUTH_VERIFIER;\n // @ts-expect-error the outbound token is not the inbound token\n const inbound: typeof VENDOR_AUTH_VERIFIER = VENDOR_AUTH_MINTER;\n void outbound;\n void inbound;\n }\n}\n"]}
1
+ {"version":3,"file":"VendorAuthRolesCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/VendorAuthRolesCompileAssertions.ts"],"names":[],"mappings":"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","sourcesContent":["import { AuthenticatedCaller } from './AuthenticatedCaller';\nimport { RawHttpRequest } from './HttpRequest';\nimport * as CoreContext from './index';\nimport { SignableRequest, VendorAuthMinter, VENDOR_AUTH_MINTER } from './VendorAuthMinter';\nimport { VendorAuthVerifier, VENDOR_AUTH_VERIFIER } from './VendorAuthVerifier';\n\n/** A CLIENT-only process's minter: mint + refresh, and NOTHING meaningless beside them. */\nclass MinterOnly extends VendorAuthMinter {\n override supportedVendorNames(): readonly string[] {\n return ['bugfixer-worker'];\n }\n\n override async mint(_name: string, _request: SignableRequest): Promise<Map<string, string>> {\n return new Map([['Authorization', 'Bearer held-worker-token']]);\n }\n\n override async refresh(_name: string): Promise<void> {\n // renew from this process's own credential source\n }\n}\n\n/** A SERVER-only process's verifier: verify, and NOTHING meaningless beside it. */\nclass VerifierOnly extends VendorAuthVerifier {\n override supportedVendorNames(): readonly string[] {\n return ['bugfixer-sentry'];\n }\n\n override async verify(_name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller(`vendor:${request.raw.rawBody.length}`);\n }\n}\n\n/**\n * COMPILE-TIME proof of the #1358 role split. `vendorAuth(name)` is served by TWO independent DI\n * roles — the outbound {@link VendorAuthMinter} and the inbound {@link VendorAuthVerifier} — and the\n * unified `VendorAuthHook` / `VENDOR_AUTH_HOOK` they replace is DELETED, not aliased. Each\n * `@ts-expect-error` FAILS THE BUILD (TS2578) the moment the thing it guards starts compiling again.\n *\n * A compiled file, never a `.spec.ts`: tsconfig.lib.json excludes specs and vitest strips types with\n * esbuild, so a `@ts-expect-error` in a spec is inert.\n *\n * Why the split must be a TYPE fact and not a convention: the unified hook forced a client-only\n * process to implement a meaningless `verify` and a server-only one a meaningless `mint`/`refresh`,\n * and — far worse — made every outbound RPC proxy construct the server's inbound verifier, which\n * closed a DI cycle the moment the verifier depended on an ordinary OIDC RPC client.\n */\nexport class VendorAuthRolesCompileAssertions {\n /** The unified hook is gone: no alias, no `@deprecated`, by policy. */\n unifiedHookIsDeleted(): void {\n // @ts-expect-error VendorAuthHook is DELETED — split into VendorAuthMinter (outbound) + VendorAuthVerifier (inbound)\n void CoreContext.VendorAuthHook;\n // @ts-expect-error VENDOR_AUTH_HOOK is DELETED — bind VENDOR_AUTH_MINTER (client) and/or VENDOR_AUTH_VERIFIER (server)\n void CoreContext.VENDOR_AUTH_HOOK;\n }\n\n /** Each role is implementable ALONE — asserted by the absence of an error on the classes above. */\n eachRoleStandsAlone(): void {\n const minter: VendorAuthMinter = new MinterOnly();\n const verifier: VendorAuthVerifier = new VerifierOnly();\n void minter;\n void verifier;\n }\n\n /** A minter-only class does not implement verification, so it cannot fill the inbound role. */\n minterIsNotAVerifier(): void {\n // @ts-expect-error a minter has no verify(): it is not a VendorAuthVerifier\n const verifier: VendorAuthVerifier = new MinterOnly();\n void verifier;\n void class extends VendorAuthMinter {\n override supportedVendorNames(): readonly string[] {\n return ['x'];\n }\n\n override async mint(): Promise<Map<string, string>> {\n return new Map();\n }\n\n override async refresh(): Promise<void> {\n // renew\n }\n\n // @ts-expect-error verify is not a member of the outbound role, so it cannot be overridden there\n override async verify(_name: string, _request: RawHttpRequest): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('never');\n }\n };\n }\n\n /** A verifier-only class does not implement minting or refresh, so it cannot fill the outbound role. */\n verifierIsNotAMinter(): void {\n // @ts-expect-error a verifier has no mint()/refresh(): it is not a VendorAuthMinter\n const minter: VendorAuthMinter = new VerifierOnly();\n void minter;\n void class extends VendorAuthVerifier {\n override supportedVendorNames(): readonly string[] {\n return ['x'];\n }\n\n override async verify(): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('vendor');\n }\n\n // @ts-expect-error mint is not a member of the inbound role, so it cannot be overridden there\n override async mint(): Promise<Map<string, string>> {\n return new Map();\n }\n };\n }\n\n /**\n * Each role DECLARES the vendor names it handles (#1362): a required abstract method with no\n * default, because startup and `createRpcClient` call it to refuse a route nothing can serve.\n */\n supportedVendorNamesIsRequired(): void {\n // @ts-expect-error a minter must implement supportedVendorNames() — there is no default\n void class extends VendorAuthMinter {\n override async mint(): Promise<Map<string, string>> {\n return new Map();\n }\n\n override async refresh(): Promise<void> {\n // renew\n }\n };\n // @ts-expect-error a verifier must implement supportedVendorNames() — there is no default\n void class extends VendorAuthVerifier {\n override async verify(): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('vendor');\n }\n };\n void class extends VendorAuthVerifier {\n // @ts-expect-error it is a METHOD beside verify(), not a field\n override readonly supportedVendorNames: readonly string[] = ['x'];\n\n override async verify(): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller('vendor');\n }\n };\n }\n\n /** The two binding identities are distinct unique symbols: one cannot be passed as the other. */\n tokensAreNotInterchangeable(): void {\n // @ts-expect-error the inbound token is not the outbound token\n const outbound: typeof VENDOR_AUTH_MINTER = VENDOR_AUTH_VERIFIER;\n // @ts-expect-error the outbound token is not the inbound token\n const inbound: typeof VENDOR_AUTH_VERIFIER = VENDOR_AUTH_MINTER;\n void outbound;\n void inbound;\n }\n}\n"]}
@@ -11,17 +11,36 @@ import { RawHttpRequest } from './HttpRequest';
11
11
  * `oidcSameProject()` RPC client (to look a credential or a signing secret up) without closing a
12
12
  * construction cycle (#1358).
13
13
  *
14
- * ## It FAILS CLOSED
14
+ * ## It FAILS AT STARTUP
15
15
  *
16
- * With no verifier bound, every inbound `vendorAuth(...)` endpoint answers 401 before the controller
17
- * runs. There is no framework default and there never will be one — never an allow-all.
16
+ * A server that SERVES a `vendorAuth(name)` endpoint with no verifier bound, or with a verifier whose
17
+ * {@link supportedVendorNames} does not list `name`, refuses to boot: route registration names the endpoint,
18
+ * the vendor and the binding line (#1362). There is no framework default and there never will be
19
+ * one — never an allow-all.
18
20
  *
19
21
  * ```typescript
20
22
  * // the API server's BindModule
21
23
  * binder.bind(VENDOR_AUTH_VERIFIER).to(MyVendorAuthVerifier).inSingletonScope();
24
+ *
25
+ * class MyVendorAuthVerifier extends VendorAuthVerifier {
26
+ * override supportedVendorNames(): readonly string[] {
27
+ * return ['bugfixer-sentry']; // every name verify() below handles
28
+ * }
29
+ * override async verify(name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> { ... }
30
+ * }
22
31
  * ```
23
32
  */
24
33
  export declare abstract class VendorAuthVerifier {
34
+ /**
35
+ * Every vendor name {@link verify} handles — each `vendorAuth(name)` this server can verify. A
36
+ * METHOD beside `verify` so the list sits next to the code that dispatches on those names: add a
37
+ * vendor to both in one edit. REQUIRED, no default.
38
+ *
39
+ * Called at STARTUP by route registration (#1362): a served `vendorAuth(name)` route whose name
40
+ * is not listed fails boot. It MUST therefore be PURE — return a fixed list, with no I/O, no
41
+ * network, no credential work and no side effect. {@link verify} is never called at boot.
42
+ */
43
+ abstract supportedVendorNames(): readonly string[];
25
44
  /**
26
45
  * Verify ONE inbound request. Return the {@link AuthenticatedCaller} the vendor's credential
27
46
  * proved; throw `ApiUnauthorizedError` to deny. The framework seeds `entries` with
@@ -39,6 +58,6 @@ export declare abstract class VendorAuthVerifier {
39
58
  /**
40
59
  * DI identifier for the optional {@link VendorAuthVerifier} binding — the INBOUND role only. A Symbol
41
60
  * for the same reason as `VENDOR_AUTH_MINTER`: autobind never constructs it, so an unbound verifier
42
- * is `undefined` and the fail-closed 401 is reachable.
61
+ * is `undefined` and route registration can refuse a served `vendorAuth(...)` endpoint at startup.
43
62
  */
44
63
  export declare const VENDOR_AUTH_VERIFIER: unique symbol;
@@ -12,14 +12,23 @@ exports.VENDOR_AUTH_VERIFIER = exports.VendorAuthVerifier = void 0;
12
12
  * `oidcSameProject()` RPC client (to look a credential or a signing secret up) without closing a
13
13
  * construction cycle (#1358).
14
14
  *
15
- * ## It FAILS CLOSED
15
+ * ## It FAILS AT STARTUP
16
16
  *
17
- * With no verifier bound, every inbound `vendorAuth(...)` endpoint answers 401 before the controller
18
- * runs. There is no framework default and there never will be one — never an allow-all.
17
+ * A server that SERVES a `vendorAuth(name)` endpoint with no verifier bound, or with a verifier whose
18
+ * {@link supportedVendorNames} does not list `name`, refuses to boot: route registration names the endpoint,
19
+ * the vendor and the binding line (#1362). There is no framework default and there never will be
20
+ * one — never an allow-all.
19
21
  *
20
22
  * ```typescript
21
23
  * // the API server's BindModule
22
24
  * binder.bind(VENDOR_AUTH_VERIFIER).to(MyVendorAuthVerifier).inSingletonScope();
25
+ *
26
+ * class MyVendorAuthVerifier extends VendorAuthVerifier {
27
+ * override supportedVendorNames(): readonly string[] {
28
+ * return ['bugfixer-sentry']; // every name verify() below handles
29
+ * }
30
+ * override async verify(name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> { ... }
31
+ * }
23
32
  * ```
24
33
  */
25
34
  class VendorAuthVerifier {
@@ -28,7 +37,7 @@ exports.VendorAuthVerifier = VendorAuthVerifier;
28
37
  /**
29
38
  * DI identifier for the optional {@link VendorAuthVerifier} binding — the INBOUND role only. A Symbol
30
39
  * for the same reason as `VENDOR_AUTH_MINTER`: autobind never constructs it, so an unbound verifier
31
- * is `undefined` and the fail-closed 401 is reachable.
40
+ * is `undefined` and route registration can refuse a served `vendorAuth(...)` endpoint at startup.
32
41
  */
33
42
  // webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)
34
43
  exports.VENDOR_AUTH_VERIFIER = Symbol.for('VendorAuthVerifier');
@@ -1 +1 @@
1
- {"version":3,"file":"VendorAuthVerifier.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/VendorAuthVerifier.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAsB,kBAAkB;CAcvC;AAdD,gDAcC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["import { AuthenticatedCaller } from './AuthenticatedCaller';\nimport { RawHttpRequest } from './HttpRequest';\n\n/**\n * VendorAuthVerifier - the INBOUND half of `vendorAuth(name)`: the app-bound mechanism that checks a\n * credential in a scheme the FRAMEWORK does not know when a vendor / partner calls THIS process.\n * `name` selects the vendor, so one verifier serves every vendor this server receives.\n *\n * It is one of TWO independent, optional DI roles behind the one contract declaration\n * `vendorAuth(name)`; its outbound twin is `VendorAuthMinter`. Only the server's `AuthFilter`\n * depends on this role. No outbound RPC client does — so a verifier may itself depend on an ordinary\n * `oidcSameProject()` RPC client (to look a credential or a signing secret up) without closing a\n * construction cycle (#1358).\n *\n * ## It FAILS CLOSED\n *\n * With no verifier bound, every inbound `vendorAuth(...)` endpoint answers 401 before the controller\n * runs. There is no framework default and there never will be one — never an allow-all.\n *\n * ```typescript\n * // the API server's BindModule\n * binder.bind(VENDOR_AUTH_VERIFIER).to(MyVendorAuthVerifier).inSingletonScope();\n * ```\n */\nexport abstract class VendorAuthVerifier {\n /**\n * Verify ONE inbound request. Return the {@link AuthenticatedCaller} the vendor's credential\n * proved; throw `ApiUnauthorizedError` to deny. The framework seeds `entries` with\n * `RequestContext.putTrusted`, as it does for a jwt or api-key caller. `vendorAuth` stays\n * caller-NOT-verified for trusted-header purposes: a vendor is not a peer service, so nothing it\n * merely ASSERTS on the wire is admitted.\n *\n * @param name the string on the contract's `vendorAuth(name)` — which vendor this route is.\n * @param request narrowed to {@link RawHttpRequest}: `request.raw` (the verbatim bytes + absolute\n * url) is PRESENT, because a served `vendorAuth(...)` endpoint requires\n * `@Endpoint(..., { rawBody: true })` at wiring time.\n */\n abstract verify(name: string, request: RawHttpRequest): Promise<AuthenticatedCaller>;\n}\n\n/**\n * DI identifier for the optional {@link VendorAuthVerifier} binding — the INBOUND role only. A Symbol\n * for the same reason as `VENDOR_AUTH_MINTER`: autobind never constructs it, so an unbound verifier\n * is `undefined` and the fail-closed 401 is reachable.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const VENDOR_AUTH_VERIFIER = Symbol.for('VendorAuthVerifier');\n"]}
1
+ {"version":3,"file":"VendorAuthVerifier.js","sourceRoot":"","sources":["../../../../../packages/core/core-context/src/VendorAuthVerifier.ts"],"names":[],"mappings":";;;AAGA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAsB,kBAAkB;CAyBvC;AAzBD,gDAyBC;AAED;;;;GAIG;AACH,mNAAmN;AACtM,QAAA,oBAAoB,GAAG,MAAM,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["import { AuthenticatedCaller } from './AuthenticatedCaller';\nimport { RawHttpRequest } from './HttpRequest';\n\n/**\n * VendorAuthVerifier - the INBOUND half of `vendorAuth(name)`: the app-bound mechanism that checks a\n * credential in a scheme the FRAMEWORK does not know when a vendor / partner calls THIS process.\n * `name` selects the vendor, so one verifier serves every vendor this server receives.\n *\n * It is one of TWO independent, optional DI roles behind the one contract declaration\n * `vendorAuth(name)`; its outbound twin is `VendorAuthMinter`. Only the server's `AuthFilter`\n * depends on this role. No outbound RPC client does — so a verifier may itself depend on an ordinary\n * `oidcSameProject()` RPC client (to look a credential or a signing secret up) without closing a\n * construction cycle (#1358).\n *\n * ## It FAILS AT STARTUP\n *\n * A server that SERVES a `vendorAuth(name)` endpoint with no verifier bound, or with a verifier whose\n * {@link supportedVendorNames} does not list `name`, refuses to boot: route registration names the endpoint,\n * the vendor and the binding line (#1362). There is no framework default and there never will be\n * one — never an allow-all.\n *\n * ```typescript\n * // the API server's BindModule\n * binder.bind(VENDOR_AUTH_VERIFIER).to(MyVendorAuthVerifier).inSingletonScope();\n *\n * class MyVendorAuthVerifier extends VendorAuthVerifier {\n * override supportedVendorNames(): readonly string[] {\n * return ['bugfixer-sentry']; // every name verify() below handles\n * }\n * override async verify(name: string, request: RawHttpRequest): Promise<AuthenticatedCaller> { ... }\n * }\n * ```\n */\nexport abstract class VendorAuthVerifier {\n /**\n * Every vendor name {@link verify} handles — each `vendorAuth(name)` this server can verify. A\n * METHOD beside `verify` so the list sits next to the code that dispatches on those names: add a\n * vendor to both in one edit. REQUIRED, no default.\n *\n * Called at STARTUP by route registration (#1362): a served `vendorAuth(name)` route whose name\n * is not listed fails boot. It MUST therefore be PURE — return a fixed list, with no I/O, no\n * network, no credential work and no side effect. {@link verify} is never called at boot.\n */\n abstract supportedVendorNames(): readonly string[];\n\n /**\n * Verify ONE inbound request. Return the {@link AuthenticatedCaller} the vendor's credential\n * proved; throw `ApiUnauthorizedError` to deny. The framework seeds `entries` with\n * `RequestContext.putTrusted`, as it does for a jwt or api-key caller. `vendorAuth` stays\n * caller-NOT-verified for trusted-header purposes: a vendor is not a peer service, so nothing it\n * merely ASSERTS on the wire is admitted.\n *\n * @param name the string on the contract's `vendorAuth(name)` — which vendor this route is.\n * @param request narrowed to {@link RawHttpRequest}: `request.raw` (the verbatim bytes + absolute\n * url) is PRESENT, because a served `vendorAuth(...)` endpoint requires\n * `@Endpoint(..., { rawBody: true })` at wiring time.\n */\n abstract verify(name: string, request: RawHttpRequest): Promise<AuthenticatedCaller>;\n}\n\n/**\n * DI identifier for the optional {@link VendorAuthVerifier} binding — the INBOUND role only. A Symbol\n * for the same reason as `VENDOR_AUTH_MINTER`: autobind never constructs it, so an unbound verifier\n * is `undefined` and route registration can refuse a served `vendorAuth(...)` endpoint at startup.\n */\n// webpieces-disable no-symbol-di-tokens -- optional DI token: must be a Symbol so the app container's autobind never auto-constructs this token, keeping @optional() @inject(...) correct (undefined when unbound)\nexport const VENDOR_AUTH_VERIFIER = Symbol.for('VendorAuthVerifier');\n"]}