@astram/guard 0.19.0 → 0.20.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 (129) hide show
  1. package/dist/auth/account.controller.d.ts +3 -1
  2. package/dist/auth/account.controller.d.ts.map +1 -1
  3. package/dist/auth/account.controller.js +79 -6
  4. package/dist/auth/admin-signin-controls.d.ts +80 -0
  5. package/dist/auth/admin-signin-controls.d.ts.map +1 -0
  6. package/dist/auth/admin-signin-controls.js +91 -0
  7. package/dist/auth/auth-audit.d.ts +59 -0
  8. package/dist/auth/auth-audit.d.ts.map +1 -0
  9. package/dist/auth/auth-audit.js +93 -0
  10. package/dist/auth/auth.controller.d.ts +53 -6
  11. package/dist/auth/auth.controller.d.ts.map +1 -1
  12. package/dist/auth/auth.controller.js +330 -44
  13. package/dist/auth/auth.module.d.ts +49 -0
  14. package/dist/auth/auth.module.d.ts.map +1 -1
  15. package/dist/auth/auth.module.js +149 -4
  16. package/dist/auth/captcha-gate.d.ts +103 -0
  17. package/dist/auth/captcha-gate.d.ts.map +1 -0
  18. package/dist/auth/captcha-gate.js +245 -0
  19. package/dist/auth/dto.d.ts +22 -1
  20. package/dist/auth/dto.d.ts.map +1 -1
  21. package/dist/auth/login-name-count-boot.d.ts +24 -0
  22. package/dist/auth/login-name-count-boot.d.ts.map +1 -0
  23. package/dist/auth/login-name-count-boot.js +47 -0
  24. package/dist/auth/logout-proof.d.ts +55 -0
  25. package/dist/auth/logout-proof.d.ts.map +1 -0
  26. package/dist/auth/logout-proof.js +127 -0
  27. package/dist/auth/pending-mfa-store.d.ts +5 -0
  28. package/dist/auth/pending-mfa-store.d.ts.map +1 -1
  29. package/dist/auth/pending-mfa-store.js +2 -0
  30. package/dist/auth/ports/audit-sink.d.ts +5 -2
  31. package/dist/auth/ports/audit-sink.d.ts.map +1 -1
  32. package/dist/auth/ports/mfa-device-store.d.ts +7 -0
  33. package/dist/auth/ports/mfa-device-store.d.ts.map +1 -1
  34. package/dist/auth/profile-resolver.d.ts +6 -0
  35. package/dist/auth/profile-resolver.d.ts.map +1 -1
  36. package/dist/auth/rate-limit/attempt-limiter.d.ts +6 -0
  37. package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -1
  38. package/dist/auth/rate-limit/attempt-limiter.js +14 -0
  39. package/dist/auth/tokens.d.ts +59 -0
  40. package/dist/auth/tokens.d.ts.map +1 -1
  41. package/dist/auth/tokens.js +61 -1
  42. package/dist/authentication/credential-provisioner.d.ts +7 -0
  43. package/dist/authentication/credential-provisioner.d.ts.map +1 -1
  44. package/dist/authentication/mariadb-credential-provisioner.d.ts +1 -0
  45. package/dist/authentication/mariadb-credential-provisioner.d.ts.map +1 -1
  46. package/dist/authentication/mariadb-credential-provisioner.js +7 -0
  47. package/dist/authentication/mariadb-credential-resolver.d.ts +23 -2
  48. package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
  49. package/dist/authentication/mariadb-credential-resolver.js +45 -1
  50. package/dist/authentication/mariadb-mfa-device-store.d.ts +7 -0
  51. package/dist/authentication/mariadb-mfa-device-store.d.ts.map +1 -1
  52. package/dist/authentication/mariadb-mfa-device-store.js +32 -0
  53. package/dist/authentication/mariadb-mfa-requirement-source.d.ts +55 -0
  54. package/dist/authentication/mariadb-mfa-requirement-source.d.ts.map +1 -0
  55. package/dist/authentication/mariadb-mfa-requirement-source.js +164 -0
  56. package/dist/authentication/mariadb-mfa-resetter.d.ts +42 -0
  57. package/dist/authentication/mariadb-mfa-resetter.d.ts.map +1 -0
  58. package/dist/authentication/mariadb-mfa-resetter.js +59 -0
  59. package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
  60. package/dist/authentication/mariadb-profile-writer.js +5 -0
  61. package/dist/authentication/sign-in-with.d.ts +14 -0
  62. package/dist/authentication/sign-in-with.d.ts.map +1 -0
  63. package/dist/authentication/sign-in-with.js +36 -0
  64. package/dist/authz/core-access.d.ts +11 -0
  65. package/dist/authz/core-access.d.ts.map +1 -0
  66. package/dist/authz/core-access.js +14 -0
  67. package/dist/authz/list-scope-sql.d.ts.map +1 -1
  68. package/dist/authz/list-scope-sql.js +15 -5
  69. package/dist/authz/wire-record-authorization.d.ts.map +1 -1
  70. package/dist/authz/wire-record-authorization.js +21 -3
  71. package/dist/captcha/captcha.providers.d.ts +18 -0
  72. package/dist/captcha/captcha.providers.d.ts.map +1 -0
  73. package/dist/captcha/captcha.providers.js +78 -0
  74. package/dist/captcha/recaptcha-verifier.d.ts +20 -0
  75. package/dist/captcha/recaptcha-verifier.d.ts.map +1 -0
  76. package/dist/captcha/recaptcha-verifier.js +48 -0
  77. package/dist/captcha/siteverify.d.ts +47 -0
  78. package/dist/captcha/siteverify.d.ts.map +1 -0
  79. package/dist/captcha/siteverify.js +98 -0
  80. package/dist/captcha/turnstile-verifier.d.ts +18 -0
  81. package/dist/captcha/turnstile-verifier.d.ts.map +1 -0
  82. package/dist/captcha/turnstile-verifier.js +41 -0
  83. package/dist/config/captcha-config.d.ts +47 -0
  84. package/dist/config/captcha-config.d.ts.map +1 -0
  85. package/dist/config/captcha-config.js +163 -0
  86. package/dist/config/reserved-principal-ids.d.ts +15 -0
  87. package/dist/config/reserved-principal-ids.d.ts.map +1 -1
  88. package/dist/config/reserved-principal-ids.js +20 -3
  89. package/dist/enforcement/global-enforcement.guard.d.ts +9 -1
  90. package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
  91. package/dist/enforcement/global-enforcement.guard.js +52 -10
  92. package/dist/enforcement/reenroll-allowlist.d.ts +5 -0
  93. package/dist/enforcement/reenroll-allowlist.d.ts.map +1 -1
  94. package/dist/enforcement/reenroll-allowlist.js +5 -0
  95. package/dist/identity/auth-facts.controller.d.ts +31 -4
  96. package/dist/identity/auth-facts.controller.d.ts.map +1 -1
  97. package/dist/identity/auth-facts.controller.js +46 -11
  98. package/dist/identity/identity-actions.d.ts +2 -2
  99. package/dist/identity/identity-actions.d.ts.map +1 -1
  100. package/dist/identity/module-registry.bridge.d.ts.map +1 -1
  101. package/dist/identity/module-registry.bridge.js +3 -6
  102. package/dist/identity/permission-facts.adapter.d.ts +22 -0
  103. package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
  104. package/dist/identity/permission-facts.adapter.js +26 -1
  105. package/dist/index.d.ts +9 -2
  106. package/dist/index.d.ts.map +1 -1
  107. package/dist/index.js +36 -10
  108. package/dist/internal.d.ts +27 -0
  109. package/dist/internal.d.ts.map +1 -1
  110. package/dist/internal.js +36 -1
  111. package/dist/module/shield.module.d.ts +27 -0
  112. package/dist/module/shield.module.d.ts.map +1 -1
  113. package/dist/module/shield.module.js +10 -0
  114. package/dist/request-auth/request-principal.d.ts +8 -1
  115. package/dist/request-auth/request-principal.d.ts.map +1 -1
  116. package/dist/request-auth/request-principal.js +11 -4
  117. package/dist/request-auth/session-token-binder.d.ts +82 -0
  118. package/dist/request-auth/session-token-binder.d.ts.map +1 -1
  119. package/dist/request-auth/session-token-binder.js +203 -1
  120. package/dist/session/mfa-requirement-service.d.ts +168 -0
  121. package/dist/session/mfa-requirement-service.d.ts.map +1 -0
  122. package/dist/session/mfa-requirement-service.js +461 -0
  123. package/dist/session/session-restriction-store.d.ts +36 -0
  124. package/dist/session/session-restriction-store.d.ts.map +1 -1
  125. package/dist/session/session-restriction-store.js +57 -6
  126. package/dist/session/session-restriction.d.ts +22 -0
  127. package/dist/session/session-restriction.d.ts.map +1 -0
  128. package/dist/session/session-restriction.js +23 -0
  129. package/package.json +5 -5
