@astram/guard 0.2.0 → 0.3.0

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.
Files changed (170) hide show
  1. package/dist/auth/account.controller.d.ts +245 -0
  2. package/dist/auth/account.controller.d.ts.map +1 -0
  3. package/dist/auth/account.controller.js +673 -0
  4. package/dist/auth/account.controller.js.map +1 -0
  5. package/dist/auth/auth.controller.d.ts +219 -5
  6. package/dist/auth/auth.controller.d.ts.map +1 -1
  7. package/dist/auth/auth.controller.js +790 -12
  8. package/dist/auth/auth.controller.js.map +1 -0
  9. package/dist/auth/auth.module.d.ts +100 -2
  10. package/dist/auth/auth.module.d.ts.map +1 -1
  11. package/dist/auth/auth.module.js +299 -7
  12. package/dist/auth/auth.module.js.map +1 -0
  13. package/dist/auth/connections.js.map +1 -0
  14. package/dist/auth/dto.d.ts +16 -6
  15. package/dist/auth/dto.d.ts.map +1 -1
  16. package/dist/auth/dto.js.map +1 -0
  17. package/dist/auth/pending-mfa-store.js.map +1 -0
  18. package/dist/auth/persistence.module.js.map +1 -0
  19. package/dist/auth/ports/audit-sink.d.ts +47 -0
  20. package/dist/auth/ports/audit-sink.d.ts.map +1 -0
  21. package/dist/auth/ports/audit-sink.js +20 -0
  22. package/dist/auth/ports/audit-sink.js.map +1 -0
  23. package/dist/auth/ports/credential-writer.d.ts +23 -0
  24. package/dist/auth/ports/credential-writer.d.ts.map +1 -0
  25. package/dist/auth/ports/credential-writer.js +2 -0
  26. package/dist/auth/ports/credential-writer.js.map +1 -0
  27. package/dist/auth/ports/email-sender.d.ts +14 -0
  28. package/dist/auth/ports/email-sender.d.ts.map +1 -0
  29. package/dist/auth/ports/email-sender.js +2 -0
  30. package/dist/auth/ports/email-sender.js.map +1 -0
  31. package/dist/auth/ports/mfa-device-store.d.ts +45 -0
  32. package/dist/auth/ports/mfa-device-store.d.ts.map +1 -0
  33. package/dist/auth/ports/mfa-device-store.js +13 -0
  34. package/dist/auth/ports/mfa-device-store.js.map +1 -0
  35. package/dist/auth/ports/profile-writer.d.ts +12 -0
  36. package/dist/auth/ports/profile-writer.d.ts.map +1 -0
  37. package/dist/auth/ports/profile-writer.js +2 -0
  38. package/dist/auth/ports/profile-writer.js.map +1 -0
  39. package/dist/auth/ports/recovery-code-store.d.ts +10 -0
  40. package/dist/auth/ports/recovery-code-store.d.ts.map +1 -0
  41. package/dist/auth/ports/recovery-code-store.js +2 -0
  42. package/dist/auth/ports/recovery-code-store.js.map +1 -0
  43. package/dist/auth/ports/reset-token-store.d.ts +12 -0
  44. package/dist/auth/ports/reset-token-store.d.ts.map +1 -0
  45. package/dist/auth/ports/reset-token-store.js +2 -0
  46. package/dist/auth/ports/reset-token-store.js.map +1 -0
  47. package/dist/auth/ports/session-facts-projector.d.ts +44 -0
  48. package/dist/auth/ports/session-facts-projector.d.ts.map +1 -0
  49. package/dist/auth/ports/session-facts-projector.js +30 -0
  50. package/dist/auth/ports/session-facts-projector.js.map +1 -0
  51. package/dist/auth/ports/session-sweeper.d.ts +12 -0
  52. package/dist/auth/ports/session-sweeper.d.ts.map +1 -0
  53. package/dist/auth/ports/session-sweeper.js +2 -0
  54. package/dist/auth/ports/session-sweeper.js.map +1 -0
  55. package/dist/auth/profile-resolver.d.ts +73 -0
  56. package/dist/auth/profile-resolver.d.ts.map +1 -0
  57. package/dist/auth/profile-resolver.js +28 -0
  58. package/dist/auth/profile-resolver.js.map +1 -0
  59. package/dist/auth/rate-limit/attempt-limiter.d.ts +91 -0
  60. package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -0
  61. package/dist/auth/rate-limit/attempt-limiter.js +133 -0
  62. package/dist/auth/rate-limit/attempt-limiter.js.map +1 -0
  63. package/dist/auth/tokens.d.ts +174 -0
  64. package/dist/auth/tokens.d.ts.map +1 -1
  65. package/dist/auth/tokens.js +190 -1
  66. package/dist/auth/tokens.js.map +1 -0
  67. package/dist/auth/totp-enrollment.d.ts +42 -0
  68. package/dist/auth/totp-enrollment.d.ts.map +1 -0
  69. package/dist/auth/totp-enrollment.js +96 -0
  70. package/dist/auth/totp-enrollment.js.map +1 -0
  71. package/dist/authentication/argon2id-password-hasher.js.map +1 -0
  72. package/dist/authentication/mariadb-credential-resolver.d.ts +38 -24
  73. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  74. package/dist/authentication/mariadb-credential-resolver.js +42 -28
  75. package/dist/authentication/mariadb-credential-resolver.js.map +1 -0
  76. package/dist/authz/cross-tenant-grant-writer.d.ts +259 -0
  77. package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -0
  78. package/dist/authz/cross-tenant-grant-writer.js +397 -0
  79. package/dist/authz/cross-tenant-grant-writer.js.map +1 -0
  80. package/dist/authz/manifest-operation-authority-adapter.d.ts +65 -0
  81. package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -0
  82. package/dist/authz/manifest-operation-authority-adapter.js +108 -0
  83. package/dist/authz/manifest-operation-authority-adapter.js.map +1 -0
  84. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts +90 -0
  85. package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -0
  86. package/dist/authz/mariadb-cross-tenant-grant-adapter.js +211 -0
  87. package/dist/authz/mariadb-cross-tenant-grant-adapter.js.map +1 -0
  88. package/dist/authz/mariadb-record-rule-adapter.d.ts +60 -0
  89. package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -0
  90. package/dist/authz/mariadb-record-rule-adapter.js +154 -0
  91. package/dist/authz/mariadb-record-rule-adapter.js.map +1 -0
  92. package/dist/authz/mariadb-record-share-adapter.d.ts +75 -0
  93. package/dist/authz/mariadb-record-share-adapter.d.ts.map +1 -0
  94. package/dist/authz/mariadb-record-share-adapter.js +106 -0
  95. package/dist/authz/mariadb-record-share-adapter.js.map +1 -0
  96. package/dist/authz/record-share-cascade.d.ts +52 -0
  97. package/dist/authz/record-share-cascade.d.ts.map +1 -0
  98. package/dist/authz/record-share-cascade.js +49 -0
  99. package/dist/authz/record-share-cascade.js.map +1 -0
  100. package/dist/authz/record-share-writer.d.ts +175 -0
  101. package/dist/authz/record-share-writer.d.ts.map +1 -0
  102. package/dist/authz/record-share-writer.js +225 -0
  103. package/dist/authz/record-share-writer.js.map +1 -0
  104. package/dist/authz/wire-record-authorization.d.ts +66 -0
  105. package/dist/authz/wire-record-authorization.d.ts.map +1 -0
  106. package/dist/authz/wire-record-authorization.js +34 -0
  107. package/dist/authz/wire-record-authorization.js.map +1 -0
  108. package/dist/boot/boot-sequence.js.map +1 -0
  109. package/dist/boot/boot.module.js.map +1 -0
  110. package/dist/completeness/catalog-reconciler.d.ts +21 -0
  111. package/dist/completeness/catalog-reconciler.d.ts.map +1 -0
  112. package/dist/completeness/catalog-reconciler.js +74 -0
  113. package/dist/completeness/catalog-reconciler.js.map +1 -0
  114. package/dist/completeness/operation-inventory-scanner.d.ts +17 -0
  115. package/dist/completeness/operation-inventory-scanner.d.ts.map +1 -0
  116. package/dist/completeness/operation-inventory-scanner.js +79 -0
  117. package/dist/completeness/operation-inventory-scanner.js.map +1 -0
  118. package/dist/completeness/ports/catalog-store.d.ts +33 -0
  119. package/dist/completeness/ports/catalog-store.d.ts.map +1 -0
  120. package/dist/completeness/ports/catalog-store.js +2 -0
  121. package/dist/completeness/ports/catalog-store.js.map +1 -0
  122. package/dist/config/app-config.js.map +1 -0
  123. package/dist/config/config.module.js.map +1 -0
  124. package/dist/enforcement/classification.decorator.d.ts +33 -14
  125. package/dist/enforcement/classification.decorator.d.ts.map +1 -1
  126. package/dist/enforcement/classification.decorator.js +34 -14
  127. package/dist/enforcement/classification.decorator.js.map +1 -0
  128. package/dist/enforcement/core-seam.js.map +1 -0
  129. package/dist/enforcement/enforcement.module.js.map +1 -0
  130. package/dist/enforcement/global-enforcement.guard.d.ts +29 -19
  131. package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
  132. package/dist/enforcement/global-enforcement.guard.js +108 -25
  133. package/dist/enforcement/global-enforcement.guard.js.map +1 -0
  134. package/dist/enforcement/reenroll-allowlist.d.ts +49 -0
  135. package/dist/enforcement/reenroll-allowlist.d.ts.map +1 -0
  136. package/dist/enforcement/reenroll-allowlist.js +77 -0
  137. package/dist/enforcement/reenroll-allowlist.js.map +1 -0
  138. package/dist/index.d.ts +35 -3
  139. package/dist/index.d.ts.map +1 -1
  140. package/dist/index.js +159 -7
  141. package/dist/index.js.map +1 -0
  142. package/dist/module/shield-tokens.d.ts +15 -0
  143. package/dist/module/shield-tokens.d.ts.map +1 -0
  144. package/dist/module/shield-tokens.js +17 -0
  145. package/dist/module/shield-tokens.js.map +1 -0
  146. package/dist/module/shield.module.d.ts +153 -9
  147. package/dist/module/shield.module.d.ts.map +1 -1
  148. package/dist/module/shield.module.js +181 -24
  149. package/dist/module/shield.module.js.map +1 -0
  150. package/dist/request-auth/request-principal.d.ts +42 -0
  151. package/dist/request-auth/request-principal.d.ts.map +1 -0
  152. package/dist/request-auth/request-principal.js +87 -0
  153. package/dist/request-auth/request-principal.js.map +1 -0
  154. package/dist/request-auth/session-facts-als.d.ts +65 -0
  155. package/dist/request-auth/session-facts-als.d.ts.map +1 -0
  156. package/dist/request-auth/session-facts-als.js +57 -0
  157. package/dist/request-auth/session-facts-als.js.map +1 -0
  158. package/dist/request-auth/session-token-binder.d.ts +130 -0
  159. package/dist/request-auth/session-token-binder.d.ts.map +1 -0
  160. package/dist/request-auth/session-token-binder.js +277 -0
  161. package/dist/request-auth/session-token-binder.js.map +1 -0
  162. package/dist/request-auth/tokens.d.ts +14 -0
  163. package/dist/request-auth/tokens.d.ts.map +1 -0
  164. package/dist/request-auth/tokens.js +16 -0
  165. package/dist/request-auth/tokens.js.map +1 -0
  166. package/dist/session/crypto-adapters.js.map +1 -0
  167. package/dist/session/index.js.map +1 -0
  168. package/dist/session/mariadb-session-record-store.js.map +1 -0
  169. package/dist/session/redis-hot-session-store.js.map +1 -0
  170. package/package.json +2 -2
