@astram/guard 0.7.0 → 0.9.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.
- package/dist/auth/account.controller.d.ts +27 -5
- package/dist/auth/account.controller.d.ts.map +1 -1
- package/dist/auth/account.controller.js +99 -26
- package/dist/auth/auth.controller.d.ts +130 -21
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +537 -143
- package/dist/auth/auth.module.d.ts +15 -1
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +212 -30
- package/dist/auth/connections.d.ts +1 -1
- package/dist/auth/connections.d.ts.map +1 -1
- package/dist/auth/connections.js +14 -3
- package/dist/auth/default-session-facts-projector.d.ts +26 -0
- package/dist/auth/default-session-facts-projector.d.ts.map +1 -0
- package/dist/auth/default-session-facts-projector.js +21 -0
- package/dist/auth/mfa-step-up.d.ts +32 -0
- package/dist/auth/mfa-step-up.d.ts.map +1 -0
- package/dist/auth/mfa-step-up.js +109 -0
- package/dist/auth/pending-mfa-store.d.ts +50 -52
- package/dist/auth/pending-mfa-store.d.ts.map +1 -1
- package/dist/auth/pending-mfa-store.js +103 -61
- package/dist/auth/ports/session-facts-projector.d.ts +5 -0
- package/dist/auth/ports/session-facts-projector.d.ts.map +1 -1
- package/dist/auth/rate-limit/canonical-hint.d.ts +24 -0
- package/dist/auth/rate-limit/canonical-hint.d.ts.map +1 -0
- package/dist/auth/rate-limit/canonical-hint.js +63 -0
- package/dist/auth/rate-limit/trust-proxy-check.d.ts +46 -0
- package/dist/auth/rate-limit/trust-proxy-check.d.ts.map +1 -0
- package/dist/auth/rate-limit/trust-proxy-check.js +94 -0
- package/dist/auth/tokens.d.ts +70 -11
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +71 -12
- package/dist/authentication/log-email-sender.d.ts +9 -4
- package/dist/authentication/log-email-sender.d.ts.map +1 -1
- package/dist/authentication/mariadb-api-key-store.d.ts +5 -1
- package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-api-key-store.js +43 -7
- package/dist/authentication/mariadb-credential-resolver.d.ts +79 -65
- package/dist/authentication/mariadb-credential-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-credential-resolver.js +116 -73
- package/dist/authentication/mariadb-credential-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-or-totp-verifier.d.ts +21 -3
- package/dist/authentication/mariadb-or-totp-verifier.d.ts.map +1 -1
- package/dist/authentication/mariadb-or-totp-verifier.js +37 -6
- package/dist/authentication/mariadb-profile-resolver.d.ts.map +1 -1
- package/dist/authentication/mariadb-profile-writer.d.ts.map +1 -1
- package/dist/authentication/mariadb-session-sweeper.d.ts +14 -1
- package/dist/authentication/mariadb-session-sweeper.d.ts.map +1 -1
- package/dist/authentication/mariadb-session-sweeper.js +29 -11
- package/dist/authentication/noop-email-sender.d.ts +12 -0
- package/dist/authentication/noop-email-sender.d.ts.map +1 -0
- package/dist/authentication/noop-email-sender.js +9 -0
- package/dist/authz/cross-tenant-grant-writer.d.ts.map +1 -1
- package/dist/authz/manifest-operation-authority-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js +3 -1
- package/dist/authz/mariadb-policy-store.js +1 -1
- package/dist/authz/mariadb-record-rule-adapter.d.ts.map +1 -1
- package/dist/authz/mariadb-record-rule-adapter.js +1 -4
- package/dist/authz/permission-version-cache.d.ts +38 -0
- package/dist/authz/permission-version-cache.d.ts.map +1 -0
- package/dist/authz/permission-version-cache.js +81 -0
- package/dist/authz/wire-record-authorization.d.ts +25 -1
- package/dist/authz/wire-record-authorization.d.ts.map +1 -1
- package/dist/authz/wire-record-authorization.js +55 -2
- package/dist/completeness/catalog-reconciler.d.ts.map +1 -1
- package/dist/config/app-config.d.ts +36 -0
- package/dist/config/app-config.d.ts.map +1 -1
- package/dist/config/app-config.js +44 -4
- package/dist/encryption/local-kek-crypto.d.ts.map +1 -1
- package/dist/enforcement/enforcement.module.d.ts.map +1 -1
- package/dist/enforcement/enforcement.module.js +9 -0
- package/dist/enforcement/engine-error.filter.d.ts +57 -0
- package/dist/enforcement/engine-error.filter.d.ts.map +1 -0
- package/dist/enforcement/engine-error.filter.js +118 -0
- package/dist/enforcement/global-enforcement.guard.d.ts +29 -2
- package/dist/enforcement/global-enforcement.guard.d.ts.map +1 -1
- package/dist/enforcement/global-enforcement.guard.js +143 -5
- package/dist/identity/auth-facts.controller.d.ts.map +1 -1
- package/dist/identity/permission-facts.adapter.d.ts.map +1 -1
- package/dist/identity/permission-facts.adapter.js +2 -3
- package/dist/index.d.ts +13 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -28
- package/dist/internal.d.ts +12 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +16 -0
- package/dist/module/shield.module.d.ts +9 -2
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +47 -38
- package/dist/request-auth/session-facts-als.d.ts +16 -1
- package/dist/request-auth/session-facts-als.d.ts.map +1 -1
- package/dist/request-auth/session-facts-als.js +23 -0
- package/dist/request-auth/session-token-binder.d.ts +166 -10
- package/dist/request-auth/session-token-binder.d.ts.map +1 -1
- package/dist/request-auth/session-token-binder.js +408 -28
- package/dist/secrets/encrypted-file-secrets.d.ts.map +1 -1
- package/dist/secrets/encrypted-file-secrets.js +9 -3
- package/dist/session/hot-revoking-session-record-store.d.ts +46 -0
- package/dist/session/hot-revoking-session-record-store.d.ts.map +1 -0
- package/dist/session/hot-revoking-session-record-store.js +58 -0
- package/dist/session/index.d.ts +3 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +8 -1
- package/dist/session/mariadb-session-record-store.d.ts +6 -0
- package/dist/session/mariadb-session-record-store.d.ts.map +1 -1
- package/dist/session/mariadb-session-record-store.js +16 -1
- package/dist/session/session-lifetimes.d.ts +30 -0
- package/dist/session/session-lifetimes.d.ts.map +1 -0
- package/dist/session/session-lifetimes.js +21 -0
- package/dist/session/session-restriction-store.d.ts +43 -0
- package/dist/session/session-restriction-store.d.ts.map +1 -0
- package/dist/session/session-restriction-store.js +35 -0
- package/package.json +14 -3
- package/dist/auth/account.controller.js.map +0 -1
- package/dist/auth/auth.controller.js.map +0 -1
- package/dist/auth/auth.module.js.map +0 -1
- package/dist/auth/connections.js.map +0 -1
- package/dist/auth/dto.js.map +0 -1
- package/dist/auth/pending-mfa-store.js.map +0 -1
- package/dist/auth/persistence.module.js.map +0 -1
- package/dist/auth/ports/audit-sink.js.map +0 -1
- package/dist/auth/ports/credential-writer.js.map +0 -1
- package/dist/auth/ports/email-sender.js.map +0 -1
- package/dist/auth/ports/membership-checker.js.map +0 -1
- package/dist/auth/ports/mfa-device-store.js.map +0 -1
- package/dist/auth/ports/profile-writer.js.map +0 -1
- package/dist/auth/ports/recovery-code-store.js.map +0 -1
- package/dist/auth/ports/reset-token-store.js.map +0 -1
- package/dist/auth/ports/scope-subtree-resolver.js.map +0 -1
- package/dist/auth/ports/session-facts-projector.js.map +0 -1
- package/dist/auth/ports/session-sweeper.js.map +0 -1
- package/dist/auth/profile-resolver.js.map +0 -1
- package/dist/auth/rate-limit/attempt-limiter.js.map +0 -1
- package/dist/auth/tokens.js.map +0 -1
- package/dist/auth/totp-enrollment.js.map +0 -1
- package/dist/authentication/argon2id-password-hasher.js.map +0 -1
- package/dist/authentication/log-email-sender.js.map +0 -1
- package/dist/authentication/mariadb-credential-resolver.js.map +0 -1
- package/dist/authentication/mariadb-credential-writer.js.map +0 -1
- package/dist/authentication/mariadb-mfa-device-store.js.map +0 -1
- package/dist/authentication/mariadb-or-totp-verifier.js.map +0 -1
- package/dist/authentication/mariadb-profile-resolver.js.map +0 -1
- package/dist/authentication/mariadb-profile-writer.js.map +0 -1
- package/dist/authentication/mariadb-recovery-code-store.js.map +0 -1
- package/dist/authentication/mariadb-reset-token-store.js.map +0 -1
- package/dist/authentication/mariadb-session-sweeper.js.map +0 -1
- package/dist/authz/cross-tenant-grant-writer.js.map +0 -1
- package/dist/authz/list-scope-sql.js.map +0 -1
- package/dist/authz/manifest-operation-authority-adapter.js.map +0 -1
- package/dist/authz/mariadb-cross-tenant-grant-adapter.js.map +0 -1
- package/dist/authz/mariadb-list-scope-adapter.js.map +0 -1
- package/dist/authz/mariadb-policy-store.js.map +0 -1
- package/dist/authz/mariadb-record-rule-adapter.js.map +0 -1
- package/dist/authz/mariadb-record-share-adapter.js.map +0 -1
- package/dist/authz/record-share-cascade.js.map +0 -1
- package/dist/authz/record-share-writer.js.map +0 -1
- package/dist/authz/wire-record-authorization.js.map +0 -1
- package/dist/boot/boot-sequence.js.map +0 -1
- package/dist/boot/boot.module.js.map +0 -1
- package/dist/completeness/catalog-reconciler.js.map +0 -1
- package/dist/completeness/operation-inventory-scanner.js.map +0 -1
- package/dist/completeness/ports/catalog-store.js.map +0 -1
- package/dist/config/app-config.js.map +0 -1
- package/dist/config/config.module.js.map +0 -1
- package/dist/enforcement/classification.decorator.js.map +0 -1
- package/dist/enforcement/core-seam.js.map +0 -1
- package/dist/enforcement/enforcement.module.js.map +0 -1
- package/dist/enforcement/global-enforcement.guard.js.map +0 -1
- package/dist/enforcement/reenroll-allowlist.js.map +0 -1
- package/dist/identity/auth-facts.controller.js.map +0 -1
- package/dist/identity/identity-actions.js.map +0 -1
- package/dist/identity/module-registry.bridge.js.map +0 -1
- package/dist/identity/permission-facts.adapter.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/migrations-dir.js.map +0 -1
- package/dist/module/shield-tokens.js.map +0 -1
- package/dist/module/shield.module.js.map +0 -1
- package/dist/request-auth/request-principal.js.map +0 -1
- package/dist/request-auth/session-facts-als.js.map +0 -1
- package/dist/request-auth/session-token-binder.js.map +0 -1
- package/dist/request-auth/tokens.js.map +0 -1
- package/dist/scope/closure-builder.js.map +0 -1
- package/dist/session/crypto-adapters.js.map +0 -1
- package/dist/session/index.js.map +0 -1
- package/dist/session/mariadb-session-record-store.js.map +0 -1
- package/dist/session/redis-hot-session-store.js.map +0 -1
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import type { FactorVerifier, PasswordHasher } from '@astram/core';
|
|
46
46
|
import type { SessionTokenBinder } from '../request-auth/session-token-binder.js';
|
|
47
|
+
import type { SessionRestrictionStore } from '../session/session-restriction-store.js';
|
|
48
|
+
import type { AttemptLimiter } from './rate-limit/attempt-limiter.js';
|
|
47
49
|
import type { CredentialWriter } from './ports/credential-writer.js';
|
|
48
50
|
import type { MfaDevice, MfaDeviceStore } from './ports/mfa-device-store.js';
|
|
49
51
|
import type { CurrentUserProfile, ProfileResolver } from './profile-resolver.js';
|
|
@@ -60,7 +62,11 @@ export declare class AccountController {
|
|
|
60
62
|
private readonly profileResolver?;
|
|
61
63
|
private readonly profileWriter?;
|
|
62
64
|
private readonly mfaDevices?;
|
|
63
|
-
|
|
65
|
+
private readonly sessionRestrictions?;
|
|
66
|
+
private readonly loginTotpVerifier?;
|
|
67
|
+
private readonly mfaLimiter?;
|
|
68
|
+
private readonly loginLimiter?;
|
|
69
|
+
constructor(binder?: SessionTokenBinder | undefined, hasher?: PasswordHasher | undefined, writer?: (CredentialWriter | null) | undefined, sweeper?: (SessionSweeper | null) | undefined, recoveryCodes?: (RecoveryCodeStore | null) | undefined, totpVerifier?: (FactorVerifier | null) | undefined, profileResolver?: (ProfileResolver | null) | undefined, profileWriter?: (ProfileWriter | null) | undefined, mfaDevices?: (MfaDeviceStore | null) | undefined, sessionRestrictions?: (SessionRestrictionStore | null) | undefined, loginTotpVerifier?: (FactorVerifier | null) | undefined, mfaLimiter?: (AttemptLimiter | null) | undefined, loginLimiter?: (AttemptLimiter | null) | undefined);
|
|
64
70
|
/**
|
|
65
71
|
* Resolve a human-readable otpauth account LABEL for the caller (email, else display
|
|
66
72
|
* name) via the profile resolver. Best-effort: no resolver / null profile / a thrown
|
|
@@ -109,8 +115,9 @@ export declare class AccountController {
|
|
|
109
115
|
* • no/invalid token → 401 (generic)
|
|
110
116
|
* • newPassword shorter than the minimum → 422 `{ error: 'weak_password' }`
|
|
111
117
|
* • writer/hasher seam absent → 501 `{ error: 'not_available' }`
|
|
112
|
-
* • wrong current password → 401 (generic, no write)
|
|
113
|
-
* •
|
|
118
|
+
* • wrong current password → 401 (generic, no write; counted)
|
|
119
|
+
* • account login budget exhausted → 401 (generic, not verified, no write)
|
|
120
|
+
* • success → 204 (no body; counter reset)
|
|
114
121
|
*/
|
|
115
122
|
changePassword(authorization: string | undefined, body: {
|
|
116
123
|
currentPassword?: string;
|
|
@@ -154,10 +161,25 @@ export declare class AccountController {
|
|
|
154
161
|
private replaceRecoveryCodes;
|
|
155
162
|
/**
|
|
156
163
|
* Verify a presented `code` against ANY of the caller's CONFIRMED device secrets
|
|
157
|
-
* (TOTP)
|
|
158
|
-
*
|
|
164
|
+
* (TOTP) through the replay-guarded login verifier: the secrets are bundled
|
|
165
|
+
* newline-joined (the `OrTotpVerifier` contract) and an accepted code is claimed in
|
|
166
|
+
* Redis, so it cannot be reused on this or any other MFA path inside the window. A
|
|
167
|
+
* Redis error on the claim → 503 (fail closed; never an unguarded accept, never a
|
|
168
|
+
* fall-through to the recovery-code check). Guarded verifier not wired → `false`.
|
|
169
|
+
* NEVER logs the code.
|
|
159
170
|
*/
|
|
160
171
|
private verifyAgainstAnyDevice;
|
|
172
|
+
/**
|
|
173
|
+
* The shared second-factor proof for the code-gated account routes (Phase 8 #1),
|
|
174
|
+
* mirroring `/auth/mfa`: the `mfa:pid:<principalId>` budget is checked BEFORE any TOTP
|
|
175
|
+
* or recovery-code verification (so a locked caller never triggers the Argon2
|
|
176
|
+
* recovery scan) → a TOTP from any confirmed device, else a recovery code (consumed) →
|
|
177
|
+
* a failed proof records a failure (400), a good one resets the counter. Locked → 429.
|
|
178
|
+
* The limiter fails open on a Redis blip (its documented posture); the TOTP replay
|
|
179
|
+
* claim fails closed (503), and the session itself is Redis-bound, so a Redis outage
|
|
180
|
+
* cannot open this path.
|
|
181
|
+
*/
|
|
182
|
+
private requireMfaProof;
|
|
161
183
|
/**
|
|
162
184
|
* `GET /account/mfa/devices` — list the caller's CONFIRMED authenticator devices.
|
|
163
185
|
* `mfaEnabled` reflects whether any confirmed device exists. Pending (unconfirmed)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"account.controller.d.ts","sourceRoot":"","sources":["../../src/auth/account.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAiBH,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAa,MAAM,cAAc,CAAC;AAI9E,OAAO,KAAK,EAEV,kBAAkB,EACnB,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"account.controller.d.ts","sourceRoot":"","sources":["../../src/auth/account.controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,6 +62,7 @@ const classification_decorator_js_1 = require("../enforcement/classification.dec
|
|
|
62
62
|
const request_principal_js_1 = require("../request-auth/request-principal.js");
|
|
63
63
|
const tokens_js_1 = require("../request-auth/tokens.js");
|
|
64
64
|
const tokens_js_2 = require("./tokens.js");
|
|
65
|
+
const canonical_hint_js_1 = require("./rate-limit/canonical-hint.js");
|
|
65
66
|
const totp_enrollment_js_1 = require("./totp-enrollment.js");
|
|
66
67
|
/** Max length of a device name (matches the `md_name VARCHAR(120)` column). */
|
|
67
68
|
const MAX_DEVICE_NAME_LEN = 120;
|
|
@@ -103,6 +104,18 @@ function notAvailable() {
|
|
|
103
104
|
function badRequest() {
|
|
104
105
|
throw new common_1.HttpException({ error: 'invalid_request' }, common_1.HttpStatus.BAD_REQUEST);
|
|
105
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* The 429 for a caller whose proof budget is exhausted (Phase 8 #1). These routes are
|
|
109
|
+
* self-scoped and already authenticated, so the throttle signal reveals nothing about
|
|
110
|
+
* another account; a 401 here would read as "session expired" to the client's refresh logic.
|
|
111
|
+
*/
|
|
112
|
+
function tooManyRequests() {
|
|
113
|
+
throw new common_1.HttpException({ error: 'too_many_requests' }, common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
114
|
+
}
|
|
115
|
+
/** The retryable 503 when the replay guard (Redis) cannot answer: fail closed, no mutation. */
|
|
116
|
+
function retryableUnavailable() {
|
|
117
|
+
throw new common_1.HttpException({ error: 'authentication_unavailable' }, common_1.HttpStatus.SERVICE_UNAVAILABLE);
|
|
118
|
+
}
|
|
106
119
|
/**
|
|
107
120
|
* The 422 for a semantically invalid profile edit — an empty change (`no_changes`)
|
|
108
121
|
* or a malformed email (`invalid_email`). Distinct from {@link badRequest}: this is
|
|
@@ -122,7 +135,11 @@ let AccountController = class AccountController {
|
|
|
122
135
|
profileResolver;
|
|
123
136
|
profileWriter;
|
|
124
137
|
mfaDevices;
|
|
125
|
-
|
|
138
|
+
sessionRestrictions;
|
|
139
|
+
loginTotpVerifier;
|
|
140
|
+
mfaLimiter;
|
|
141
|
+
loginLimiter;
|
|
142
|
+
constructor(binder, hasher, writer, sweeper, recoveryCodes, totpVerifier, profileResolver, profileWriter, mfaDevices, sessionRestrictions, loginTotpVerifier, mfaLimiter, loginLimiter) {
|
|
126
143
|
this.binder = binder;
|
|
127
144
|
this.hasher = hasher;
|
|
128
145
|
this.writer = writer;
|
|
@@ -132,6 +149,10 @@ let AccountController = class AccountController {
|
|
|
132
149
|
this.profileResolver = profileResolver;
|
|
133
150
|
this.profileWriter = profileWriter;
|
|
134
151
|
this.mfaDevices = mfaDevices;
|
|
152
|
+
this.sessionRestrictions = sessionRestrictions;
|
|
153
|
+
this.loginTotpVerifier = loginTotpVerifier;
|
|
154
|
+
this.mfaLimiter = mfaLimiter;
|
|
155
|
+
this.loginLimiter = loginLimiter;
|
|
135
156
|
}
|
|
136
157
|
/**
|
|
137
158
|
* Resolve a human-readable otpauth account LABEL for the caller (email, else display
|
|
@@ -218,8 +239,9 @@ let AccountController = class AccountController {
|
|
|
218
239
|
* • no/invalid token → 401 (generic)
|
|
219
240
|
* • newPassword shorter than the minimum → 422 `{ error: 'weak_password' }`
|
|
220
241
|
* • writer/hasher seam absent → 501 `{ error: 'not_available' }`
|
|
221
|
-
* • wrong current password → 401 (generic, no write)
|
|
222
|
-
* •
|
|
242
|
+
* • wrong current password → 401 (generic, no write; counted)
|
|
243
|
+
* • account login budget exhausted → 401 (generic, not verified, no write)
|
|
244
|
+
* • success → 204 (no body; counter reset)
|
|
223
245
|
*/
|
|
224
246
|
async changePassword(authorization, body) {
|
|
225
247
|
const claims = await this.requireClaims(authorization);
|
|
@@ -238,14 +260,25 @@ let AccountController = class AccountController {
|
|
|
238
260
|
}
|
|
239
261
|
const writer = this.writer;
|
|
240
262
|
const hasher = this.hasher;
|
|
263
|
+
// Phase 8 #8: the current-password check spends the SAME per-account budget as
|
|
264
|
+
// password login (`login:pid:<principalId>`, shared counter): checked BEFORE the
|
|
265
|
+
// verify (a locked account never reaches the hasher), a wrong current password records
|
|
266
|
+
// a failure, and a success resets it — exactly as a completed login does. Locked → the
|
|
267
|
+
// same generic 401 as a wrong password (no oracle).
|
|
268
|
+
const loginKey = (0, canonical_hint_js_1.loginHintKey)('', claims.id);
|
|
269
|
+
if (this.loginLimiter != null && !(await this.loginLimiter.check(loginKey))) {
|
|
270
|
+
unauthorized();
|
|
271
|
+
}
|
|
241
272
|
// Verify the current password: read the stored hash BY PRINCIPAL ID via the writer
|
|
242
273
|
// seam's `getPasswordHash` (change-password holds the principal id from the token,
|
|
243
274
|
// whereas CredentialResolver keys by identityHint). A missing hash or a mismatch is
|
|
244
275
|
// the SAME generic 401 (no oracle), and no write happens.
|
|
245
276
|
const stored = await writer.getPasswordHash(claims.id);
|
|
246
277
|
if (stored === null || !(await hasher.verify(stored, currentPassword))) {
|
|
278
|
+
await this.loginLimiter?.recordFailure(loginKey);
|
|
247
279
|
unauthorized();
|
|
248
280
|
}
|
|
281
|
+
await this.loginLimiter?.reset(loginKey);
|
|
249
282
|
const newHash = await hasher.hash(newPassword);
|
|
250
283
|
await writer.setPassword(claims.id, newHash);
|
|
251
284
|
// Revoke the caller's OTHER sessions (keep the current one live). Best-effort:
|
|
@@ -290,9 +323,7 @@ let AccountController = class AccountController {
|
|
|
290
323
|
this.enforceReenroll(claims, false);
|
|
291
324
|
// Normalize: treat only non-empty string fields as a real change (trim so a
|
|
292
325
|
// whitespace-only value is not a change).
|
|
293
|
-
const name = typeof body?.name === 'string' && body.name.trim().length > 0
|
|
294
|
-
? body.name.trim()
|
|
295
|
-
: undefined;
|
|
326
|
+
const name = typeof body?.name === 'string' && body.name.trim().length > 0 ? body.name.trim() : undefined;
|
|
296
327
|
const email = typeof body?.email === 'string' && body.email.trim().length > 0
|
|
297
328
|
? body.email.trim()
|
|
298
329
|
: undefined;
|
|
@@ -339,16 +370,52 @@ let AccountController = class AccountController {
|
|
|
339
370
|
}
|
|
340
371
|
/**
|
|
341
372
|
* Verify a presented `code` against ANY of the caller's CONFIRMED device secrets
|
|
342
|
-
* (TOTP)
|
|
343
|
-
*
|
|
373
|
+
* (TOTP) through the replay-guarded login verifier: the secrets are bundled
|
|
374
|
+
* newline-joined (the `OrTotpVerifier` contract) and an accepted code is claimed in
|
|
375
|
+
* Redis, so it cannot be reused on this or any other MFA path inside the window. A
|
|
376
|
+
* Redis error on the claim → 503 (fail closed; never an unguarded accept, never a
|
|
377
|
+
* fall-through to the recovery-code check). Guarded verifier not wired → `false`.
|
|
378
|
+
* NEVER logs the code.
|
|
344
379
|
*/
|
|
345
380
|
async verifyAgainstAnyDevice(store, principalId, code) {
|
|
381
|
+
if (this.loginTotpVerifier === undefined || this.loginTotpVerifier === null)
|
|
382
|
+
return false;
|
|
346
383
|
const secrets = await store.listConfirmedSecrets(principalId);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
384
|
+
if (secrets.length === 0)
|
|
385
|
+
return false;
|
|
386
|
+
try {
|
|
387
|
+
const verdict = await this.loginTotpVerifier.verify({ factor: 'totp', payload: { code } }, secrets.join('\n'));
|
|
388
|
+
return verdict.satisfied;
|
|
350
389
|
}
|
|
351
|
-
|
|
390
|
+
catch {
|
|
391
|
+
retryableUnavailable();
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The shared second-factor proof for the code-gated account routes (Phase 8 #1),
|
|
396
|
+
* mirroring `/auth/mfa`: the `mfa:pid:<principalId>` budget is checked BEFORE any TOTP
|
|
397
|
+
* or recovery-code verification (so a locked caller never triggers the Argon2
|
|
398
|
+
* recovery scan) → a TOTP from any confirmed device, else a recovery code (consumed) →
|
|
399
|
+
* a failed proof records a failure (400), a good one resets the counter. Locked → 429.
|
|
400
|
+
* The limiter fails open on a Redis blip (its documented posture); the TOTP replay
|
|
401
|
+
* claim fails closed (503), and the session itself is Redis-bound, so a Redis outage
|
|
402
|
+
* cannot open this path.
|
|
403
|
+
*/
|
|
404
|
+
async requireMfaProof(store, principalId, code) {
|
|
405
|
+
const mfaKey = `mfa:pid:${principalId}`;
|
|
406
|
+
if (this.mfaLimiter != null && !(await this.mfaLimiter.check(mfaKey))) {
|
|
407
|
+
tooManyRequests();
|
|
408
|
+
}
|
|
409
|
+
const byTotp = await this.verifyAgainstAnyDevice(store, principalId, code);
|
|
410
|
+
const byRecovery = !byTotp &&
|
|
411
|
+
this.recoveryCodes !== undefined &&
|
|
412
|
+
this.recoveryCodes !== null &&
|
|
413
|
+
(await this.recoveryCodes.consume(principalId, code));
|
|
414
|
+
if (!byTotp && !byRecovery) {
|
|
415
|
+
await this.mfaLimiter?.recordFailure(mfaKey);
|
|
416
|
+
badRequest();
|
|
417
|
+
}
|
|
418
|
+
await this.mfaLimiter?.reset(mfaKey);
|
|
352
419
|
}
|
|
353
420
|
/**
|
|
354
421
|
* `GET /account/mfa/devices` — list the caller's CONFIRMED authenticator devices.
|
|
@@ -446,6 +513,11 @@ let AccountController = class AccountController {
|
|
|
446
513
|
if (claims.mustReenroll === true && this.binder !== undefined && this.binder !== null) {
|
|
447
514
|
await this.binder.clearReenroll(claims.sessionId);
|
|
448
515
|
}
|
|
516
|
+
// Durable side (Phase 3B review P1). Best-effort: a failed clear only means a binding rebuilt
|
|
517
|
+
// after a Redis loss is restricted again (the safe direction); the user re-confirms.
|
|
518
|
+
if (claims.mustReenroll === true && this.sessionRestrictions) {
|
|
519
|
+
await this.sessionRestrictions.clearMustReenroll(claims.sessionId).catch(() => undefined);
|
|
520
|
+
}
|
|
449
521
|
if (wasFirst) {
|
|
450
522
|
const recoveryCodes = await this.replaceRecoveryCodes(claims.id, recoveryStore, hasher);
|
|
451
523
|
return { recoveryCodes };
|
|
@@ -499,14 +571,9 @@ let AccountController = class AccountController {
|
|
|
499
571
|
const store = this.mfaDevices;
|
|
500
572
|
const code = typeof body?.code === 'string' ? body.code : '';
|
|
501
573
|
// Proof BEFORE the delete: a TOTP from any confirmed device (incl. the one being
|
|
502
|
-
// removed — legit for the last-device disable) OR a valid recovery code.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
this.recoveryCodes !== undefined &&
|
|
506
|
-
this.recoveryCodes !== null &&
|
|
507
|
-
(await this.recoveryCodes.consume(claims.id, code));
|
|
508
|
-
if (!byTotp && !byRecovery)
|
|
509
|
-
badRequest();
|
|
574
|
+
// removed — legit for the last-device disable) OR a valid recovery code. Rate-limited
|
|
575
|
+
// and replay-guarded like `/auth/mfa` (Phase 8 #1).
|
|
576
|
+
await this.requireMfaProof(store, claims.id, code);
|
|
510
577
|
const removed = await store.remove(claims.id, id);
|
|
511
578
|
if (!removed) {
|
|
512
579
|
throw new common_1.HttpException({ error: 'not_found' }, common_1.HttpStatus.NOT_FOUND);
|
|
@@ -546,11 +613,9 @@ let AccountController = class AccountController {
|
|
|
546
613
|
// MFA must be on — else there's nothing to protect / no code source → generic 400.
|
|
547
614
|
if ((await store.countConfirmed(claims.id)) === 0)
|
|
548
615
|
badRequest();
|
|
549
|
-
// Proof: a TOTP from any confirmed device OR an existing recovery code
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (!byTotp && !byRecovery)
|
|
553
|
-
badRequest();
|
|
616
|
+
// Proof: a TOTP from any confirmed device OR an existing recovery code (rate-limited
|
|
617
|
+
// and replay-guarded like `/auth/mfa`, Phase 8 #1).
|
|
618
|
+
await this.requireMfaProof(store, claims.id, code);
|
|
554
619
|
const recoveryCodes = await this.replaceRecoveryCodes(claims.id, recoveryStore, hasher);
|
|
555
620
|
return { recoveryCodes };
|
|
556
621
|
}
|
|
@@ -657,7 +722,15 @@ exports.AccountController = AccountController = __decorate([
|
|
|
657
722
|
__param(7, (0, common_1.Inject)(tokens_js_2.SHIELD_PROFILE_WRITER)),
|
|
658
723
|
__param(8, (0, common_1.Optional)()),
|
|
659
724
|
__param(8, (0, common_1.Inject)(tokens_js_2.SHIELD_MFA_DEVICE_STORE)),
|
|
660
|
-
|
|
725
|
+
__param(9, (0, common_1.Optional)()),
|
|
726
|
+
__param(9, (0, common_1.Inject)(tokens_js_2.SHIELD_SESSION_RESTRICTIONS)),
|
|
727
|
+
__param(10, (0, common_1.Optional)()),
|
|
728
|
+
__param(10, (0, common_1.Inject)(tokens_js_2.SHIELD_LOGIN_TOTP_VERIFIER)),
|
|
729
|
+
__param(11, (0, common_1.Optional)()),
|
|
730
|
+
__param(11, (0, common_1.Inject)(tokens_js_2.SHIELD_MFA_LIMITER)),
|
|
731
|
+
__param(12, (0, common_1.Optional)()),
|
|
732
|
+
__param(12, (0, common_1.Inject)(tokens_js_2.SHIELD_LOGIN_LIMITER)),
|
|
733
|
+
__metadata("design:paramtypes", [Function, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object])
|
|
661
734
|
], AccountController);
|
|
662
735
|
/**
|
|
663
736
|
* A deliberately-minimal email shape check (non-empty, a single `@`, a `.` in the
|
|
@@ -14,10 +14,13 @@
|
|
|
14
14
|
* ── OUTCOME → HTTP STATUS MAP (the security contract) ────────────────────────
|
|
15
15
|
* login authenticated → 200 SessionSuccess { session(no-secret), tokens }
|
|
16
16
|
* login mfa_required → 200 MfaChallengeResponse { mfaRequired, challengeToken }
|
|
17
|
+
* (a Redis challenge: principal + hint + credential
|
|
18
|
+
* fingerprint, never the password — M6)
|
|
17
19
|
* login invalid_credentials → 401 GENERIC ← unknown-user AND wrong-password IDENTICAL
|
|
18
20
|
* login auth_unavailable → 503 RETRYABLE ← transient blip is NOT a credential deny
|
|
19
21
|
* mfa success → 200 SessionSuccess
|
|
20
22
|
* mfa wrong/expired/forged → 401 GENERIC ← indistinguishable from a bad code
|
|
23
|
+
* mfa TOTP step locked (#9) → 401 GENERIC (5 wrong codes / 15 min per account)
|
|
21
24
|
* mfa auth_unavailable → 503 RETRYABLE
|
|
22
25
|
* refresh ok → 200 SessionSuccess (new tokens, rotated session)
|
|
23
26
|
* refresh reuse/invalid → 401 GENERIC session_rejected (lineage-kill is the engine's)
|
|
@@ -50,12 +53,17 @@
|
|
|
50
53
|
* composed engines via DI tokens; it never news-up an engine or an adapter.
|
|
51
54
|
*/
|
|
52
55
|
import type { Authenticator, PasswordHasher, SessionAuthority } from '@astram/core';
|
|
56
|
+
import type { SessionRestrictionStore } from '../session/session-restriction-store.js';
|
|
57
|
+
import type { SessionRecordStore } from '@astram/core';
|
|
58
|
+
import type { Clock, SessionLifetimeConfig } from '@astram/core';
|
|
59
|
+
import { type MfaStepUp } from './mfa-step-up.js';
|
|
53
60
|
import type { SessionFactsProjector } from './ports/session-facts-projector.js';
|
|
54
61
|
import type { ScopeSubtreeResolver } from './ports/scope-subtree-resolver.js';
|
|
55
62
|
import type { MembershipChecker } from './ports/membership-checker.js';
|
|
56
63
|
import type { AttemptLimiter } from './rate-limit/attempt-limiter.js';
|
|
64
|
+
import { TrustProxyBootCheck } from './rate-limit/trust-proxy-check.js';
|
|
57
65
|
import { PendingMfaStore } from './pending-mfa-store.js';
|
|
58
|
-
import type
|
|
66
|
+
import { type SessionTokenBinder } from '../request-auth/session-token-binder.js';
|
|
59
67
|
import type { CurrentUserProfile, ProfileResolver } from './profile-resolver.js';
|
|
60
68
|
import type { CredentialResolver } from '@astram/core';
|
|
61
69
|
import type { ResetTokenStore } from './ports/reset-token-store.js';
|
|
@@ -87,6 +95,8 @@ export declare class AuthController {
|
|
|
87
95
|
private readonly resetLinkBaseUrl?;
|
|
88
96
|
private readonly recoveryCodes?;
|
|
89
97
|
private readonly loginLimiter?;
|
|
98
|
+
private readonly loginIpLimiter?;
|
|
99
|
+
private readonly trustProxyCheck?;
|
|
90
100
|
private readonly recoveryLimiter?;
|
|
91
101
|
private readonly forgotLimiter?;
|
|
92
102
|
private readonly forgotHintLimiter?;
|
|
@@ -96,23 +106,28 @@ export declare class AuthController {
|
|
|
96
106
|
private readonly scopeSubtreeResolver?;
|
|
97
107
|
private readonly membershipChecker?;
|
|
98
108
|
private readonly permissionFacts?;
|
|
99
|
-
|
|
109
|
+
private readonly mfaLimiter?;
|
|
110
|
+
private readonly mfaStepUp?;
|
|
111
|
+
private readonly clock?;
|
|
112
|
+
private readonly lifetimes?;
|
|
113
|
+
private readonly rootIds?;
|
|
114
|
+
private readonly sessionRecords?;
|
|
115
|
+
private readonly sessionRestrictions?;
|
|
116
|
+
constructor(authenticator: Authenticator, sessionAuthority: SessionAuthority, pendingMfa: PendingMfaStore, tokenBinder?: SessionTokenBinder | undefined, profileResolver?: ProfileResolver | undefined, credentialResolver?: (CredentialResolver | null) | undefined, resetTokenStore?: (ResetTokenStore | null) | undefined, emailSender?: (EmailSender | null) | undefined, credentialWriter?: (CredentialWriter | null) | undefined, sessionSweeper?: (SessionSweeper | null) | undefined, passwordHasher?: PasswordHasher | undefined, resetLinkBaseUrl?: string | undefined, recoveryCodes?: (RecoveryCodeStore | null) | undefined, loginLimiter?: (AttemptLimiter | null) | undefined, loginIpLimiter?: (AttemptLimiter | null) | undefined, trustProxyCheck?: (TrustProxyBootCheck | null) | undefined, recoveryLimiter?: (AttemptLimiter | null) | undefined, forgotLimiter?: (AttemptLimiter | null) | undefined, forgotHintLimiter?: (AttemptLimiter | null) | undefined, resetLimiter?: (AttemptLimiter | null) | undefined, auditSink?: (AuditSink | null) | undefined, sessionFactsProjector?: (SessionFactsProjector | null) | undefined, scopeSubtreeResolver?: (ScopeSubtreeResolver | null) | undefined, membershipChecker?: (MembershipChecker | null) | undefined, permissionFacts?: (MariaDbPermissionFactsAdapter | null) | undefined, mfaLimiter?: (AttemptLimiter | null) | undefined, mfaStepUp?: (MfaStepUp | null) | undefined, clock?: (Clock | null) | undefined, lifetimes?: (SessionLifetimeConfig | null) | undefined, rootIds?: (readonly string[] | null) | undefined, sessionRecords?: (Pick<SessionRecordStore, "findSessionById"> | null) | undefined, sessionRestrictions?: (SessionRestrictionStore | null) | undefined);
|
|
117
|
+
/**
|
|
118
|
+
* The access-token binding TTL for an establishment (security hardening #8):
|
|
119
|
+
* `clamp(session.expiresAtMs − now, 1, accessTtlMs)` — on login AND refresh. Never derived
|
|
120
|
+
* from `issuedAtMs` (the original login), which made refresh-minted tokens live for the
|
|
121
|
+
* session's age.
|
|
122
|
+
*/
|
|
123
|
+
private accessTtlFor;
|
|
100
124
|
/**
|
|
101
125
|
* Bind the established session's access token to the principal's claims (the
|
|
102
|
-
* request-auth layer). The access-token TTL is
|
|
103
|
-
*
|
|
126
|
+
* request-auth layer). The access-token TTL is {@link accessTtlFor} (≤ the access TTL).
|
|
127
|
+
* Best-effort: a binding failure must not fail the login (the caller still
|
|
104
128
|
* gets valid tokens); the guard fails closed if the binding is absent.
|
|
105
129
|
*/
|
|
106
130
|
private bindSession;
|
|
107
|
-
/**
|
|
108
|
-
* Project the principal's coarse grants for the session binding (ADR-0011), via the
|
|
109
|
-
* OPTIONAL {@link SessionFactsProjector} seam. Returns the {@link CoarseGrants} to
|
|
110
|
-
* cache, or `undefined` when there is no projector, the projection yielded nothing,
|
|
111
|
-
* OR it threw — every "no facts" path collapses to `undefined` so the caller simply
|
|
112
|
-
* omits `cachedFacts` and the request DB-reloads (fail-closed; a projection failure
|
|
113
|
-
* NEVER fails the login/refresh and never fabricates a grant).
|
|
114
|
-
*/
|
|
115
|
-
private projectFacts;
|
|
116
131
|
/**
|
|
117
132
|
* Project coarse grants for a principal id + tenant (ADR-0011), distinguishing THREE
|
|
118
133
|
* outcomes for the REFRESH path (which must know whether to carry the existing cache
|
|
@@ -148,6 +163,16 @@ export declare class AuthController {
|
|
|
148
163
|
* bumped by moveNode/deleteNode) re-derives its CORRECT subtree here, closing the
|
|
149
164
|
* stale-subtree-after-move window (bounded by the access-token lifetime — ADR-0011).
|
|
150
165
|
*/
|
|
166
|
+
/**
|
|
167
|
+
* The permission version of `tenantId`, read just BEFORE its subtree is resolved, to stamp the
|
|
168
|
+
* binding (`scopeSubtreeVersion`, security hardening Phase 1C): when gate 5 later reloads facts
|
|
169
|
+
* at a newer version, the enforcement guard re-derives the subtree. Read before the resolve so a
|
|
170
|
+
* racing bump leaves the stamp too old (one extra re-derive), never too new. Only when a
|
|
171
|
+
* subtree resolver is wired (no subtree → nothing to stamp, no extra read). Best-effort: no
|
|
172
|
+
* facts adapter, a non-integer, or a throw → `undefined` (unstamped → the guard re-derives once
|
|
173
|
+
* at the next reload). One indexed read at login/refresh/scope-switch, never per request.
|
|
174
|
+
*/
|
|
175
|
+
private subtreeVersionFor;
|
|
151
176
|
private resolveScopeSubtreeFor;
|
|
152
177
|
/**
|
|
153
178
|
* Compute the caller's OWN auth-facts projection to INLINE into a login/mfa success
|
|
@@ -183,12 +208,33 @@ export declare class AuthController {
|
|
|
183
208
|
* never a claim), matching the internal adapter's root-safety.
|
|
184
209
|
*/
|
|
185
210
|
private principalFromClaims;
|
|
211
|
+
/**
|
|
212
|
+
* The account (principal id) a typed hint resolves to, for keying the per-account
|
|
213
|
+
* limiters (review F1). Goes through the SAME injected {@link CredentialResolver} the
|
|
214
|
+
* composed authenticator uses — the default is the indexed
|
|
215
|
+
* `WHERE ic_identity_hint = ? LIMIT 1` on the `utf8mb4_unicode_ci` column — so the DB
|
|
216
|
+
* collation, not client-side normalisation, decides which spellings are one account.
|
|
217
|
+
* No new access path. The RAW hint is passed (never normalised).
|
|
218
|
+
*
|
|
219
|
+
* Returns `null` for an unknown hint, a blank hint, or when no resolver is wired (→ the
|
|
220
|
+
* canonical-hash fallback key). A store ERROR propagates (the caller fails closed).
|
|
221
|
+
*/
|
|
222
|
+
/**
|
|
223
|
+
* The ONE gate for every per-IP limiter (login failures, forgot-password, reset-password):
|
|
224
|
+
* returns the limiter only when bootstrap confirmed an explicit Express `trust proxy`
|
|
225
|
+
* ({@link TrustProxyBootCheck.perIpLimitingEnabled}); otherwise `null` (limiter OFF).
|
|
226
|
+
* Without it every client behind a proxy shares one `req.ip`, so enforcing a per-IP
|
|
227
|
+
* limit would block everyone (an unauthenticated global DoS). Per-account limits are
|
|
228
|
+
* never routed through here.
|
|
229
|
+
*/
|
|
230
|
+
private perIp;
|
|
231
|
+
private lookupAccount;
|
|
186
232
|
/**
|
|
187
233
|
* `POST /auth/login` — verify a password credential and either establish a
|
|
188
234
|
* session, issue an MFA challenge, deny generically, or signal a transient
|
|
189
235
|
* outage. NEVER logs the request body.
|
|
190
236
|
*/
|
|
191
|
-
login(body: LoginRequest): Promise<LoginResponse>;
|
|
237
|
+
login(body: LoginRequest, ip: string): Promise<LoginResponse>;
|
|
192
238
|
/**
|
|
193
239
|
* `GET /auth/me` — the signed-in user's **display profile** (name, email, active
|
|
194
240
|
* company name, role names) for the UI header/card. Fills the flagged gap: login
|
|
@@ -269,12 +315,27 @@ export declare class AuthController {
|
|
|
269
315
|
forgotPassword(body: {
|
|
270
316
|
identityHint?: string;
|
|
271
317
|
}, ip: string): Promise<void>;
|
|
318
|
+
/**
|
|
319
|
+
* The detached per-account half of {@link forgotPassword} (review F1):
|
|
320
|
+
* 1. resolve the RAW hint through the same {@link CredentialResolver} the authenticator
|
|
321
|
+
* uses (the DB collation decides which spellings are one account);
|
|
322
|
+
* 2. meter the per-account budget — `forgot:pid:<principalId>`, or the canonical-hash
|
|
323
|
+
* fallback key for an unknown hint — and audit `auth.forgot_password.hint_throttled`
|
|
324
|
+
* ONCE on the edge (distinct from the IP site's `throttled`, so "one account bombed
|
|
325
|
+
* across rotating IPs" is visible). No notify (it would itself be the email-bomb);
|
|
326
|
+
* 3. send only when both budgets allow, the account resolved, and the reset seams exist.
|
|
327
|
+
* A lookup error still meters (fallback key) and skips the send (still 204).
|
|
328
|
+
*/
|
|
329
|
+
private meterAndDeliverReset;
|
|
272
330
|
/**
|
|
273
331
|
* The detached found-path work for {@link forgotPassword}: mint a raw token, issue
|
|
274
332
|
* its SHA-256 hash to the store (30-min TTL), resolve the recipient email via the
|
|
275
333
|
* profile resolver, and send the framework-composed reset link. Runs OUTSIDE the
|
|
276
|
-
* request/response timing (fire-and-forget) so it cannot form a timing oracle.
|
|
277
|
-
*
|
|
334
|
+
* request/response timing (fire-and-forget) so it cannot form a timing oracle. This
|
|
335
|
+
* method itself writes no log line — but the raw token and the email ARE handed to the
|
|
336
|
+
* bound {@link EmailSender}, and the dev-only `LogEmailSender` (the default ONLY when
|
|
337
|
+
* NODE_ENV is `development`/`test`) prints the whole message, link included, to stdout.
|
|
338
|
+
* Any other environment must supply a real sender or `emailSender: 'disabled'`.
|
|
278
339
|
*/
|
|
279
340
|
private deliverResetLink;
|
|
280
341
|
/**
|
|
@@ -298,9 +359,9 @@ export declare class AuthController {
|
|
|
298
359
|
* `POST /auth/mfa` — complete a pending MFA login. Consumes the single-use
|
|
299
360
|
* challenge handle (unknown/expired/forged → same generic 401 as a bad code),
|
|
300
361
|
* then BRANCHES on the presented factor:
|
|
301
|
-
* • `'totp'` →
|
|
302
|
-
*
|
|
303
|
-
*
|
|
362
|
+
* • `'totp'` → the MFA STEP-UP (M6): verify ONLY the code against the principal the
|
|
363
|
+
* password step recorded (no password is held or re-run). Rate-limited per account
|
|
364
|
+
* (#9). See {@link completeViaTotp}.
|
|
304
365
|
* • `'recovery_code'` → the lost-device escape hatch: verify a one-time
|
|
305
366
|
* recovery code (a separate branch, NOT the core factor loop — recovery codes
|
|
306
367
|
* are N single-use Argon2 hashes, incompatible with the pure one-secret
|
|
@@ -308,9 +369,25 @@ export declare class AuthController {
|
|
|
308
369
|
* • any other factor → generic 401 (no leak of "unsupported factor").
|
|
309
370
|
*
|
|
310
371
|
* Anti-enumeration: unknown/forged/expired challenge, unknown hint, wrong code,
|
|
311
|
-
* and wrong recovery code ALL return the identical generic 401.
|
|
372
|
+
* a locked TOTP step, and a wrong recovery code ALL return the identical generic 401.
|
|
373
|
+
* A challenge-store outage is a retryable 503.
|
|
312
374
|
*/
|
|
313
375
|
mfa(body: MfaRequest): Promise<SessionSuccess>;
|
|
376
|
+
/**
|
|
377
|
+
* The `/auth/mfa` TOTP branch (M6 + #9). The consumed challenge proves the password
|
|
378
|
+
* step passed for `pending.principalId`; this verifies ONLY the code, through the
|
|
379
|
+
* {@link MfaStepUp} (same `OrTotpVerifier` + replay guard as the authenticator, principal
|
|
380
|
+
* minted by the identity broker).
|
|
381
|
+
*
|
|
382
|
+
* - `mfa:pid:<principalId>` is checked BEFORE verifying: locked → generic 401 and the
|
|
383
|
+
* step-up is never consulted.
|
|
384
|
+
* - a rejected code records a failure; the edge where the count first reaches the
|
|
385
|
+
* threshold audits `auth.mfa.locked` ONCE (no notify — pre-auth, see recovery lockout).
|
|
386
|
+
* - a transient outage → 503, never counted.
|
|
387
|
+
* - success → establish + bind, then reset the TOTP counter AND the login counter (the
|
|
388
|
+
* login counter is reset only here, once authentication fully succeeded).
|
|
389
|
+
*/
|
|
390
|
+
private completeViaTotp;
|
|
314
391
|
/**
|
|
315
392
|
* The `/auth/mfa` RECOVERY-CODE branch (spec §3). The lost-device user completes
|
|
316
393
|
* login with a pre-issued single-use recovery code instead of a TOTP.
|
|
@@ -383,11 +460,43 @@ export declare class AuthController {
|
|
|
383
460
|
/**
|
|
384
461
|
* `POST /auth/refresh` — rotate the refresh token. Reuse/invalid/expired →
|
|
385
462
|
* generic 401 (`session_rejected`; the engine already killed the lineage,
|
|
386
|
-
* ADR-0015
|
|
463
|
+
* ADR-0015, and `HotRevokingSessionRecordStore` unbinds it in Redis so the live access
|
|
464
|
+
* token dies on its next request — M-A). The new access token is bound for ≤ the access
|
|
465
|
+
* TTL; the previous one keeps a 30 s grace (#8, D7). If the session's claims cannot be
|
|
466
|
+
* carried forward (`rebindOnRotate` → false) the refresh is a generic 401 — never an
|
|
467
|
+
* unbound token. ⚠ See the class header: the engine's rotate collapses a transient
|
|
387
468
|
* store outage into the SAME reject, so refresh cannot emit the ADR-0016 503 from
|
|
388
469
|
* this seam today (flagged core-API gap).
|
|
389
470
|
*/
|
|
390
471
|
refresh(body: RefreshRequest): Promise<SessionSuccess>;
|
|
472
|
+
/**
|
|
473
|
+
* The refresh-time projection for one node: coarse grants + live roles (ADR-0035) and the
|
|
474
|
+
* subtree with its version stamp (Phase 1C: the version is read FIRST). Each part is best-effort
|
|
475
|
+
* exactly as at login (`undefined` = carry / omit, `null` facts = strip).
|
|
476
|
+
*/
|
|
477
|
+
private projectForNode;
|
|
478
|
+
/**
|
|
479
|
+
* SILENT RECOVERY of a lost session binding (invariant #12, ADR-0013, ADR-0035, GAPS #5).
|
|
480
|
+
* `rotate()` has already proved the durable session is active and inside its idle and absolute
|
|
481
|
+
* windows, and returned its principal + tenant. The rebuilt claims NEVER carry more authority
|
|
482
|
+
* than the session had (Phase 3B review P1); they come from durable sources only:
|
|
483
|
+
* - principal + tenant: the durable session record (the LOGIN node; a scope-switch is not
|
|
484
|
+
* durable, so a rebuilt session returns to the node it was opened on);
|
|
485
|
+
* - the RE-ENROLL restriction: `shield_identity.session.s_must_reenroll`, set at a
|
|
486
|
+
* recovery-code login. A restricted session rebuilds `mustReenroll: true` and NEVER root
|
|
487
|
+
* (exactly how the recovery login bound it);
|
|
488
|
+
* - the account status: a disabled user gets no rebuild;
|
|
489
|
+
* - roles: the live projection (ADR-0035) — `[]` when none is available (non-widening; the
|
|
490
|
+
* first request's gate-5 reload fills them, because no facts are cached then);
|
|
491
|
+
* - `isRoot` (unrestricted sessions only): the server-side root allowlist, never a claim;
|
|
492
|
+
* - subtree: the resolver (absent → the self-node fallback, non-widening);
|
|
493
|
+
* - kind `user` (sessions are human; service accounts authenticate with keys).
|
|
494
|
+
* No restriction store, or any error reading it → no rebuild (fail closed). The claims entry is
|
|
495
|
+
* written with `NX`, then the durable session is READ AGAIN: if a logout or revoke landed
|
|
496
|
+
* meanwhile, the rebuilt binding is dropped and the refresh fails. Returns `true` when the
|
|
497
|
+
* session is bound and still active. Any error → `false` (the caller answers 401).
|
|
498
|
+
*/
|
|
499
|
+
private rebuildBinding;
|
|
391
500
|
/**
|
|
392
501
|
* `POST /auth/logout` — revoke the session. Idempotent + leak-free: revoking an
|
|
393
502
|
* unknown or already-revoked session still returns 204 (the engine's `revoke`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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"}
|