@@ -55,6 +55,12 @@ export interface CurrentUserProfile {
55
55
  readonly company: ProfileTenant;
56
56
  /** The roles the user holds in the active tenant (display names). */
57
57
  readonly roles: readonly ProfileRole[];
58
+ /**
59
+ * Auth capabilities R2.4: set by `GET /auth/me` (never by the resolver) while the session is in a
60
+ * mandatory-2FA grace period — the enrolment deadline as an ISO-8601 UTC instant, for a banner.
61
+ * Absent when no grace deadline is bound.
62
+ */
63
+ readonly mfaEnrollmentDeadline?: string;
58
64
  }
59
65
  /**
60
66
  * The port a consumer implements to back `GET /auth/me`. Supplied to the framework
@@ -1 +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"}
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;IACvC;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CACzC;AAED;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;CACnE"}
@@ -151,5 +151,11 @@ export declare class AttemptLimiter {
151
151
  * leaves the (soon-to-expire) counter in place.
152
152
  */
153
153
  reset(key: string): Promise<void>;
154
+ /**
155
+ * Delete the counter on purpose (auth capabilities R11.1: an admin unlock). Unlike the
156
+ * best-effort {@link reset}, a Redis error REJECTS with {@link AttemptLimiterUnavailableError}:
157
+ * an unlock must never report a success it did not achieve.
158
+ */
159
+ clear(key: string): Promise<void>;
154
160
  }
155
161
  //# sourceMappingURL=attempt-limiter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"attempt-limiter.d.ts","sourceRoot":"","sources":["../../../src/auth/rate-limit/attempt-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAGH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAEjC;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,yJAI1B,CAAC;AAEV,0FAA0F;AAC1F,eAAO,MAAM,qBAAqB,4JAGzB,CAAC;AAEV,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;;CAKxD;AAED,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,cAAc;IAKvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IALtB,oFAAoF;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAmC;gBAG9C,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,oBAAoB;IAG5C;;;;;OAKG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,iFAAiF;IACjF,OAAO,CAAC,CAAC;IAIT;;;;;OAKG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc1C;;;;;;;;;;;;;;;OAeG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAmBnD;;;;;OAKG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBjD;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAOxC"}
1
+ {"version":3,"file":"attempt-limiter.d.ts","sourceRoot":"","sources":["../../../src/auth/rate-limit/attempt-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAGH,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAEjC;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,yJAI1B,CAAC;AAEV,0FAA0F;AAC1F,eAAO,MAAM,qBAAqB,4JAGzB,CAAC;AAEV,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,8BAA+B,SAAQ,KAAK;;CAKxD;AAED,gFAAgF;AAChF,MAAM,WAAW,oBAAoB;IACnC,mFAAmF;IACnF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,cAAc;IAKvB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IALtB,oFAAoF;IACpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAmC;gBAG9C,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,oBAAoB;IAG5C;;;;;OAKG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,iFAAiF;IACjF,OAAO,CAAC,CAAC;IAIT;;;;;OAKG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAc1C;;;;;;;;;;;;;;;OAeG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAmBnD;;;;;OAKG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAUxC;;;;;;;;;;;OAWG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAmBjD;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvC;;;;OAIG;IACG,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAUxC"}
@@ -214,6 +214,20 @@ class AttemptLimiter {
214
214
  // Ignore — the counter TTL will expire it anyway; never fail a success path.
215
215
  }
216
216
  }
217
+ /**
218
+ * Delete the counter on purpose (auth capabilities R11.1: an admin unlock). Unlike the
219
+ * best-effort {@link reset}, a Redis error REJECTS with {@link AttemptLimiterUnavailableError}:
220
+ * an unlock must never report a success it did not achieve.
221
+ */
222
+ async clear(key) {
223
+ try {
224
+ await this.redis.del(this.k(key));
225
+ }
226
+ catch (err) {
227
+ AttemptLimiter.logger.warn(`attempt-limiter clear failed — Redis unavailable: ${describeError(err)}`);
228
+ throw new AttemptLimiterUnavailableError();
229
+ }
230
+ }
217
231
  }
218
232
  exports.AttemptLimiter = AttemptLimiter;
219
233
  /** A tiny, secret-free error description for the fail-open log (never logs a key). */
@@ -166,6 +166,14 @@ export declare const SHIELD_PROFILE_WRITER: unique symbol;
166
166
  * un-audited). Consumed by the `/auth/mfa` recovery branch (fire-and-forget).
167
167
  */
168
168
  export declare const SHIELD_AUDIT_SINK: unique symbol;