@@ -1,14 +1,48 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
2
18
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
19
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
20
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
21
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
22
  return c > 3 && r && Object.defineProperty(target, key, r), r;
7
23
  };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
8
41
  var ShieldModule_1;
9
42
  Object.defineProperty(exports, "__esModule", { value: true });
10
43
  exports.ShieldModule = exports.SHIELD_DECISION_AUTHORITY = void 0;
11
44
  const common_1 = require("@nestjs/common");
45
+ const node_url_1 = require("node:url");
12
46
  const core_seam_js_1 = require("../enforcement/core-seam.js");
13
47
  const enforcement_module_js_1 = require("../enforcement/enforcement.module.js");
14
48
  const boot_module_js_1 = require("../boot/boot.module.js");
@@ -21,14 +55,76 @@ const app_config_js_1 = require("../config/app-config.js");
21
55
  // the enforcement-only path never INSTANTIATES it (no live pool/Redis, no HMAC
22
56
  // gate, no core load).
23
57
  const auth_module_js_1 = require("../auth/auth.module.js");
58
+ // The `SHIELD_DECISION_AUTHORITY` token now lives in `./shield-tokens.js` so the
59
+ // enforcement guard can import it without a cycle (guard → token → ShieldModule →
60
+ // EnforcementModule → guard). Re-exported here to keep the public import path stable.
61
+ const shield_tokens_js_1 = require("./shield-tokens.js");
62
+ Object.defineProperty(exports, "SHIELD_DECISION_AUTHORITY", { enumerable: true, get: function () { return shield_tokens_js_1.SHIELD_DECISION_AUTHORITY; } });
63
+ /**
64
+ * A `Function`-wrapped dynamic `import()` that survives the CommonJS **build** — the same
65
+ * shim as auth.module.ts's `evalImport`. Under `module: commonjs`, `tsc` would rewrite a
66
+ * literal `import()` into `require(...)`; routing through the `Function` constructor keeps
67
+ * a genuine native `import()` in the emitted dist so this file's OWN compiled output stays
68
+ * free of a top-level `require('../authz/wire-record-authorization.js')` (→ core). We pass
69
+ * the file's absolute URL (resolved off `__dirname` in the CJS dist / `import.meta.url`
70
+ * would not exist here) so the eval'd import has a base to resolve against.
71
+ */
72
+ const evalImport = new Function('specifier', 'return import(specifier);');
73
+ let cachedRecordAuthApi;
74
+ /**
75
+ * Lazily load the framework's record-authorization builder. Invoked ONLY inside the
76
+ * `SHIELD_DECISION_AUTHORITY` async factory when a consumer supplies
77
+ * `recordAuthorization` — NEVER at module top-level. The target file value-imports
78
+ * `@astram/core` + the four adapter classes, so deferring its load keeps a runtime
79
+ * `require('@astram/core')` OUT of THIS module's (the enforcement-only) load path.
80
+ *
81
+ * TWO strategies, mirroring auth.module.ts's `loadCore`, so it works in BOTH runtimes:
82
+ * 1. A plain dynamic `import()` — native under vitest/SWC (source path).
83
+ * 2. On failure (the tsc-built CJS dist downlevels #1 to a throwing `require` when the
84
+ * transitive `@astram/core` ESM cannot be `require`d in that context), the
85
+ * {@link evalImport} `Function`-wrapped native `import()` resolved off `__dirname`.
86
+ */
87
+ async function loadBuildRecordAuthorization() {
88
+ if (cachedRecordAuthApi === undefined) {
89
+ try {
90
+ cachedRecordAuthApi = (await Promise.resolve().then(() => __importStar(require('../authz/wire-record-authorization.js'))));
91
+ }
92
+ catch {
93
+ // Built-dist fallback (the CJS dist path — see evalImport). `__dirname` is defined
94
+ // in the emitted CJS module; resolve the compiled sibling relative to it and import
95
+ // it as a file:// URL so Node's CJS→ESM interop loads it (and its ESM-only core dep).
96
+ const base = (0, node_url_1.pathToFileURL)(`${__dirname}/../authz/wire-record-authorization.js`).href;
97
+ cachedRecordAuthApi = (await evalImport(base));
98
+ }
99
+ }
100
+ return cachedRecordAuthApi.buildRecordAuthorization;
101
+ }
24
102
  /**
25
- * DI token for the Core decision authority (the ADR-0001 single decision
26
- * authority) that the enforcement guard consults. The consumer supplies the real
27
- * `@astram/core` authority through {@link ShieldModule.forRoot}; when omitted the
28
- * framework binds the fail-closed {@link stubDecisionAuthority} (deny-by-default),
29
- * so a mis-wiring cannot silently open a door.
103
+ * Resolve the `SHIELD_DECISION_AUTHORITY` VALUE from the caller's options (the ONE seam,
104
+ * ADR-0023 — only how its value is PRODUCED changes here). Precedence + mutual exclusion:
105
+ *
106
+ * - `recordAuthorization` PRESENT → the framework builds the REAL wired authority itself
107
+ * (the four record-tier adapters constructed inside guard, un-overridable). This is the
108
+ * Phase-5 path: the app can no longer supply or weaken those adapters.
109
+ * - `decisionAuthority` PRESENT (legacy passthrough) → bind the caller's pre-built
110
+ * authority verbatim (kept for other consumers / back-compat).
111
+ * - BOTH present → MISCONFIG: throw a clear error (they are mutually exclusive).
112
+ * - NEITHER → the fail-closed {@link stubDecisionAuthority} (unchanged fallback).
113
+ *
114
+ * Async because the `recordAuthorization` path lazy-loads the (core-touching) builder.
30
115
  */
