@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
@@ -56,6 +56,14 @@ export interface SessionTokensView {
56
56
  export interface SessionSuccess {
57
57
  readonly session: SessionView;
58
58
  readonly tokens: SessionTokensView;
59
+ /**
60
+ * Set to `true` ONLY when the session was completed via a RECOVERY CODE at the
61
+ * `/auth/mfa` step (the lost-device escape hatch). Absent on every normal
62
+ * password / TOTP / refresh success, so those paths are byte-for-byte
63
+ * unaffected. The frontend keys off this to route the recovered session into
64
+ * the enforced re-enroll flow. Carries NO secret — a pure signal flag.
65
+ */
66
+ readonly mfaRecoveryUsed?: boolean;
59
67
  }
60
68
  /** `POST /auth/login` request body. */
61
69
  export interface LoginRequest {
@@ -85,16 +93,18 @@ export interface MfaChallengeResponse {
85
93
  export type LoginResponse = SessionSuccess | MfaChallengeResponse;
86
94
  /**
87
95
  * `POST /auth/mfa` request body — completes a pending MFA challenge. `factor` is
88
- * `'totp'` in Phase 0b (the only real factor; WebAuthn/recovery are contract slots
89
- * only). `code` is the presented one-time code (digits). ⛔ `code` is single-use
90
- * material — never logged.
96
+ * `'totp'` (an authenticator code) or `'recovery_code'` (the lost-device escape
97
+ * hatch — a pre-issued single-use recovery code, verified in a separate branch,
98
+ * see `auth.controller.ts`). WebAuthn remains a contract slot only. `code` is the
99
+ * presented one-time code / recovery code. ⛔ `code` is single-use material —
100
+ * never logged.
91
101
  */
92
102
  export interface MfaRequest {
93
103
  /** The opaque handle from {@link MfaChallengeResponse}. Short-TTL, single-use. */
94
104
  readonly challengeToken: string;
95
- /** The second-factor class being satisfied (Phase 0b: `'totp'`). */
96
- readonly factor: 'totp';
97
- /** The presented one-time code. NEVER logged. */
105
+ /** The second-factor class being satisfied (`'totp'` or `'recovery_code'`). */
106
+ readonly factor: 'totp' | 'recovery_code';
107
+ /** The presented one-time code / recovery code. NEVER logged. */
98
108
  readonly code: string;
99
109
  }
100
110
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../../src/auth/dto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9D,oBAAoB;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oBAAoB;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAMD,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,oBAAoB,CAAC;AAMlE;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC;AAMzC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC;AAM7C;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;CACxB"}
1
+ {"version":3,"file":"dto.d.ts","sourceRoot":"","sources":["../../src/auth/dto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC9D,oBAAoB;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oBAAoB;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;;;;OAMG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAMD,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,oBAAoB,CAAC;AAMlE;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,kFAAkF;IAClF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAC;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,cAAc,CAAC;AAMzC;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,cAAc,CAAC;AAM7C;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;CACxB"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dto.js","sourceRoot":"","sources":["../../src/auth/dto.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending-mfa-store.js","sourceRoot":"","sources":["../../src/auth/pending-mfa-store.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;;;AAEH,6CAA0C;AAW1C,uEAAuE;AACvE,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;AAErC,wFAAwF;AACxF,MAAM,WAAW,GAAG,EAAE,CAAC;AAEvB;;;GAGG;AACH,MAAa,eAAe;IAIP;IAEA;IALF,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAE3D,YACmB,QAAgB,cAAc;IAC/C,iEAAiE;IAChD,QAAsB,IAAI,CAAC,GAAG;QAF9B,UAAK,GAAL,KAAK,CAAyB;QAE9B,UAAK,GAAL,KAAK,CAAyB;IAC9C,CAAC;IAEJ;;;;OAIG;IACH,KAAK,CAAC,YAAoB,EAAE,QAAgB;QAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,IAAA,yBAAW,EAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE;YACtB,YAAY;YACZ,QAAQ;YACR,WAAW,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK;SACvC,CAAC,CAAC;QACH,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;OAMG;IACH,OAAO,CAAC,KAAa;QACnB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACjE,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACtC,4EAA4E;QAC5E,+EAA+E;QAC/E,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACrC,IAAI,IAAI,CAAC,KAAK,EAAE,IAAI,KAAK,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QACnD,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;IACxE,CAAC;IAED,mFAAmF;IAC3E,KAAK;QACX,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC1C,IAAI,GAAG,IAAI,KAAK,CAAC,WAAW;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;CACF;AAlDD,0CAkDC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence.module.js","sourceRoot":"","sources":["../../src/auth/persistence.module.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;;;;;;;;;;AAEH,2CAA2E;AAI3E,iEAAwD;AACxD,2CAA2D;AAC3D,qDAAwF;AAExF;;;;GAIG;AACH,MAAM,cAAc,GAAa;IAC/B,OAAO,EAAE,0BAAc;IACvB,UAAU,EAAE,CAAC,MAAiB,EAAQ,EAAE,CAAC,IAAA,6BAAY,EAAC,MAAM,CAAC;IAC7D,MAAM,EAAE,CAAC,6BAAU,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,aAAa,GAAa;IAC9B,OAAO,EAAE,wBAAY;IACrB,UAAU,EAAE,CAAC,MAAiB,EAAS,EAAE,CAAC,IAAA,kCAAiB,EAAC,MAAM,CAAC;IACnE,MAAM,EAAE,CAAC,6BAAU,CAAC;CACrB,CAAC;AAEF;;;;GAIG;AACH,MAAM,iBAAiB,GAAa;IAClC,OAAO,EAAE,oCAAmB;IAC5B,UAAU,EAAE,CAAC,IAAU,EAAE,KAAY,EAAuB,EAAE,CAC5D,IAAI,oCAAmB,CAAC,IAAI,EAAE,KAAK,CAAC;IACtC,MAAM,EAAE,CAAC,0BAAc,EAAE,wBAAY,CAAC;CACvC,CAAC;AAEF;;;;GAIG;AAEI,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IAC5B,MAAM,CAAC,OAAO;QACZ,OAAO;YACL,MAAM,EAAE,mBAAiB;YACzB,SAAS,EAAE,CAAC,cAAc,EAAE,aAAa,EAAE,iBAAiB,CAAC;YAC7D,OAAO,EAAE,CAAC,0BAAc,EAAE,wBAAY,CAAC;SACxC,CAAC;IACJ,CAAC;CACF,CAAA;AARY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,iBAAiB,CAQ7B"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * AuditSink — the auth-surface **audit seam** (spec §6.2). The auth surface is
3
+ * otherwise un-audited (only the @astram/engine pipe audits record CRUD); this port
4
+ * introduces the FIRST auth audit event. The framework builds the event; the consumer
5
+ * persists it (astram3 writes `shield_audit.audit_log`).
6
+ *
7
+ * OPTIONAL, consumer-plugged through `ShieldModule.forRoot({ auditSink })` and threaded
8
+ * to `AuthModule.forRoot` exactly like the other account/credential seams. Absent
9
+ * (`null`) → the route still works, just un-audited (the `@Optional()` injection in
10
+ * `AuthController` resolves to no seam → the best-effort call is a no-op).
11
+ *
12
+ * BEST-EFFORT by contract: `record` is invoked fire-and-forget on the recovery-login
13
+ * success path — a throwing or slow sink NEVER fails the (legitimate) login. The
14
+ * consumer's impl should not surface errors into the response path.
15
+ *
16
+ * NO SECRET LEAK: the event carries NO password, recovery code, TOTP secret, or token —
17
+ * only the actor id, tenant, action key, and non-secret attributes.
18
+ */
19
+ /**
20
+ * A framework-built auth audit event. `type` is the audit category (`'authentication'`
21
+ * for the auth surface); `action` is the specific event key (e.g. `'mfa.recovery_used'`).
22
+ * `attributes` carries non-secret structured detail (never a code/token/secret).
23
+ */
24
+ export interface AuthAuditEvent {
25
+ /** The audit category. Always `'authentication'` for auth-surface events. */
26
+ readonly type: 'authentication';
27
+ /** When the event occurred (epoch ms). */
28
+ readonly occurredAtMs: number;
29
+ /** The acting principal's id (the account the event is about). */
30
+ readonly actorId: string;
31
+ /** The active tenant/company the event is scoped to, if known. */
32
+ readonly tenantId?: string;
33
+ /** The specific event key, e.g. `'mfa.recovery_used'`. */
34
+ readonly action: string;
35
+ /** Non-secret structured detail. NEVER a code / token / secret. */
36
+ readonly attributes?: Record<string, unknown>;
37
+ }
38
+ /**
39
+ * The port a consumer implements to persist auth audit events. Supplied to the
40
+ * framework via `ShieldModule.forRoot({ auditSink })`. Best-effort: `record` is called
41
+ * fire-and-forget, so an impl error must not propagate into the login response.
42
+ */
43
+ export interface AuditSink {
44
+ /** Persist one auth audit event. Best-effort — never fails the caller's flow. */
45
+ record(event: AuthAuditEvent): Promise<void>;
46
+ }
47
+ //# sourceMappingURL=audit-sink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-sink.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/audit-sink.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,0CAA0C;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/C;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,iFAAiF;IACjF,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * AuditSink — the auth-surface **audit seam** (spec §6.2). The auth surface is
4
+ * otherwise un-audited (only the @astram/engine pipe audits record CRUD); this port
5
+ * introduces the FIRST auth audit event. The framework builds the event; the consumer
6
+ * persists it (astram3 writes `shield_audit.audit_log`).
7
+ *
8
+ * OPTIONAL, consumer-plugged through `ShieldModule.forRoot({ auditSink })` and threaded
9
+ * to `AuthModule.forRoot` exactly like the other account/credential seams. Absent
10
+ * (`null`) → the route still works, just un-audited (the `@Optional()` injection in
11
+ * `AuthController` resolves to no seam → the best-effort call is a no-op).
12
+ *
13
+ * BEST-EFFORT by contract: `record` is invoked fire-and-forget on the recovery-login
14
+ * success path — a throwing or slow sink NEVER fails the (legitimate) login. The
15
+ * consumer's impl should not surface errors into the response path.
16
+ *
17
+ * NO SECRET LEAK: the event carries NO password, recovery code, TOTP secret, or token —
18
+ * only the actor id, tenant, action key, and non-secret attributes.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-sink.js","sourceRoot":"","sources":["../../../src/auth/ports/audit-sink.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * CredentialWriter — the seam that WRITES a principal's password credential. The
3
+ * framework never owns the credential store; the consumer plugs this (like
4
+ * CredentialResolver, but the write side). Supplied via
5
+ * ShieldModule.forRoot({ credentialWriter }). Absent → the routes that need it
6
+ * return 501. Writes HASHES only — never plaintext.
7
+ *
8
+ * TOTP secrets are NOT written here — the multi-device MFA model stores
9
+ * every authenticator secret in the `MfaDeviceStore` (one storage location, one code
10
+ * path). So this seam is now password-only: `setPassword` + `getPasswordHash`.
11
+ */
12
+ export interface CredentialWriter {
13
+ /** Set the principal's password to an already-hashed value. */
14
+ setPassword(principalId: string, passwordHash: string): Promise<void>;
15
+ /**
16
+ * Read the stored password hash by principal id — needed by change-password, which
17
+ * holds the principal id (from the token), whereas CredentialResolver keys by
18
+ * identityHint. Null if the principal is unknown. (This is the ONE read on the
19
+ * writer seam; see Task 5 for why it lives here rather than a separate seam.)
20
+ */
21
+ getPasswordHash(principalId: string): Promise<string | null>;
22
+ }
23
+ //# sourceMappingURL=credential-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-writer.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/credential-writer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE;;;;;OAKG;IACH,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9D"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-writer.js","sourceRoot":"","sources":["../../../src/auth/ports/credential-writer.ts"],"names":[],"mappings":""}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * EmailSender — the delivery seam. The framework builds the message; the consumer
3
+ * delivers it (SendGrid, SMTP, or a test capturer). Consumer-plugged.
4
+ */
5
+ export interface EmailMessage {
6
+ readonly to: string;
7
+ readonly subject: string;
8
+ readonly html: string;
9
+ readonly text: string;
10
+ }
11
+ export interface EmailSender {
12
+ send(message: EmailMessage): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=email-sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-sender.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/email-sender.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AACD,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5C"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-sender.js","sourceRoot":"","sources":["../../../src/auth/ports/email-sender.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * MfaDeviceStore — the seam that persists a principal's TOTP authenticator DEVICES
3
+ * (multi-device MFA). The framework never owns the store; the consumer
4
+ * plugs this (like {@link CredentialWriter}, but for the N-device MFA model).
5
+ * Each device has its OWN base32 secret; login matches ANY confirmed device (the
6
+ * OR-match lives in the consumer resolver + a custom TOTP verifier, NOT here — this
7
+ * port is pure persistence). Supplied via `ShieldModule.forRoot({ mfaDeviceStore })`.
8
+ * Absent (`null`) → the device routes return 501. A device's `secret` is NEVER
9
+ * returned to a client (only {@link listConfirmedSecrets} for the login bundle,
10
+ * server-side) — the display shape {@link MfaDevice} deliberately omits it.
11
+ */
12
+ /** The DISPLAY shape of one MFA device (NO secret — never leaves the server). */
13
+ export interface MfaDevice {
14
+ readonly id: string;
15
+ readonly name: string;
16
+ readonly createdAtMs: number;
17
+ readonly lastUsedAtMs: number | null;
18
+ readonly confirmed: boolean;
19
+ }
20
+ export interface MfaDeviceStore {
21
+ /** All of the principal's devices incl. pending (internal/debug; routes filter to confirmed for display). */
22
+ listAll(principalId: string): Promise<MfaDevice[]>;
23
+ /** Confirmed devices' base32 secrets — for the login requiredFactor bundle + verify. */
24
+ listConfirmedSecrets(principalId: string): Promise<string[]>;
25
+ /**
26
+ * The base32 secret of a SINGLE device the principal owns (confirmed OR pending), or
27
+ * `null` if not found / not owned. Used ONLY server-side by `confirm` to check the
28
+ * presented code against the pending device's own secret — the secret is NEVER
29
+ * returned to a client (the display {@link MfaDevice} deliberately omits it).
30
+ */
31
+ getSecret(principalId: string, deviceId: string): Promise<string | null>;
32
+ /** Create a PENDING device (own id); prunes the caller's stale pending rows first. Returns the deviceId. */
33
+ begin(principalId: string, name: string, secret: string): Promise<string>;
34
+ /** Activate a pending device (confirmed). False if not found / not owned / not pending. */
35
+ confirm(principalId: string, deviceId: string): Promise<boolean>;
36
+ /** Rename a device. Returns the updated device, or null if not found / not owned. */
37
+ rename(principalId: string, deviceId: string, name: string): Promise<MfaDevice | null>;
38
+ /** Delete a device. False if not found / not owned. */
39
+ remove(principalId: string, deviceId: string): Promise<boolean>;
40
+ /** Count of CONFIRMED devices (drives "first device" / "mfa enabled"). */
41
+ countConfirmed(principalId: string): Promise<number>;
42
+ /** Best-effort: stamp last-used for the device whose secret verified (match by unique secret). */
43
+ stampUsed(secret: string): Promise<void>;
44
+ }
45
+ //# sourceMappingURL=mfa-device-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa-device-store.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/mfa-device-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,6GAA6G;IAC7G,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACnD,wFAAwF;IACxF,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D;;;;;OAKG;IACH,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzE,4GAA4G;IAC5G,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1E,2FAA2F;IAC3F,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACjE,qFAAqF;IACrF,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC;IACvF,uDAAuD;IACvD,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChE,0EAA0E;IAC1E,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,kGAAkG;IAClG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C"}
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ /**
3
+ * MfaDeviceStore — the seam that persists a principal's TOTP authenticator DEVICES
4
+ * (multi-device MFA). The framework never owns the store; the consumer
5
+ * plugs this (like {@link CredentialWriter}, but for the N-device MFA model).
6
+ * Each device has its OWN base32 secret; login matches ANY confirmed device (the
7
+ * OR-match lives in the consumer resolver + a custom TOTP verifier, NOT here — this
8
+ * port is pure persistence). Supplied via `ShieldModule.forRoot({ mfaDeviceStore })`.
9
+ * Absent (`null`) → the device routes return 501. A device's `secret` is NEVER
10
+ * returned to a client (only {@link listConfirmedSecrets} for the login bundle,
11
+ * server-side) — the display shape {@link MfaDevice} deliberately omits it.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa-device-store.js","sourceRoot":"","sources":["../../../src/auth/ports/mfa-device-store.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,12 @@
1
+ import type { CurrentUserProfile } from '../profile-resolver.js';
2
+ /**
3
+ * ProfileWriter — self-service profile edit (name/email). Returns the refreshed
4
+ * display profile (names, never UUIDs). Consumer-plugged.
5
+ */
6
+ export interface ProfileWriter {
7
+ update(principalId: string, changes: {
8
+ name?: string;
9
+ email?: string;
10
+ }): Promise<CurrentUserProfile>;
11
+ }
12
+ //# sourceMappingURL=profile-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-writer.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/profile-writer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,CACJ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GACzC,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAChC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-writer.js","sourceRoot":"","sources":["../../../src/auth/ports/profile-writer.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * RecoveryCodeStore — persists Argon2-hashed one-time MFA recovery codes.
3
+ * `replaceAll` wipes prior codes on (re-)enroll. `consume` returns true iff a
4
+ * matching unconsumed code exists, then marks it consumed (single-use).
5
+ */
6
+ export interface RecoveryCodeStore {
7
+ replaceAll(principalId: string, codeHashes: readonly string[]): Promise<void>;
8
+ consume(principalId: string, rawCode: string): Promise<boolean>;
9
+ }
10
+ //# sourceMappingURL=recovery-code-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-code-store.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/recovery-code-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery-code-store.js","sourceRoot":"","sources":["../../../src/auth/ports/recovery-code-store.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ResetTokenStore — persists single-use, TTL'd password-reset tokens. The framework
3
+ * hashes the raw token (SHA-256) before it reaches this store; the store never sees
4
+ * the raw token. `consume` returns the principalId iff the token is unexpired AND
5
+ * unconsumed, then marks it consumed (single-use). Consumer-plugged.
6
+ */
7
+ export interface ResetTokenStore {
8
+ issue(principalId: string, tokenHash: string, expiresAtMs: number): Promise<void>;
9
+ consume(tokenHash: string): Promise<string | null>;
10
+ invalidateAllForPrincipal(principalId: string): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=reset-token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-token-store.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/reset-token-store.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACnD,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset-token-store.js","sourceRoot":"","sources":["../../../src/auth/ports/reset-token-store.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The `SessionFactsProjector` PORT (ADR-0011 eager projection) — the OPT-IN seam the
3
+ * auth surface uses to compute a principal's coarse grants at LOGIN / REFRESH time so
4
+ * they can be cached on the session binding, letting gate 5 serve them from the
5
+ * request's `cachedFacts` instead of a DB reload on every request.
6
+ *
7
+ * ── WHY A DEDICATED PORT (not the core `PermissionFactsPort` directly) ────────────
8
+ * The core `PermissionFactsPort` is an authorization-engine dep wired into the record
9
+ * decision authority (`recordAuthorization`), not something AuthModule injects. Rather
10
+ * than couple the auth controller to that engine wiring, the consumer supplies a thin
11
+ * `SessionFactsProjector` — which it typically implements by delegating to the SAME
12
+ * `DbPermissionFactsAdapter.loadCoarseGrants` it already hands the decision authority.
13
+ * One tiny interface, one clean seam, no engine coupling in AuthModule.
14
+ *
15
+ * ── OPT-IN / ADDITIVE ────────────────────────────────────────────────────────────
16
+ * ABSENT (the default): the auth controller omits `cachedFacts` when binding the
17
+ * session → gate 5 sees no cache → DB reload every request = today's exact behavior.
18
+ * PRESENT: the controller calls `project(...)` at login + refresh and stores the
19
+ * returned {@link CoarseGrants} on the binding, so subsequent requests hit the cache.
20
+ * Turning it on NEVER changes a decision — it only removes a DB round-trip when the
21
+ * cached version is fresh; a stale/absent cache still reloads (gate 5's version gate).
22
+ *
23
+ * ── FAIL-CLOSED ──────────────────────────────────────────────────────────────────
24
+ * `project(...)` returning `null` (or throwing — the caller swallows it best-effort)
25
+ * means "no facts cached" → the session binds WITHOUT `cachedFacts` → gate 5 DB-reloads.
26
+ * A projection failure therefore degrades safely to the correct (un-cached) behavior;
27
+ * it never fabricates or omits a grant that would change a decision.
28
+ */
29
+ import type { CoarseGrants } from '@astram/core';
30
+ export interface SessionFactsProjector {
31
+ /**
32
+ * Project the coarse feature/object-type grants for `principalId` in `tenantId` at
33
+ * login/refresh time (ADR-0011). Returns the {@link CoarseGrants} to cache on the
34
+ * session binding, or `null` when nothing should be cached (→ gate 5 DB-reloads).
35
+ *
36
+ * A principal with NO grants SHOULD return a {@link CoarseGrants} with an empty
37
+ * `grantedPermissionKeys` (tagged with the current version) — that is a legitimate
38
+ * "genuinely no grants" cache, distinct from `null` ("don't cache"). Implementations
39
+ * typically delegate to the same `PermissionFactsPort.loadCoarseGrants` the decision
40
+ * authority uses.
41
+ */
42
+ project(principalId: string, tenantId: string): Promise<CoarseGrants | null>;
43
+ }
44
+ //# sourceMappingURL=session-facts-projector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-facts-projector.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/session-facts-projector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,qBAAqB;IACpC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;CAC9E"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ /**
3
+ * The `SessionFactsProjector` PORT (ADR-0011 eager projection) — the OPT-IN seam the
4
+ * auth surface uses to compute a principal's coarse grants at LOGIN / REFRESH time so
5
+ * they can be cached on the session binding, letting gate 5 serve them from the
6
+ * request's `cachedFacts` instead of a DB reload on every request.
7
+ *
8
+ * ── WHY A DEDICATED PORT (not the core `PermissionFactsPort` directly) ────────────
9
+ * The core `PermissionFactsPort` is an authorization-engine dep wired into the record
10
+ * decision authority (`recordAuthorization`), not something AuthModule injects. Rather
11
+ * than couple the auth controller to that engine wiring, the consumer supplies a thin
12
+ * `SessionFactsProjector` — which it typically implements by delegating to the SAME
13
+ * `DbPermissionFactsAdapter.loadCoarseGrants` it already hands the decision authority.
14
+ * One tiny interface, one clean seam, no engine coupling in AuthModule.
15
+ *
16
+ * ── OPT-IN / ADDITIVE ────────────────────────────────────────────────────────────
17
+ * ABSENT (the default): the auth controller omits `cachedFacts` when binding the
18
+ * session → gate 5 sees no cache → DB reload every request = today's exact behavior.
19
+ * PRESENT: the controller calls `project(...)` at login + refresh and stores the
20
+ * returned {@link CoarseGrants} on the binding, so subsequent requests hit the cache.
21
+ * Turning it on NEVER changes a decision — it only removes a DB round-trip when the
22
+ * cached version is fresh; a stale/absent cache still reloads (gate 5's version gate).
23
+ *
24
+ * ── FAIL-CLOSED ──────────────────────────────────────────────────────────────────
25
+ * `project(...)` returning `null` (or throwing — the caller swallows it best-effort)
26
+ * means "no facts cached" → the session binds WITHOUT `cachedFacts` → gate 5 DB-reloads.
27
+ * A projection failure therefore degrades safely to the correct (un-cached) behavior;
28
+ * it never fabricates or omits a grant that would change a decision.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-facts-projector.js","sourceRoot":"","sources":["../../../src/auth/ports/session-facts-projector.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * SessionSweeper — revokes a principal's sessions after a credential change. The
3
+ * core has no revoke-all-for-principal, so the consumer implements this over its
4
+ * session store (there is an s_principal_id column + index). Consumer-plugged.
5
+ */
6
+ export interface SessionSweeper {
7
+ /** Revoke ALL of the principal's sessions (used after a password reset). */
8
+ revokeAllForPrincipal(principalId: string): Promise<void>;
9
+ /** Revoke all EXCEPT keepSessionId (used after an authenticated change-password). */
10
+ revokeOthersForPrincipal(principalId: string, keepSessionId: string): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=session-sweeper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-sweeper.d.ts","sourceRoot":"","sources":["../../../src/auth/ports/session-sweeper.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,qFAAqF;IACrF,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-sweeper.js","sourceRoot":"","sources":["../../../src/auth/ports/session-sweeper.ts"],"names":[],"mappings":""}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * ProfileResolver — the seam that turns an authenticated `Principal` into the
3
+ * signed-in user's **display profile** for `GET /auth/me` (the current-user
4
+ * endpoint). This is the fill for the flagged gap: login/session return only IDs
5
+ * (`principalId` / `tenantId`), so a UI has no human-readable name/company/role to
6
+ * show without leaking a raw UUID.
7
+ *
8
+ * WHY THIS IS A SEAM (not baked into the framework): the framework owns the
9
+ * CREDENTIAL store (`shield_identity`) but is deliberately AGNOSTIC to the app's
10
+ * ORG model — how users, companies (tenants), and roles are named lives in the
11
+ * consumer's own schema (astram3's `shield_app` tables, another app's users/roles
12
+ * model, etc.). So the framework opens THIS port and the consumer implements it,
13
+ * exactly like {@link CredentialResolver}. Zero core changes; the framework never
14
+ * hard-codes a table or a display-column.
15
+ *
16
+ * SECURITY / CONTRACT:
17
+ * - Called ONLY after the request-auth layer has resolved a live bearer token to a
18
+ * `Principal` (so it always acts as an authenticated caller — never anonymous).
19
+ * - Returns display NAMES, never raw UUIDs (the never-show-UUIDs rule): the
20
+ * consumer resolves `principal.id` → the user's name/email, `principal.tenantId`
21
+ * → the company's display name, `principal.roleIds` → the roles' names.
22
+ * - `null` = the principal has no resolvable profile (e.g. the user row was
23
+ * deleted since login) — the endpoint returns 404-style "no profile", never the
24
+ * ids. A store OUTAGE should THROW (fail-closed) — the endpoint maps it to a
25
+ * generic error, not an empty profile.
26
+ */
27
+ import type { Principal } from '@astram/core';
28
+ /** A role as shown to the user — the display name, plus its id for the UI's keys. */
29
+ export interface ProfileRole {
30
+ /** The role's stable id (safe in `key=`/`data-*`, never rendered as text). */
31
+ readonly id: string;
32
+ /** The role's human-readable display name (what the UI renders). */
33
+ readonly name: string;
34
+ }
35
+ /** The signed-in user's tenant (company) as shown to the user. */
36
+ export interface ProfileTenant {
37
+ /** The tenant/company id (safe in machine contexts, never rendered as text). */
38
+ readonly id: string;
39
+ /** The company's human-readable display name (what the UI renders). */
40
+ readonly name: string;
41
+ }
42
+ /**
43
+ * The current-user display profile returned by `GET /auth/me`. Everything here is
44
+ * SAFE to render — names, not UUIDs. The bare `id` fields are for the UI's internal
45
+ * keys/routing only (the never-show-UUIDs rule allows ids in machine contexts).
46
+ */
47
+ export interface CurrentUserProfile {
48
+ /** The user's id (machine use only — routing/keys, never rendered as text). */
49
+ readonly id: string;
50
+ /** The user's display name (what the header/card renders). */
51
+ readonly name: string;
52
+ /** The user's email/username, if the app exposes one (else omit). */
53
+ readonly email?: string;
54
+ /** The active tenant/company (the boundary the session is scoped to). */
55
+ readonly company: ProfileTenant;
56
+ /** The roles the user holds in the active tenant (display names). */
57
+ readonly roles: readonly ProfileRole[];
58
+ }
59
+ /**
60
+ * The port a consumer implements to back `GET /auth/me`. Supplied to the framework
61
+ * via `ShieldModule.forRoot({ profileResolver })`. Framework-agnostic: the framework
62
+ * never reads the app's org tables — it calls this seam.
63
+ */
64
+ export interface ProfileResolver {
65
+ /**
66
+ * Resolve an authenticated `Principal` → the display profile, or `null` when no
67
+ * profile exists for this principal (deleted user, etc.). MUST return display
68
+ * NAMES (never raw UUIDs) for `name`/`company.name`/`roles[].name`. A store outage
69
+ * should THROW (fail-closed) rather than return a partial/empty profile.
70
+ */
71
+ resolve(principal: Principal): Promise<CurrentUserProfile | null>;
72
+ }
73
+ //# sourceMappingURL=profile-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-resolver.d.ts","sourceRoot":"","sources":["../../src/auth/profile-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,gFAAgF;IAChF,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CACxC;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACnE"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ /**
3
+ * ProfileResolver — the seam that turns an authenticated `Principal` into the
4
+ * signed-in user's **display profile** for `GET /auth/me` (the current-user
5
+ * endpoint). This is the fill for the flagged gap: login/session return only IDs
6
+ * (`principalId` / `tenantId`), so a UI has no human-readable name/company/role to
7
+ * show without leaking a raw UUID.
8
+ *
9
+ * WHY THIS IS A SEAM (not baked into the framework): the framework owns the
10
+ * CREDENTIAL store (`shield_identity`) but is deliberately AGNOSTIC to the app's
11
+ * ORG model — how users, companies (tenants), and roles are named lives in the
12
+ * consumer's own schema (astram3's `shield_app` tables, another app's users/roles
13
+ * model, etc.). So the framework opens THIS port and the consumer implements it,
14
+ * exactly like {@link CredentialResolver}. Zero core changes; the framework never
15
+ * hard-codes a table or a display-column.
16
+ *
17
+ * SECURITY / CONTRACT:
18
+ * - Called ONLY after the request-auth layer has resolved a live bearer token to a
19
+ * `Principal` (so it always acts as an authenticated caller — never anonymous).
20
+ * - Returns display NAMES, never raw UUIDs (the never-show-UUIDs rule): the
21
+ * consumer resolves `principal.id` → the user's name/email, `principal.tenantId`
22
+ * → the company's display name, `principal.roleIds` → the roles' names.
23
+ * - `null` = the principal has no resolvable profile (e.g. the user row was
24
+ * deleted since login) — the endpoint returns 404-style "no profile", never the
25
+ * ids. A store OUTAGE should THROW (fail-closed) — the endpoint maps it to a
26
+ * generic error, not an empty profile.
27
+ */
28
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-resolver.js","sourceRoot":"","sources":["../../src/auth/profile-resolver.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG"}