169
+ /**
170
+ * The framework DEFAULT auth `AuditSink` (R12.1, B-3). `@astram/identity`'s
171
+ * `IdentityFeatureModule.forRoot` binds it (globally) to an adapter over the engine audit sink,
172
+ * which writes `<ns>_audit.audit_log`. {@link SHIELD_AUDIT_SINK} resolves to the app-supplied
173
+ * `auditSink` when there is one, else to this token when bound, else `null`. Guard never imports
174
+ * identity (ADR-0003): the token is the seam. Exported from `@astram/guard/internal`.
175
+ */
176
+ export declare const SHIELD_DEFAULT_AUTH_AUDIT_SINK: unique symbol;
169
177
  /**
170
178
  * The `MfaDeviceStore` (guard port, `../auth/ports/mfa-device-store.ts`) — persists
171
179
  * a principal's N TOTP authenticator DEVICES (multi-device MFA). Consumed
@@ -388,4 +396,55 @@ export declare const SHIELD_PERMISSION_VERSION_CACHE: unique symbol;
388
396
  * the rebuild is refused (401) and recovery logins cannot be marked (they fail closed).
389
397
  */
390
398
  export declare const SHIELD_SESSION_RESTRICTIONS: unique symbol;
399
+ /**
400
+ * The validated `signInWith` option (auth capabilities R1.8; `readonly SignInMethod[]`, default
401
+ * `['email']`). Consumed by the default credential resolver (and by login-options).
402
+ */
403
+ export declare const SHIELD_SIGN_IN_WITH: unique symbol;
404
+ /**
405
+ * Mandatory 2FA (auth capabilities R2, B-1): the guard's MariaDB {@link
406
+ * import('@astram/core').MfaRequirementSource} (`authentication/mariadb-mfa-requirement-source.ts`)
407
+ * over the runtime pool — policies, the scope chain, confirmed devices and the per-user grace clock.
408
+ * Provided by `AuthModule` whenever the pool exists; reads nothing until asked.
409
+ */
410
+ export declare const SHIELD_MFA_REQUIREMENT_SOURCE: unique symbol;
411
+ /**
412
+ * Mandatory 2FA (R2.3–R2.5, R2.7): the {@link
413
+ * import('../session/mfa-requirement-service.js').MfaRequirementService} the login, the request
414
+ * path (gate-5 reload and the self-authenticating routes), device confirm and device removal use.
415
+ * `null` unless {@link SHIELD_MFA_POLICY_FEATURE} is bound: without the policy feature, guard
416
+ * behaves exactly as before (no evaluation, no extra read, no new claim).
417
+ */
418
+ export declare const SHIELD_MFA_REQUIREMENT: unique symbol;
419
+ /**
420
+ * Admin sign-in controls (auth capabilities R11.2): the {@link
421
+ * import('./admin-signin-controls.js').AdminStepUp} — the admin's own current TOTP on their
422
+ * `mfa:pid` budget through the replay-guarded login verifier. For `@astram/identity` only;
423
+ * exported from `@astram/guard/internal`.
424
+ */
425
+ export declare const SHIELD_ADMIN_STEP_UP: unique symbol;
426
+ /**
427
+ * Admin sign-in controls (R11.1 unlock): the {@link
428
+ * import('./admin-signin-controls.js').AccountUnlocker} that clears one principal's login, MFA,
429
+ * recovery and CAPTCHA account counters. For `@astram/identity` only; exported from
430
+ * `@astram/guard/internal`.
431
+ */
432
+ export declare const SHIELD_ACCOUNT_UNLOCKER: unique symbol;
433
+ /**
434
+ * The switch that turns mandatory-2FA evaluation on (R2). `@astram/identity` binds it `true`
435
+ * globally when its security-policy feature is registered (`IdentityFeatureModule.forRoot`); guard
436
+ * never imports identity (ADR-0003), so the token is the seam. Exported from
437
+ * `@astram/guard/internal`.
438
+ */
439
+ export declare const SHIELD_MFA_POLICY_FEATURE: unique symbol;
440
+ /**
441
+ * Auth capabilities R7 (CAPTCHA). `SHIELD_CAPTCHA_CONFIG`: the validated {@link
442
+ * import('../config/captcha-config.js').CaptchaConfig} (`null` unless `captcha: 'env'`).
443
+ * `SHIELD_CAPTCHA_VERIFIER`: the provider adapter (core `CaptchaVerifier`). `SHIELD_CAPTCHA_GATE`:
444
+ * the {@link import('./captcha-gate.js').CaptchaGate} on login / forgot / reset (`null` when off,
445
+ * so every route answers exactly as before).
446
+ */
447
+ export declare const SHIELD_CAPTCHA_CONFIG: unique symbol;
448
+ export declare const SHIELD_CAPTCHA_VERIFIER: unique symbol;
449
+ export declare const SHIELD_CAPTCHA_GATE: unique symbol;
391
450
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/auth/tokens.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/auth/tokens.ts"],"names":[],"mappings":"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"}
@@ -23,7 +23,8 @@
23
23
  * ADR-0003: no framework/DB/crypto imports here — just Symbols (+ one re-export).