31
- exports.SHIELD_DECISION_AUTHORITY = Symbol('SHIELD_DECISION_AUTHORITY');
116
+ async function resolveDecisionAuthority(options) {
117
+ if (options.recordAuthorization && options.decisionAuthority) {
118
+ throw new Error('ShieldModule: `recordAuthorization` and `decisionAuthority` are mutually exclusive — ' +
119
+ 'supply the record-authorization inputs (pool + app ports + registry) so the framework ' +
120
+ 'builds the decision authority, OR a pre-built `decisionAuthority`, but not both.');
121
+ }
122
+ if (options.recordAuthorization) {
123
+ const build = await loadBuildRecordAuthorization();
124
+ return build(options.recordAuthorization);
125
+ }
126
+ return options.decisionAuthority ?? core_seam_js_1.stubDecisionAuthority;
127
+ }
32
128
  /**
33
129
  * `ShieldModule` — the SINGLE registration seam (ADR-0023 §4).
34
130
  *
@@ -68,13 +164,34 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
68
164
  // irrelevant (no AuthModule composes) — a documented no-op.
69
165
  const authModule = options.auth
70
166
  ? auth_module_js_1.AuthModule.forRoot(
71
- // Only pass the property when the consumer supplied one — under
167
+ // Only pass a property when the consumer supplied one — under
72
168
  // `exactOptionalPropertyTypes`, an explicit `undefined` is not assignable
73
- // to the optional `credentialResolver`; a spread keeps it truly absent so
74
- // AuthModule falls back to the default MariaDb binding.
75
- options.credentialResolver
76
- ? { credentialResolver: options.credentialResolver }
77
- : {})
169
+ // to an optional field; a spread keeps it truly absent so AuthModule falls
170
+ // back to its defaults (MariaDb credential store; 501 /auth/me).
171
+ {
172
+ ...(options.credentialResolver
173
+ ? { credentialResolver: options.credentialResolver }
174
+ : {}),
175
+ ...(options.profileResolver
176
+ ? { profileResolver: options.profileResolver }
177
+ : {}),
178
+ // The account/credential-surface seams — same spread-when-present rule
179
+ // (an explicit `undefined` is not assignable under
180
+ // exactOptionalPropertyTypes; absent → AuthModule binds `null` → 501).
181
+ ...(options.credentialWriter ? { credentialWriter: options.credentialWriter } : {}),
182
+ ...(options.resetTokenStore ? { resetTokenStore: options.resetTokenStore } : {}),
183
+ ...(options.emailSender ? { emailSender: options.emailSender } : {}),
184
+ ...(options.recoveryCodeStore ? { recoveryCodeStore: options.recoveryCodeStore } : {}),
185
+ ...(options.profileWriter ? { profileWriter: options.profileWriter } : {}),
186
+ ...(options.sessionSweeper ? { sessionSweeper: options.sessionSweeper } : {}),
187
+ ...(options.auditSink ? { auditSink: options.auditSink } : {}),
188
+ ...(options.mfaDeviceStore ? { mfaDeviceStore: options.mfaDeviceStore } : {}),
189
+ ...(options.totpVerifier ? { totpVerifier: options.totpVerifier } : {}),
190
+ ...(options.rootIds ? { rootIds: options.rootIds } : {}),
191
+ ...(options.sessionFactsProjector
192
+ ? { sessionFactsProjector: options.sessionFactsProjector }
193
+ : {}),
194
+ })
78
195
  : undefined;
79
196
  return {
80
197
  module: ShieldModule_1,
@@ -87,8 +204,19 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
87
204
  providers: [
88
205
  { provide: config_module_js_1.APP_CONFIG, useValue: config },
89
206
  {
90
- provide: exports.SHIELD_DECISION_AUTHORITY,
91
- useValue: options.decisionAuthority ?? core_seam_js_1.stubDecisionAuthority,
207
+ // Async factory: the `recordAuthorization` path lazy-loads the (core-touching)
208
+ // builder — see resolveDecisionAuthority. The legacy `decisionAuthority` +
209
+ // stub paths resolve synchronously but flow through the same one seam. Both
210
+ // supplied → throws (mutual exclusion).
211
+ provide: shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
212
+ useFactory: () => resolveDecisionAuthority({
213
+ ...(options.recordAuthorization
214
+ ? { recordAuthorization: options.recordAuthorization }
215
+ : {}),
216
+ ...(options.decisionAuthority
217
+ ? { decisionAuthority: options.decisionAuthority }
218
+ : {}),
219
+ }),
92
220
  },
93
221
  ],
94
222
  // Re-export the composed AuthModule app-wide ONLY when auth is opted in — its
@@ -96,7 +224,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
96
224
  // visible to a consumer's controllers/services via the @Global seam.
97
225
  exports: [
98
226
  config_module_js_1.APP_CONFIG,
99
- exports.SHIELD_DECISION_AUTHORITY,
227
+ shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
100
228
  ...(authModule ? [authModule] : []),
101
229
  ],
102
230
  };
@@ -124,8 +252,16 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
124
252
  inject: [optionsToken],
125
253
  };
126
254
  const authorityProvider = {
127
- provide: exports.SHIELD_DECISION_AUTHORITY,
128
- useFactory: (opts) => opts.decisionAuthority ?? core_seam_js_1.stubDecisionAuthority,
255
+ provide: shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
256
+ // Same one-seam resolution as forRoot, over the async-resolved options: the
257
+ // `recordAuthorization` path (the app hands pool + app ports + registry — computed
258
+ // in its useFactory from the injected registrations) has the framework build the
259
+ // real wired authority; `decisionAuthority` is the legacy passthrough; both →
260
+ // throws; neither → stub. Async because the record path lazy-loads the builder.
261
+ useFactory: (opts) => resolveDecisionAuthority({
262
+ ...(opts.recordAuthorization ? { recordAuthorization: opts.recordAuthorization } : {}),
263
+ ...(opts.decisionAuthority ? { decisionAuthority: opts.decisionAuthority } : {}),
264
+ }),
129
265
  inject: [optionsToken],
130
266
  };
131
267
  // Same one-instance opt-in AuthModule as forRoot (imported + re-exported).
@@ -133,13 +269,34 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
133
269
  // registration-time collaborator, threaded straight through here.
134
270
  const authModule = options.auth
135
271
  ? auth_module_js_1.AuthModule.forRoot(
136
- // Only pass the property when the consumer supplied one — under
272
+ // Only pass a property when the consumer supplied one — under
137
273
  // `exactOptionalPropertyTypes`, an explicit `undefined` is not assignable
138
- // to the optional `credentialResolver`; a spread keeps it truly absent so
139
- // AuthModule falls back to the default MariaDb binding.
140
- options.credentialResolver
141
- ? { credentialResolver: options.credentialResolver }
142
- : {})
274
+ // to an optional field; a spread keeps it truly absent so AuthModule falls
275
+ // back to its defaults (MariaDb credential store; 501 /auth/me).
276
+ {
277
+ ...(options.credentialResolver
278
+ ? { credentialResolver: options.credentialResolver }
279
+ : {}),
280
+ ...(options.profileResolver
281
+ ? { profileResolver: options.profileResolver }
282
+ : {}),
283
+ // The account/credential-surface seams — same spread-when-present rule
284
+ // (an explicit `undefined` is not assignable under
285
+ // exactOptionalPropertyTypes; absent → AuthModule binds `null` → 501).
286
+ ...(options.credentialWriter ? { credentialWriter: options.credentialWriter } : {}),
287
+ ...(options.resetTokenStore ? { resetTokenStore: options.resetTokenStore } : {}),
288
+ ...(options.emailSender ? { emailSender: options.emailSender } : {}),
289
+ ...(options.recoveryCodeStore ? { recoveryCodeStore: options.recoveryCodeStore } : {}),
290
+ ...(options.profileWriter ? { profileWriter: options.profileWriter } : {}),
291
+ ...(options.sessionSweeper ? { sessionSweeper: options.sessionSweeper } : {}),
292
+ ...(options.auditSink ? { auditSink: options.auditSink } : {}),
293
+ ...(options.mfaDeviceStore ? { mfaDeviceStore: options.mfaDeviceStore } : {}),
294
+ ...(options.totpVerifier ? { totpVerifier: options.totpVerifier } : {}),
295
+ ...(options.rootIds ? { rootIds: options.rootIds } : {}),
296
+ ...(options.sessionFactsProjector
297
+ ? { sessionFactsProjector: options.sessionFactsProjector }
298
+ : {}),
299
+ })
143
300
  : undefined;
144
301
  return {
145
302
  module: ShieldModule_1,
@@ -156,7 +313,7 @@ let ShieldModule = ShieldModule_1 = class ShieldModule {
156
313
  providers: [asyncOptionsProvider, configProvider, authorityProvider],
157
314
  exports: [
158
315
  config_module_js_1.APP_CONFIG,
159
- exports.SHIELD_DECISION_AUTHORITY,
316
+ shield_tokens_js_1.SHIELD_DECISION_AUTHORITY,
160
317
  ...(authModule ? [authModule] : []),
161
318
  ],
162
319
  };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shield.module.js","sourceRoot":"","sources":["../../src/module/shield.module.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Request-principal resolution — extract the bearer access token from an HTTP
3
+ * request and resolve it (via the {@link SessionTokenBinder}) to a `Principal`,
4
+ * fail-closed. This is the request-auth read path the enforcement guard uses for
5
+ * `@Classified` routes before calling `decide()`.
6
+ *
7
+ * The `Principal` is rebuilt from the bound claims. We deliberately do NOT re-run
8
+ * the Core identity broker here (which would apply the root allowlist and could
9
+ * mint a root from an id) — instead we reconstruct a plain, non-root
10
+ * Principal from the exact claims captured at login. `isRoot` is set to
11
+ * `false` on the request path unless the binding explicitly recorded it (it is
12
+ * captured from the login-time Principal, so a genuinely-root login stays
13
+ * root; a normal login can never become one on a later request).
14
+ */
15
+ import type { Principal } from '@astram/core';
16
+ import type { SessionTokenBinder, BoundPrincipalClaims } from './session-token-binder.js';
17
+ /** Extract a bearer token from an `Authorization: Bearer <token>` header. */
18
+ export declare function extractBearerToken(authorizationHeader: unknown): string | null;
19
+ /**
20
+ * Rebuild a `Principal` from bound claims (validated). `isRoot` is read
21
+ * from the binding (captured at login), NEVER inferred — a normal user cannot become
22
+ * a root on the request path.
23
+ */
24
+ export declare function principalFromClaims(claims: BoundPrincipalClaims): Principal | null;
25
+ /**
26
+ * Resolve an HTTP request's bearer token to a `Principal`, or `null` (fail-closed)
27
+ * when there is no token, no binding, or the binding is malformed.
28
+ */
29
+ export declare function resolveRequestPrincipal(authorizationHeader: unknown, binder: SessionTokenBinder): Promise<Principal | null>;
30
+ /**
31
+ * Resolve an HTTP request's bearer token to BOTH a `Principal` AND the raw
32
+ * {@link BoundPrincipalClaims}, or `null` (fail-closed) — the same read path as
33
+ * {@link resolveRequestPrincipal}, but it also surfaces the claims the `Principal`
34
+ * projection drops (notably `mustReenroll` and `sessionId`). The enforcement guard
35
+ * uses this so it can read `claims.mustReenroll` to apply the enforced-re-enroll gate
36
+ * while still attaching the resolved `Principal` for `decide()`.
37
+ */
38
+ export declare function resolveRequestAuth(authorizationHeader: unknown, binder: SessionTokenBinder): Promise<{
39
+ principal: Principal;
40
+ claims: BoundPrincipalClaims;
41
+ } | null>;
42
+ //# sourceMappingURL=request-principal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-principal.d.ts","sourceRoot":"","sources":["../../src/request-auth/request-principal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAK1F,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,mBAAmB,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK9E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,oBAAoB,GAAG,SAAS,GAAG,IAAI,CAoBlF;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAM3B;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,mBAAmB,EAAE,OAAO,EAC5B,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC;IAAE,SAAS,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,GAAG,IAAI,CAAC,CAQxE"}
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ /**
3
+ * Request-principal resolution — extract the bearer access token from an HTTP
4
+ * request and resolve it (via the {@link SessionTokenBinder}) to a `Principal`,
5
+ * fail-closed. This is the request-auth read path the enforcement guard uses for
6
+ * `@Classified` routes before calling `decide()`.
7
+ *
8
+ * The `Principal` is rebuilt from the bound claims. We deliberately do NOT re-run
9
+ * the Core identity broker here (which would apply the root allowlist and could
10
+ * mint a root from an id) — instead we reconstruct a plain, non-root
11
+ * Principal from the exact claims captured at login. `isRoot` is set to
12
+ * `false` on the request path unless the binding explicitly recorded it (it is
13
+ * captured from the login-time Principal, so a genuinely-root login stays
14
+ * root; a normal login can never become one on a later request).
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.extractBearerToken = extractBearerToken;
18
+ exports.principalFromClaims = principalFromClaims;
19
+ exports.resolveRequestPrincipal = resolveRequestPrincipal;
20
+ exports.resolveRequestAuth = resolveRequestAuth;
21
+ /** Valid `Principal.kind` values (mirror of the Core union; validated at rebuild). */
22
+ const PRINCIPAL_KINDS = new Set(['user', 'role_group', 'service_account']);
23
+ /** Extract a bearer token from an `Authorization: Bearer <token>` header. */
24
+ function extractBearerToken(authorizationHeader) {
25
+ if (typeof authorizationHeader !== 'string')
26
+ return null;
27
+ const m = /^Bearer\s+(.+)$/i.exec(authorizationHeader.trim());
28
+ const token = m?.[1]?.trim();
29
+ return token && token.length > 0 ? token : null;
30
+ }
31
+ /**
32
+ * Rebuild a `Principal` from bound claims (validated). `isRoot` is read
33
+ * from the binding (captured at login), NEVER inferred — a normal user cannot become
34
+ * a root on the request path.
35
+ */
36
+ function principalFromClaims(claims) {
37
+ if (typeof claims.id !== 'string' ||
38
+ claims.id.length === 0 ||
39
+ typeof claims.tenantId !== 'string' ||
40
+ claims.tenantId.length === 0) {
41
+ return null;
42
+ }
43
+ const kind = PRINCIPAL_KINDS.has(claims.kind) ? claims.kind : 'user';
44
+ const roleIds = Array.isArray(claims.roleIds)
45
+ ? claims.roleIds.filter((r) => typeof r === 'string')
46
+ : [];
47
+ return Object.freeze({
48
+ kind,
49
+ id: claims.id,
50
+ tenantId: claims.tenantId,
51
+ roleIds: Object.freeze([...roleIds]),
52
+ isRoot: claims.isRoot === true,
53
+ });
54
+ }
55
+ /**
56
+ * Resolve an HTTP request's bearer token to a `Principal`, or `null` (fail-closed)
57
+ * when there is no token, no binding, or the binding is malformed.
58
+ */
59
+ async function resolveRequestPrincipal(authorizationHeader, binder) {
60
+ const token = extractBearerToken(authorizationHeader);
61
+ if (token === null)
62
+ return null;
63
+ const claims = await binder.resolve(token);
64
+ if (claims === null)
65
+ return null;
66
+ return principalFromClaims(claims);
67
+ }
68
+ /**
69
+ * Resolve an HTTP request's bearer token to BOTH a `Principal` AND the raw
70
+ * {@link BoundPrincipalClaims}, or `null` (fail-closed) — the same read path as
71
+ * {@link resolveRequestPrincipal}, but it also surfaces the claims the `Principal`
72
+ * projection drops (notably `mustReenroll` and `sessionId`). The enforcement guard
73
+ * uses this so it can read `claims.mustReenroll` to apply the enforced-re-enroll gate
74
+ * while still attaching the resolved `Principal` for `decide()`.
75
+ */
76
+ async function resolveRequestAuth(authorizationHeader, binder) {
77
+ const token = extractBearerToken(authorizationHeader);
78
+ if (token === null)
79
+ return null;
80
+ const claims = await binder.resolve(token);
81
+ if (claims === null)
82
+ return null;
83
+ const principal = principalFromClaims(claims);
84
+ if (principal === null)
85
+ return null;
86
+ return { principal, claims };
87
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-principal.js","sourceRoot":"","sources":["../../src/request-auth/request-principal.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;GAaG;;AASH,gDAKC;AAOD,kDAoBC;AAMD,0DASC;AAUD,gDAWC;AAxED,sFAAsF;AACtF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,YAAY,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAE3E,6EAA6E;AAC7E,SAAgB,kBAAkB,CAAC,mBAA4B;IAC7D,IAAI,OAAO,mBAAmB,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IACzD,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9D,MAAM,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAC7B,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,MAA4B;IAC9D,IACE,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ;QAC7B,MAAM,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC;QACtB,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QACnC,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAC5B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,eAAe,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,MAAM,CAAC,IAA0B,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5F,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC;QAC3C,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;QAClE,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,IAAI;QACJ,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;QACpC,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,IAAI;KAC/B,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,uBAAuB,CAC3C,mBAA4B,EAC5B,MAA0B;IAE1B,MAAM,KAAK,GAAG,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,kBAAkB,CACtC,mBAA4B,EAC5B,MAA0B;IAE1B,MAAM,KAAK,GAAG,kBAAkB,CAAC,mBAAmB,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,SAAS,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACpC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * The per-request **session-facts AsyncLocalStorage** (ADR-0011 hot path) — the
3
+ * SINGLE shared instance the enforcement guard ENTERS and the consumer's
4
+ * `SessionFactsReader` READS. This is the seam that lets gate 5 serve a principal's
5
+ * coarse grants from the request's cached facts instead of a DB reload on every
6
+ * request, WITHOUT touching the frozen 4-arg `decide()` signature or the core
7
+ * `SessionFactsReader` dep contract.
8
+ *
9
+ * ── WHY A FRAMEWORK-EXPORTED SINGLETON (not DI-injected) ─────────────────────────
10
+ * The guard (framework) must ENTER the scope; the reader (app-owned — astram3 swaps
11
+ * `nullSessionFacts` for an ALS-backed one) must READ the SAME scope. An
12
+ * `AsyncLocalStorage` only shares state across `run()`/`getStore()` calls on the
13
+ * SAME instance, so both sides MUST hold one identical object. Exporting ONE module
14
+ * singleton and importing it on both sides is the simplest way to guarantee that —
15
+ * no DI token round-trip, no risk of two divergent instances. It is a pure in-memory
16
+ * request-scoping primitive (no I/O, no secret), so a shared module-level singleton
17
+ * is safe and idiomatic (this is exactly how Nest's own `ClsService`-style helpers
18
+ * and request-context libraries share an ALS).
19
+ *
20
+ * ── CROSS-PRINCIPAL ISOLATION (the #1 danger) ────────────────────────────────────
21
+ * `AsyncLocalStorage.run(store, cb)` binds `store` to the async execution context of
22
+ * `cb` and everything it awaits — and ONLY that context. Two concurrent requests each
23
+ * call `run()` with THEIR OWN store, so `getStore()` inside each returns that request's
24
+ * facts and never the other's. The guard enters the scope with THIS request's
25
+ * `claims.cachedFacts` (same bearer token → same principal → same facts) wrapping the
26
+ * `decide()` call, so the facts a gate reads are always the current request's. The
27
+ * reader holds NO per-request state on `this` — only the ALS handle — so nothing leaks
28
+ * across requests through the reader object itself.
29
+ *
30
+ * ── FAIL-CLOSED ──────────────────────────────────────────────────────────────────
31
+ * Outside any `run()` scope (or when the guard entered with `cachedFacts: null` because
32
+ * the binding carried none / was malformed), `getStore()` is `undefined` / the store's
33
+ * `cachedFacts` is `null`. The reader maps BOTH to `{ cachedFacts: null,
34
+ * permissionsVersion: 0 }`, so gate 5 falls back to the authoritative DB reload
35
+ * (`PermissionFactsPort.loadCoarseGrants`). A cache miss NEVER fabricates a permit.
36
+ */
37
+ import { AsyncLocalStorage } from 'node:async_hooks';
38
+ import type { CoarseGrants } from '@astram/core';
39
+ /**
40
+ * The per-request session-facts store carried through the ALS. `cachedFacts` is the
41
+ * principal's coarse grants for THIS request (or `null` when none were projected →
42
+ * gate 5 DB-reloads); `permissionsVersion` is the version those facts were projected
43
+ * at (0 when absent). Mirrors the core `SessionFactsReader` shape 1:1.
44
+ */
45
+ export interface SessionFactsStore {
46
+ /** THIS request's coarse grants (ADR-0011), or `null` → gate 5 reloads via the port. */
47
+ readonly cachedFacts: CoarseGrants | null;
48
+ /** The version `cachedFacts` was projected at (0 when absent). */
49
+ readonly permissionsVersion: number;
50
+ }
51
+ /**
52
+ * The ONE shared session-facts `AsyncLocalStorage` (see the file header). The
53
+ * enforcement guard imports this to `run()` the `decide()` call inside the request's
54
+ * scope; the consumer's ALS-backed `SessionFactsReader` imports the SAME instance to
55
+ * `getStore()`. Exported through the guard barrel so the app reads the identical
56
+ * singleton — never a second, divergent ALS.
57
+ */
58
+ export declare const SHIELD_SESSION_FACTS_ALS: AsyncLocalStorage<SessionFactsStore>;
59
+ /**
60
+ * The fail-closed "no facts" store — what a reader returns when there is no active
61
+ * scope or the scope carried no facts. Kept here as the single source of the
62
+ * fail-closed shape so the guard and the reader agree byte-for-byte.
63
+ */
64
+ export declare const NO_SESSION_FACTS: SessionFactsStore;
65
+ //# sourceMappingURL=session-facts-als.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-facts-als.d.ts","sourceRoot":"","sources":["../../src/request-auth/session-facts-als.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAIrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,wFAAwF;IACxF,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IAC1C,kEAAkE;IAClE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,wBAAwB,sCAA6C,CAAC;AAEnF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,iBAG7B,CAAC"}
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ /**
3
+ * The per-request **session-facts AsyncLocalStorage** (ADR-0011 hot path) — the
4
+ * SINGLE shared instance the enforcement guard ENTERS and the consumer's
5
+ * `SessionFactsReader` READS. This is the seam that lets gate 5 serve a principal's
6
+ * coarse grants from the request's cached facts instead of a DB reload on every
7
+ * request, WITHOUT touching the frozen 4-arg `decide()` signature or the core
8
+ * `SessionFactsReader` dep contract.
9
+ *
10
+ * ── WHY A FRAMEWORK-EXPORTED SINGLETON (not DI-injected) ─────────────────────────
11
+ * The guard (framework) must ENTER the scope; the reader (app-owned — astram3 swaps
12
+ * `nullSessionFacts` for an ALS-backed one) must READ the SAME scope. An
13
+ * `AsyncLocalStorage` only shares state across `run()`/`getStore()` calls on the
14
+ * SAME instance, so both sides MUST hold one identical object. Exporting ONE module
15
+ * singleton and importing it on both sides is the simplest way to guarantee that —
16
+ * no DI token round-trip, no risk of two divergent instances. It is a pure in-memory
17
+ * request-scoping primitive (no I/O, no secret), so a shared module-level singleton
18
+ * is safe and idiomatic (this is exactly how Nest's own `ClsService`-style helpers
19
+ * and request-context libraries share an ALS).
20
+ *
21
+ * ── CROSS-PRINCIPAL ISOLATION (the #1 danger) ────────────────────────────────────
22
+ * `AsyncLocalStorage.run(store, cb)` binds `store` to the async execution context of
23
+ * `cb` and everything it awaits — and ONLY that context. Two concurrent requests each
24
+ * call `run()` with THEIR OWN store, so `getStore()` inside each returns that request's
25
+ * facts and never the other's. The guard enters the scope with THIS request's
26
+ * `claims.cachedFacts` (same bearer token → same principal → same facts) wrapping the
27
+ * `decide()` call, so the facts a gate reads are always the current request's. The
28
+ * reader holds NO per-request state on `this` — only the ALS handle — so nothing leaks
29
+ * across requests through the reader object itself.
30
+ *
31
+ * ── FAIL-CLOSED ──────────────────────────────────────────────────────────────────
32
+ * Outside any `run()` scope (or when the guard entered with `cachedFacts: null` because
33
+ * the binding carried none / was malformed), `getStore()` is `undefined` / the store's
34
+ * `cachedFacts` is `null`. The reader maps BOTH to `{ cachedFacts: null,
35
+ * permissionsVersion: 0 }`, so gate 5 falls back to the authoritative DB reload
36
+ * (`PermissionFactsPort.loadCoarseGrants`). A cache miss NEVER fabricates a permit.
37
+ */
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.NO_SESSION_FACTS = exports.SHIELD_SESSION_FACTS_ALS = void 0;
40
+ const node_async_hooks_1 = require("node:async_hooks");
41
+ /**
42
+ * The ONE shared session-facts `AsyncLocalStorage` (see the file header). The
43
+ * enforcement guard imports this to `run()` the `decide()` call inside the request's
44
+ * scope; the consumer's ALS-backed `SessionFactsReader` imports the SAME instance to
45
+ * `getStore()`. Exported through the guard barrel so the app reads the identical
46
+ * singleton — never a second, divergent ALS.
47
+ */
48
+ exports.SHIELD_SESSION_FACTS_ALS = new node_async_hooks_1.AsyncLocalStorage();
49
+ /**
50
+ * The fail-closed "no facts" store — what a reader returns when there is no active
51
+ * scope or the scope carried no facts. Kept here as the single source of the
52
+ * fail-closed shape so the guard and the reader agree byte-for-byte.
53
+ */
54
+ exports.NO_SESSION_FACTS = Object.freeze({
55
+ cachedFacts: null,
56
+ permissionsVersion: 0,
57
+ });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-facts-als.js","sourceRoot":"","sources":["../../src/request-auth/session-facts-als.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;;;AAEH,uDAAqD;AAmBrD;;;;;;GAMG;AACU,QAAA,wBAAwB,GAAG,IAAI,oCAAiB,EAAqB,CAAC;AAEnF;;;;GAIG;AACU,QAAA,gBAAgB,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC/D,WAAW,EAAE,IAAI;IACjB,kBAAkB,EAAE,CAAC;CACtB,CAAC,CAAC"}