24
24
  */
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.SHIELD_PERMISSION_FACTS = exports.SHIELD_RESET_LIMITER = exports.SHIELD_FORGOT_HINT_LIMITER = exports.SHIELD_FORGOT_LIMITER = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.SHIELD_RECOVERY_LIMITER = exports.SHIELD_LOGIN_IP_LIMITER = exports.SHIELD_LOGIN_LIMITER = exports.SHIELD_MEMBERSHIP_CHECKER = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.SHIELD_ROOT_IDS = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_TOTP_VERIFIER = exports.SHIELD_RESET_LINK_BASE_URL = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_AUDIT_SINK = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_EMAIL_DELIVERY = exports.SHIELD_ATTEMPT_LIMITER_FACTORY = exports.SHIELD_MESSAGE_DISPATCHER = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_CONFIRM_EMAIL_LIMITER = exports.SHIELD_EMAIL_CHANGE_LIMITER = exports.SHIELD_EMAIL_CHANGE_LINK_BASE_URL = exports.SHIELD_EMAIL_ADDRESS_CHANGER = exports.SHIELD_EMAIL_CHANGE_TOKEN_STORE = exports.SHIELD_RESET_TOKEN_STORE = exports.SHIELD_CREDENTIAL_WRITER = exports.SHIELD_RECENT_ROTATION_STORE = exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = exports.SHIELD_PROFILE_RESOLVER = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.SHIELD_DB_POOL = exports.SHIELD_PASSWORD_HASHER = void 0;
26
+ exports.SHIELD_MFA_POLICY_FEATURE = exports.SHIELD_ACCOUNT_UNLOCKER = exports.SHIELD_ADMIN_STEP_UP = exports.SHIELD_MFA_REQUIREMENT = exports.SHIELD_MFA_REQUIREMENT_SOURCE = exports.SHIELD_SIGN_IN_WITH = exports.SHIELD_SESSION_RESTRICTIONS = exports.SHIELD_PERMISSION_VERSION_CACHE = exports.SHIELD_PERMISSION_FACTS = exports.SHIELD_RESET_LIMITER = exports.SHIELD_FORGOT_HINT_LIMITER = exports.SHIELD_FORGOT_LIMITER = exports.SHIELD_LOGIN_TOTP_VERIFIER = exports.SHIELD_MFA_STEP_UP = exports.SHIELD_MFA_LIMITER = exports.SHIELD_RECOVERY_LIMITER = exports.SHIELD_LOGIN_IP_LIMITER = exports.SHIELD_LOGIN_LIMITER = exports.SHIELD_MEMBERSHIP_CHECKER = exports.SHIELD_SCOPE_SUBTREE_RESOLVER = exports.SHIELD_SESSION_FACTS_PROJECTOR = exports.SHIELD_ROOT_IDS = exports.SHIELD_TOTP_VERIFIER_OVERRIDE = exports.SHIELD_TOTP_VERIFIER = exports.SHIELD_RESET_LINK_BASE_URL = exports.SHIELD_SESSION_SWEEPER = exports.SHIELD_MFA_DEVICE_STORE = exports.SHIELD_DEFAULT_AUTH_AUDIT_SINK = exports.SHIELD_AUDIT_SINK = exports.SHIELD_PROFILE_WRITER = exports.SHIELD_RECOVERY_CODE_STORE = exports.SHIELD_EMAIL_DELIVERY = exports.SHIELD_ATTEMPT_LIMITER_FACTORY = exports.SHIELD_MESSAGE_DISPATCHER = exports.SHIELD_EMAIL_SENDER = exports.SHIELD_CONFIRM_EMAIL_LIMITER = exports.SHIELD_EMAIL_CHANGE_LIMITER = exports.SHIELD_EMAIL_CHANGE_LINK_BASE_URL = exports.SHIELD_EMAIL_ADDRESS_CHANGER = exports.SHIELD_EMAIL_CHANGE_TOKEN_STORE = exports.SHIELD_RESET_TOKEN_STORE = exports.SHIELD_CREDENTIAL_WRITER = exports.SHIELD_RECENT_ROTATION_STORE = exports.SHIELD_SESSION_AUTHORITY = exports.SHIELD_AUTHENTICATOR = exports.SHIELD_PROFILE_RESOLVER = exports.SHIELD_CREDENTIAL_RESOLVER = exports.SHIELD_REDIS = exports.SHIELD_DB_POOL = exports.SHIELD_PASSWORD_HASHER = void 0;
27
+ exports.SHIELD_CAPTCHA_GATE = exports.SHIELD_CAPTCHA_VERIFIER = exports.SHIELD_CAPTCHA_CONFIG = void 0;
27
28
  var argon2id_password_hasher_js_1 = require("../authentication/argon2id-password-hasher.js");
28
29
  Object.defineProperty(exports, "SHIELD_PASSWORD_HASHER", { enumerable: true, get: function () { return argon2id_password_hasher_js_1.SHIELD_PASSWORD_HASHER; } });
29
30
  /**
@@ -178,6 +179,14 @@ exports.SHIELD_PROFILE_WRITER = Symbol('SHIELD_PROFILE_WRITER');
178
179
  * un-audited). Consumed by the `/auth/mfa` recovery branch (fire-and-forget).
179
180
  */
180
181
  exports.SHIELD_AUDIT_SINK = Symbol('SHIELD_AUDIT_SINK');
182
+ /**
183
+ * The framework DEFAULT auth `AuditSink` (R12.1, B-3). `@astram/identity`'s
184
+ * `IdentityFeatureModule.forRoot` binds it (globally) to an adapter over the engine audit sink,
185
+ * which writes `<ns>_audit.audit_log`. {@link SHIELD_AUDIT_SINK} resolves to the app-supplied
186
+ * `auditSink` when there is one, else to this token when bound, else `null`. Guard never imports
187
+ * identity (ADR-0003): the token is the seam. Exported from `@astram/guard/internal`.
188
+ */
189
+ exports.SHIELD_DEFAULT_AUTH_AUDIT_SINK = Symbol('SHIELD_DEFAULT_AUTH_AUDIT_SINK');
181
190
  /**
182
191
  * The `MfaDeviceStore` (guard port, `../auth/ports/mfa-device-store.ts`) — persists
183
192
  * a principal's N TOTP authenticator DEVICES (multi-device MFA). Consumed
@@ -407,3 +416,54 @@ exports.SHIELD_PERMISSION_VERSION_CACHE = Symbol('SHIELD_PERMISSION_VERSION_CACH
407
416
  * the rebuild is refused (401) and recovery logins cannot be marked (they fail closed).
408
417
  */
409
418
  exports.SHIELD_SESSION_RESTRICTIONS = Symbol('SHIELD_SESSION_RESTRICTIONS');
419
+ /**
420
+ * The validated `signInWith` option (auth capabilities R1.8; `readonly SignInMethod[]`, default
421
+ * `['email']`). Consumed by the default credential resolver (and by login-options).
422
+ */
423
+ exports.SHIELD_SIGN_IN_WITH = Symbol('SHIELD_SIGN_IN_WITH');
424
+ /**
425
+ * Mandatory 2FA (auth capabilities R2, B-1): the guard's MariaDB {@link
426
+ * import('@astram/core').MfaRequirementSource} (`authentication/mariadb-mfa-requirement-source.ts`)
427
+ * over the runtime pool — policies, the scope chain, confirmed devices and the per-user grace clock.
428
+ * Provided by `AuthModule` whenever the pool exists; reads nothing until asked.
429
+ */
430
+ exports.SHIELD_MFA_REQUIREMENT_SOURCE = Symbol('SHIELD_MFA_REQUIREMENT_SOURCE');
431
+ /**
432
+ * Mandatory 2FA (R2.3–R2.5, R2.7): the {@link
433
+ * import('../session/mfa-requirement-service.js').MfaRequirementService} the login, the request
434
+ * path (gate-5 reload and the self-authenticating routes), device confirm and device removal use.
435
+ * `null` unless {@link SHIELD_MFA_POLICY_FEATURE} is bound: without the policy feature, guard
436
+ * behaves exactly as before (no evaluation, no extra read, no new claim).
437
+ */
438
+ exports.SHIELD_MFA_REQUIREMENT = Symbol('SHIELD_MFA_REQUIREMENT');
439
+ /**
440
+ * Admin sign-in controls (auth capabilities R11.2): the {@link
441
+ * import('./admin-signin-controls.js').AdminStepUp} — the admin's own current TOTP on their
442
+ * `mfa:pid` budget through the replay-guarded login verifier. For `@astram/identity` only;
443
+ * exported from `@astram/guard/internal`.
444
+ */
445
+ exports.SHIELD_ADMIN_STEP_UP = Symbol('SHIELD_ADMIN_STEP_UP');
446
+ /**
447
+ * Admin sign-in controls (R11.1 unlock): the {@link
448
+ * import('./admin-signin-controls.js').AccountUnlocker} that clears one principal's login, MFA,
449
+ * recovery and CAPTCHA account counters. For `@astram/identity` only; exported from
450
+ * `@astram/guard/internal`.
451
+ */
452
+ exports.SHIELD_ACCOUNT_UNLOCKER = Symbol('SHIELD_ACCOUNT_UNLOCKER');
453
+ /**
454
+ * The switch that turns mandatory-2FA evaluation on (R2). `@astram/identity` binds it `true`
455
+ * globally when its security-policy feature is registered (`IdentityFeatureModule.forRoot`); guard
456
+ * never imports identity (ADR-0003), so the token is the seam. Exported from
457
+ * `@astram/guard/internal`.
458
+ */
459
+ exports.SHIELD_MFA_POLICY_FEATURE = Symbol('SHIELD_MFA_POLICY_FEATURE');
460
+ /**
461
+ * Auth capabilities R7 (CAPTCHA). `SHIELD_CAPTCHA_CONFIG`: the validated {@link
462
+ * import('../config/captcha-config.js').CaptchaConfig} (`null` unless `captcha: 'env'`).
463
+ * `SHIELD_CAPTCHA_VERIFIER`: the provider adapter (core `CaptchaVerifier`). `SHIELD_CAPTCHA_GATE`:
464
+ * the {@link import('./captcha-gate.js').CaptchaGate} on login / forgot / reset (`null` when off,
465
+ * so every route answers exactly as before).
466
+ */
467
+ exports.SHIELD_CAPTCHA_CONFIG = Symbol('SHIELD_CAPTCHA_CONFIG');
468
+ exports.SHIELD_CAPTCHA_VERIFIER = Symbol('SHIELD_CAPTCHA_VERIFIER');
469
+ exports.SHIELD_CAPTCHA_GATE = Symbol('SHIELD_CAPTCHA_GATE');
@@ -27,6 +27,13 @@ export interface CredentialProvisioner {
27
27
  * (`none` when no row). Empty input ⇒ empty map, no query. Duplicate ids are fine.
28
28
  */
29
29
  statesOf(principalIds: readonly string[], query?: CredentialQuery): Promise<Map<string, CredentialState>>;
30
+ /**
31
+ * The principal whose credential's login hint (`ic_identity_hint`) equals `hint` under the
32
+ * column's collation (case-insensitive), or `null`. Identity uses it to refuse a login name that
33
+ * equals another principal's legacy (non-email) hint (auth capabilities review round 1, item F).
34
+ * Optional for custom provisioners; a store error propagates.
35
+ */
36
+ hintHolderOf?(hint: string, query?: CredentialQuery): Promise<string | null>;
30
37
  }
31
38
  /** The principal or login hint already has a credential. Fixed message: no hint, no driver text. */
32
39
  export declare class CredentialExistsError extends Error {
@@ -1 +1 @@
1
- {"version":3,"file":"credential-provisioner.d.ts","sourceRoot":"","sources":["../../src/authentication/credential-provisioner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0FAA0F;AAC1F,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gCAAiC,SAAQ,wBAAwB;IAChF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,yGAAyG;AACzG,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,eAAe,CAAC,KAAK,EAAE,wBAAwB,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,gEAAgE;IAChE,iBAAiB,CACf,KAAK,EAAE,gCAAgC,EACvC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAChF;;;OAGG;IACH,QAAQ,CACN,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;CAC1C;AAED,oGAAoG;AACpG,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C"}
1
+ {"version":3,"file":"credential-provisioner.d.ts","sourceRoot":"","sources":["../../src/authentication/credential-provisioner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,0FAA0F;AAC1F,MAAM,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,gCAAiC,SAAQ,wBAAwB;IAChF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,yGAAyG;AACzG,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,qBAAqB;IACpC,8EAA8E;IAC9E,eAAe,CAAC,KAAK,EAAE,wBAAwB,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzF,gEAAgE;IAChE,iBAAiB,CACf,KAAK,EAAE,gCAAgC,EACvC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IAChF;;;OAGG;IACH,QAAQ,CACN,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;IACzC;;;;;OAKG;IACH,YAAY,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAC9E;AAED,oGAAoG;AACpG,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C"}
@@ -20,6 +20,7 @@ export declare class MariaDbCredentialProvisioner implements CredentialProvision
20
20
  provisionPassword(input: PasswordCredentialProvisionInput, query?: CredentialQuery): Promise<void>;
21
21
  stateOf(principalId: string, query?: CredentialQuery): Promise<CredentialState>;
22
22
  statesOf(principalIds: readonly string[], query?: CredentialQuery): Promise<Map<string, CredentialState>>;
23
+ hintHolderOf(hint: string, query?: CredentialQuery): Promise<string | null>;
23
24
  private validate;
24
25
  private run;
25
26
  private insert;
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-credential-provisioner.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-provisioner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gCAAgC,EACtC,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAkB7C,qBAAa,4BAA6B,YAAW,qBAAqB;IAMtE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAQ1B,eAAe,CAAC,KAAK,EAAE,wBAAwB,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxF,iBAAiB,CACrB,KAAK,EAAE,gCAAgC,EACvC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC;IAQV,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAU/E,QAAQ,CACZ,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAqBxC,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,GAAG;YAIG,MAAM;CA0BrB"}
1
+ {"version":3,"file":"mariadb-credential-provisioner.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-provisioner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gCAAgC,EACtC,MAAM,6BAA6B,CAAC;AAMrC,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAkB7C,qBAAa,4BAA6B,YAAW,qBAAqB;IAMtE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,oEAAoE;IACpE,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,IAAI,EAAE,IAAI,EACV,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAQ1B,eAAe,CAAC,KAAK,EAAE,wBAAwB,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxF,iBAAiB,CACrB,KAAK,EAAE,gCAAgC,EACvC,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC;IAQV,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;IAU/E,QAAQ,CACZ,YAAY,EAAE,SAAS,MAAM,EAAE,EAC/B,KAAK,CAAC,EAAE,eAAe,GACtB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IAqBlC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAUjF,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,GAAG;YAIG,MAAM;CA0BrB"}
@@ -65,6 +65,13 @@ class MariaDbCredentialProvisioner {
65
65
  }
66
66
  return out;
67
67
  }
68
+ async hintHolderOf(hint, query) {
69
+ if (typeof hint !== 'string' || hint === '')
70
+ return null;
71
+ const rows = (await this.run(query)(`SELECT ic_principal_id FROM ${this.table} WHERE ic_identity_hint = ? LIMIT 1`, [hint]));
72
+ const holder = rows[0]?.ic_principal_id;
73
+ return typeof holder === 'string' ? holder : null;
74
+ }
68
75
  validate(input) {
69
76
  requireText(input.principalId, 'principalId');
70
77
  requireText(input.hint, 'hint');
@@ -58,9 +58,10 @@
58
58
  * reads `shield_identity` via the runtime `shield_core` account.
59
59
  */
60
60
  import type { Pool } from 'mariadb';
61
- import type { CredentialResolver, ResolvedCredential } from '@astram/core';
61
+ import { type CredentialResolver, type ResolvedCredential } from '@astram/core';
62
62
  import type { MfaDeviceStore } from '../auth/ports/mfa-device-store.js';
63
63
  import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
64
+ import { type SignInMethod } from './sign-in-with.js';
64
65
  /** DI token for the resolver — re-exported from the shared auth tokens module. */
65
66
  export { SHIELD_CREDENTIAL_RESOLVER } from '../auth/tokens.js';
66
67
  /**
@@ -88,6 +89,8 @@ export declare class MariaDbCredentialResolver implements CredentialResolver {
88
89
  private readonly credentialTable;
89
90
  /** `<app>`.`users` (the account status JOIN). */
90
91
  private readonly usersTable;
92
+ /** `signInWith` includes `username` (R1.8): a hint without `@` may be a login name. */
93
+ private readonly loginNameEnabled;
91
94
  constructor(pool: Pool,
92
95
  /**
93
96
  * The device store whose CONFIRMED keys become the login's `totp` required factor.
@@ -98,7 +101,11 @@ export declare class MariaDbCredentialResolver implements CredentialResolver {
98
101
  /** Clock (epoch-ms) for the users-table re-probe window. */
99
102
  now?: () => number,
100
103
  /** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
101
- schemas?: FrameworkSchemaNames);
104
+ schemas?: FrameworkSchemaNames,
105
+ /** `signInWith` (R1.8); absent ⇒ `['email']`, today's lookup byte for byte. */
106
+ options?: {
107
+ readonly signInWith?: readonly SignInMethod[];
108
+ });
102
109
  /**
103
110
  * Resolve `identityHint` → {@link ResolvedCredential}, or `null` when no such
104
111
  * principal exists.
@@ -122,6 +129,20 @@ export declare class MariaDbCredentialResolver implements CredentialResolver {
122
129
  * restriction store). Any other error propagates (fail closed).
123
130
  */
124
131
  private lookup;
132
+ /**
133
+ * The normalized login name to try for `identityHint`, or `null` when the login-name path does
134
+ * not apply: `username` not enabled, the hint has `@` (an email form, R1.3), or the normalized
135
+ * text is not an allowed login name (no row could match).
136
+ */
137
+ private loginNameOf;
138
+ /**
139
+ * R1.4: ONE statement, two indexed branches — the typed text as a credential hint (PK, as
140
+ * today) and the normalized text as `users.u_login_name` (unique) joined to the credential on
141
+ * `ic_principal_id` (indexed, migration 103). Both branches carry the account status. Two
142
+ * DIFFERENT credential rows (two accounts, or one account holding two credentials) cannot be
143
+ * told apart ⇒ no row (fail closed: the caller answers the generic 401 after decoy work).
144
+ */
145
+ private lookupWithLoginName;
125
146
  /**
126
147
  * The principal's required second factors: one `totp` factor bundling every CONFIRMED
127
148
  * device key, or `[]` when none (or no device store wired). A store error propagates.
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-credential-resolver.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,kBAAkB,EAAkB,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAE7C,kFAAkF;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AA6D/D;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IAchE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG;IArBtB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAK;IAEnC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAGjB,IAAI,EAAE,IAAI;IAC3B;;;;OAIG;IACc,UAAU,CAAC,GAAE,IAAI,CAAC,cAAc,EAAE,sBAAsB,CAAC,GAAG,IAAI,aAAA;IACjF,4DAA4D;IAC3C,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAOhC;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAoCvE;;;;;;OAMG;YACW,MAAM;IAyBpB;;;OAGG;YACW,kBAAkB;CAQjC"}
1
+ {"version":3,"file":"mariadb-credential-resolver.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-credential-resolver.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAIL,KAAK,kBAAkB,EAEvB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAAqB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEzE,kFAAkF;AAClF,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AA+D/D;;;;GAIG;AACH,qBAAa,yBAA0B,YAAW,kBAAkB;IAgBhE,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,4DAA4D;IAC5D,OAAO,CAAC,QAAQ,CAAC,GAAG;IAvBtB;;;;OAIG;IACH,OAAO,CAAC,sBAAsB,CAAK;IAEnC,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,uFAAuF;IACvF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAU;gBAGxB,IAAI,EAAE,IAAI;IAC3B;;;;OAIG;IACc,UAAU,CAAC,GAAE,IAAI,CAAC,cAAc,EAAE,sBAAsB,CAAC,GAAG,IAAI,aAAA;IACjF,4DAA4D;IAC3C,GAAG,GAAE,MAAM,MAAiB;IAC7C,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAC9B,+EAA+E;IAC/E,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;KAAE;IAQ7D;;;;;;;;;;;;;OAaG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAoCvE;;;;;;OAMG;YACW,MAAM;IA2BpB;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAMnB;;;;;;OAMG;YACW,mBAAmB;IAuBjC;;;OAGG;YACW,kBAAkB;CAQjC"}
@@ -60,7 +60,9 @@
60
60
  */
61
61
  Object.defineProperty(exports, "__esModule", { value: true });
62
62
  exports.MariaDbCredentialResolver = exports.SHIELD_CREDENTIAL_RESOLVER = void 0;
63
+ const core_1 = require("@astram/core");
63
64
  const framework_schema_names_js_1 = require("../config/framework-schema-names.js");
65
+ const sign_in_with_js_1 = require("./sign-in-with.js");
64
66
  /** DI token for the resolver — re-exported from the shared auth tokens module. */
65
67
  var tokens_js_1 = require("../auth/tokens.js");
66
68
  Object.defineProperty(exports, "SHIELD_CREDENTIAL_RESOLVER", { enumerable: true, get: function () { return tokens_js_1.SHIELD_CREDENTIAL_RESOLVER; } });
@@ -118,6 +120,8 @@ class MariaDbCredentialResolver {
118
120
  credentialTable;
119
121
  /** `<app>`.`users` (the account status JOIN). */
120
122
  usersTable;
123
+ /** `signInWith` includes `username` (R1.8): a hint without `@` may be a login name. */
124
+ loginNameEnabled;
121
125
  constructor(pool,
122
126
  /**
123
127
  * The device store whose CONFIRMED keys become the login's `totp` required factor.
@@ -128,13 +132,16 @@ class MariaDbCredentialResolver {
128
132
  /** Clock (epoch-ms) for the users-table re-probe window. */
129
133
  now = Date.now,
130
134
  /** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
131
- schemas) {
135
+ schemas,
136
+ /** `signInWith` (R1.8); absent ⇒ `['email']`, today's lookup byte for byte. */
137
+ options) {
132
138
  this.pool = pool;
133
139
  this.mfaDevices = mfaDevices;
134
140
  this.now = now;
135
141
  const s = (0, framework_schema_names_js_1.schemaNamesAtConstruction)(schemas);
136
142
  this.credentialTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.identity, 'identity_credential');
137
143
  this.usersTable = (0, framework_schema_names_js_1.qualifiedTableName)(s.app, 'users');
144
+ this.loginNameEnabled = (0, sign_in_with_js_1.resolveSignInWith)(options?.signInWith).includes('username');
138
145
  }
139
146
  /**
140
147
  * Resolve `identityHint` → {@link ResolvedCredential}, or `null` when no such
@@ -191,6 +198,9 @@ class MariaDbCredentialResolver {
191
198
  async lookup(identityHint) {
192
199
  if (this.now() >= this.usersTableMissingUntil) {
193
200
  try {
201
+ const loginName = this.loginNameOf(identityHint);
202
+ if (loginName !== null)
203
+ return await this.lookupWithLoginName(identityHint, loginName);
194
204
  return (await this.pool.query(`SELECT ic_password_hash, ic_principal_id, ic_tenant_id, ic_roles, u.u_status
195
205
  FROM ${this.credentialTable}
196
206
  LEFT JOIN ${this.usersTable} u ON u.u_uuid = ic_principal_id
@@ -208,6 +218,40 @@ class MariaDbCredentialResolver {
208
218
  WHERE ic_identity_hint = ?
209
219
  LIMIT 1`, [identityHint]));
210
220
  }
221
+ /**
222
+ * The normalized login name to try for `identityHint`, or `null` when the login-name path does
223
+ * not apply: `username` not enabled, the hint has `@` (an email form, R1.3), or the normalized
224
+ * text is not an allowed login name (no row could match).
225
+ */
226
+ loginNameOf(identityHint) {
227
+ if (!this.loginNameEnabled || (0, core_1.looksLikeEmail)(identityHint))
228
+ return null;
229
+ const name = (0, core_1.normalizeLoginName)(identityHint);
230
+ return (0, core_1.isValidLoginName)(name) ? name : null;
231
+ }
232
+ /**
233
+ * R1.4: ONE statement, two indexed branches — the typed text as a credential hint (PK, as
234
+ * today) and the normalized text as `users.u_login_name` (unique) joined to the credential on
235
+ * `ic_principal_id` (indexed, migration 103). Both branches carry the account status. Two
236
+ * DIFFERENT credential rows (two accounts, or one account holding two credentials) cannot be
237
+ * told apart ⇒ no row (fail closed: the caller answers the generic 401 after decoy work).
238
+ */
239
+ async lookupWithLoginName(identityHint, loginName) {
240
+ const rows = (await this.pool.query(`SELECT ic.ic_identity_hint, ic.ic_password_hash, ic.ic_principal_id, ic.ic_tenant_id,
241
+ ic.ic_roles, u.u_status
242
+ FROM ${this.credentialTable} ic
243
+ LEFT JOIN ${this.usersTable} u ON u.u_uuid = ic.ic_principal_id
244
+ WHERE ic.ic_identity_hint = ?
245
+ UNION ALL
246
+ SELECT ic.ic_identity_hint, ic.ic_password_hash, ic.ic_principal_id, ic.ic_tenant_id,
247
+ ic.ic_roles, u.u_status
248
+ FROM ${this.usersTable} u
249
+ JOIN ${this.credentialTable} ic ON ic.ic_principal_id = u.u_uuid
250
+ WHERE u.u_login_name = ?
251
+ LIMIT 3`, [identityHint, loginName]));
252
+ const distinct = new Set(rows.map((r) => r.ic_identity_hint));
253
+ return distinct.size === 1 ? [rows[0]] : [];
254
+ }
211
255
  /**
212
256
  * The principal's required second factors: one `totp` factor bundling every CONFIRMED
213
257
  * device key, or `[]` when none (or no device store wired). A store error propagates.
@@ -15,6 +15,13 @@ export declare class MariaDbMfaDeviceStore implements MfaDeviceStore {
15
15
  confirm(principalId: string, deviceId: string): Promise<boolean>;
16
16
  rename(principalId: string, deviceId: string, name: string): Promise<MfaDevice | null>;
17
17
  remove(principalId: string, deviceId: string): Promise<boolean>;
18
+ /**
19
+ * R2.7: remove `deviceId` unless it is the principal's last CONFIRMED device, atomically. One
20
+ * transaction: lock the principal's confirmed rows (`FOR UPDATE`, so a concurrent removal waits
21
+ * and then re-reads the committed state), re-count, refuse (`last_device`, rolled back) when this
22
+ * delete would leave none, else delete and commit. Errors roll back and propagate.
23
+ */
24
+ removeKeepingOne(principalId: string, deviceId: string): Promise<'removed' | 'not_found' | 'last_device'>;
18
25
  countConfirmed(principalId: string): Promise<number>;
19
26
  stampUsed(secret: string): Promise<void>;
20
27
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mariadb-mfa-device-store.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-mfa-device-store.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAuB7C,qBAAa,qBAAsB,YAAW,cAAc;IAKxD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAGZ,IAAI,EAAE,IAAI;IAC3B,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAK1B,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IASlD,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS5D,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASxE,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBzE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkBhE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAetF,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ/D,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASpD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAU/C"}
1
+ {"version":3,"file":"mariadb-mfa-device-store.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-mfa-device-store.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAuB7C,qBAAa,qBAAsB,YAAW,cAAc;IAKxD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJvB,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAGZ,IAAI,EAAE,IAAI;IAC3B,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAK1B,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC;IASlD,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAS5D,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IASxE,KAAK,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBzE,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkBhE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAetF,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQrE;;;;;OAKG;IACG,gBAAgB,CACpB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,GAAG,WAAW,GAAG,aAAa,CAAC;IA+B7C,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASpD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAU/C"}
@@ -95,6 +95,38 @@ class MariaDbMfaDeviceStore {
95
95
  const res = (await this.pool.query(`DELETE FROM ${this.table} WHERE md_id = ? AND md_principal_id = ?`, [deviceId, principalId]));
96
96
  return res.affectedRows === 1;
97
97
  }
98
+ /**
99
+ * R2.7: remove `deviceId` unless it is the principal's last CONFIRMED device, atomically. One
100
+ * transaction: lock the principal's confirmed rows (`FOR UPDATE`, so a concurrent removal waits
101
+ * and then re-reads the committed state), re-count, refuse (`last_device`, rolled back) when this
102
+ * delete would leave none, else delete and commit. Errors roll back and propagate.
103
+ */
104
+ async removeKeepingOne(principalId, deviceId) {
105
+ const conn = await this.pool.getConnection();
106
+ try {
107
+ await conn.beginTransaction();
108
+ try {
109
+ const rows = (await conn.query(`SELECT md_id FROM ${this.table}
110
+ WHERE md_principal_id = ? AND md_confirmed_at_ms IS NOT NULL
111
+ FOR UPDATE`, [principalId]));
112
+ const confirmed = rows.map((r) => String(r.md_id).toLowerCase());
113
+ if (confirmed.length === 1 && confirmed[0] === deviceId.toLowerCase()) {
114
+ await conn.rollback();
115
+ return 'last_device';
116
+ }
117
+ const res = (await conn.query(`DELETE FROM ${this.table} WHERE md_id = ? AND md_principal_id = ?`, [deviceId, principalId]));
118
+ await conn.commit();
119
+ return res.affectedRows === 1 ? 'removed' : 'not_found';
120
+ }
121
+ catch (err) {
122
+ await conn.rollback().catch(() => undefined);
123
+ throw err;
124
+ }
125
+ }
126
+ finally {
127
+ conn.release();
128
+ }
129
+ }
98
130
  async countConfirmed(principalId) {
99
131
  const rows = (await this.pool.query(`SELECT COUNT(*) AS c FROM ${this.table}
100
132
  WHERE md_principal_id = ? AND md_confirmed_at_ms IS NOT NULL`, [principalId]));
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `MariaDbMfaRequirementSource` — the guard's MariaDB adapter for core's
3
+ * {@link MfaRequirementSource} port (auth capabilities B-1, R2.4, R2.8, R2.9). Guard never imports
4
+ * identity (ADR-0003): it reads identity's tables directly with its runtime account, as
5
+ * `permission-facts.adapter.ts` does.
6
+ *
7
+ * - scope chain: `<authz>.scope_closure` ancestors of the node (the node itself always included);
8
+ * - policies: `<app>.security_policy` rows on the chain, plus EVERY platform-floor row;
9
+ * - enrolled: a CONFIRMED `<identity>.mfa_device` row (R2.8: every device is TOTP today; pending
10
+ * devices and recovery codes never count);
11
+ * - the grace clock: `<identity>.mfa_requirement_state`, kept at the EARLIER value (`LEAST`).
12
+ *
13
+ * Lifecycle reads only (sign-in, a permission-version move, device removal): never on an unchanged
14
+ * version (the caller's stamp check guarantees it). Database names come from the resolved framework
15
+ * schemas, never a literal. The adapter is built only when the policy feature is wired, so a
16
+ * missing policy table (identity migration 104 not applied) is an error like any other: it
17
+ * propagates and the caller fails closed (review round 1, item D). A missing clock table still
18
+ * reads as "none stored" (the first clock write then fails closed). Every other error propagates.
19
+ */
20
+ import type { MfaPolicy, MfaRequirementSource } from '@astram/core';
21
+ import type { MfaStatusBatchSource } from '../session/mfa-requirement-service.js';
22
+ import { type FrameworkSchemaNames } from '../config/framework-schema-names.js';
23
+ /** The one pool method the adapter uses. */
24
+ export interface MfaSourcePool {
25
+ query(sql: string, params?: unknown[]): Promise<unknown>;
26
+ }
27
+ export declare class MariaDbMfaRequirementSource implements MfaRequirementSource, MfaStatusBatchSource {
28
+ private readonly pool;
29
+ private readonly closureTable;
30
+ private readonly policyTable;
31
+ private readonly deviceTable;
32
+ private readonly stateTable;
33
+ private readonly membershipTable;
34
+ constructor(pool: MfaSourcePool,
35
+ /** Framework database names; absent ⇒ resolved from `SHIELD_DB_NAMESPACE` here, once. */
36
+ schemas?: FrameworkSchemaNames);
37
+ scopeChainOf(nodeId: string): Promise<readonly string[]>;
38
+ loadPolicies(nodeIds: readonly string[]): Promise<readonly MfaPolicy[]>;
39
+ hasConfirmedFactor(principalId: string): Promise<boolean>;
40
+ getRequiredSince(principalId: string): Promise<number | null>;
41
+ setRequiredSince(principalId: string, sinceMs: number): Promise<number>;
42
+ /** R11.3: the ids among `principalIds` with a CONFIRMED device. One `IN` query; none ⇒ no query. */
43
+ confirmedFactorsOf(principalIds: readonly string[]): Promise<ReadonlySet<string>>;
44
+ /** R11.3: stored grace clocks for `principalIds`. One `IN` query; no table ⇒ none. */
45
+ requiredSinceOf(principalIds: readonly string[]): Promise<ReadonlyMap<string, number>>;
46
+ /**
47
+ * Review item E2: the principal's ACTIVE membership nodes (`<app>.company_membership`,
48
+ * `cm_is_active = 1`, the predicate the users page uses), for the sign-in clock check. One query.
49
+ */
50
+ activeNodesOf(principalId: string): Promise<readonly string[]>;
51
+ /** R11.3: each node with its ancestors (the node itself always included). One `IN` query. */
52
+ scopeChainsOf(nodeIds: readonly string[]): Promise<ReadonlyMap<string, readonly string[]>>;
53
+ clearRequiredSince(principalId: string): Promise<void>;
54
+ }
55
+ //# sourceMappingURL=mariadb-mfa-requirement-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mariadb-mfa-requirement-source.d.ts","sourceRoot":"","sources":["../../src/authentication/mariadb-mfa-requirement-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAA4B,MAAM,cAAc,CAAC;AAC9F,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAClF,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,qCAAqC,CAAC;AAE7C,4CAA4C;AAC5C,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D;AAsCD,qBAAa,2BAA4B,YAAW,oBAAoB,EAAE,oBAAoB;IAQ1F,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPvB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;gBAGtB,IAAI,EAAE,aAAa;IACpC,yFAAyF;IACzF,OAAO,CAAC,EAAE,oBAAoB;IAU1B,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IAUxD,YAAY,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC;IAevE,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQzD,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAiB7D,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAa7E,oGAAoG;IAC9F,kBAAkB,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAavF,sFAAsF;IAChF,eAAe,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IA4B5F;;;OAGG;IACG,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC;IASpE,6FAA6F;IACvF,aAAa,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;IAgB1F,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAS7D"}