@astram/guard 0.8.0 → 0.10.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 +37 -9
- package/dist/auth/account.controller.d.ts.map +1 -1
- package/dist/auth/account.controller.js +154 -35
- package/dist/auth/auth.controller.d.ts +6 -5
- package/dist/auth/auth.controller.d.ts.map +1 -1
- package/dist/auth/auth.controller.js +127 -75
- package/dist/auth/auth.module.d.ts.map +1 -1
- package/dist/auth/auth.module.js +10 -7
- package/dist/auth/connections.d.ts.map +1 -1
- package/dist/auth/connections.js +3 -1
- package/dist/auth/rate-limit/attempt-limiter.d.ts +59 -1
- package/dist/auth/rate-limit/attempt-limiter.d.ts.map +1 -1
- package/dist/auth/rate-limit/attempt-limiter.js +85 -2
- package/dist/auth/tokens.d.ts +13 -10
- package/dist/auth/tokens.d.ts.map +1 -1
- package/dist/auth/tokens.js +13 -10
- package/dist/authentication/mariadb-api-key-store.d.ts +2 -1
- package/dist/authentication/mariadb-api-key-store.d.ts.map +1 -1
- package/dist/authentication/mariadb-api-key-store.js +13 -1
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -7
- 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-tokens.d.ts +12 -0
- package/dist/module/shield-tokens.d.ts.map +1 -1
- package/dist/module/shield-tokens.js +13 -1
- package/dist/module/shield.module.d.ts +6 -2
- package/dist/module/shield.module.d.ts.map +1 -1
- package/dist/module/shield.module.js +40 -18
- package/package.json +14 -3
|
@@ -38,13 +38,14 @@
|
|
|
38
38
|
* `DELETE /account/mfa/devices/:id` (code-gated), `POST
|
|
39
39
|
* /account/mfa/recovery-codes/regenerate` (code-gated).
|
|
40
40
|
*
|
|
41
|
-
* ── NO ORACLE ──
|
|
42
|
-
* (
|
|
43
|
-
*
|
|
41
|
+
* ── NO ORACLE ── no-token / bad token is one generic 401. A wrong current password is a
|
|
42
|
+
* 400 (Phase 9 #3; never 401, which clients read as "token expired" and replay), the
|
|
43
|
+
* same for a missing stored hash. Passwords/hashes are NEVER logged.
|
|
44
44
|
*/
|
|
45
45
|
import type { FactorVerifier, PasswordHasher } from '@astram/core';
|
|
46
46
|
import type { SessionTokenBinder } from '../request-auth/session-token-binder.js';
|
|
47
47
|
import type { SessionRestrictionStore } from '../session/session-restriction-store.js';
|
|
48
|
+
import type { AttemptLimiter } from './rate-limit/attempt-limiter.js';
|
|
48
49
|
import type { CredentialWriter } from './ports/credential-writer.js';
|
|
49
50
|
import type { MfaDevice, MfaDeviceStore } from './ports/mfa-device-store.js';
|
|
50
51
|
import type { CurrentUserProfile, ProfileResolver } from './profile-resolver.js';
|
|
@@ -62,7 +63,10 @@ export declare class AccountController {
|
|
|
62
63
|
private readonly profileWriter?;
|
|
63
64
|
private readonly mfaDevices?;
|
|
64
65
|
private readonly sessionRestrictions?;
|
|
65
|
-
|
|
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);
|
|
66
70
|
/**
|
|
67
71
|
* Resolve a human-readable otpauth account LABEL for the caller (email, else display
|
|
68
72
|
* name) via the profile resolver. Best-effort: no resolver / null profile / a thrown
|
|
@@ -111,8 +115,16 @@ export declare class AccountController {
|
|
|
111
115
|
* • no/invalid token → 401 (generic)
|
|
112
116
|
* • newPassword shorter than the minimum → 422 `{ error: 'weak_password' }`
|
|
113
117
|
* • writer/hasher seam absent → 501 `{ error: 'not_available' }`
|
|
114
|
-
* • wrong current password →
|
|
115
|
-
*
|
|
118
|
+
* • wrong current password → 400 `{ error: 'invalid_current_password' }`
|
|
119
|
+
* (no write; counted; also for no stored hash)
|
|
120
|
+
* • account login budget exhausted → 429 `{ error: 'too_many_requests' }`
|
|
121
|
+
* (not verified, no write)
|
|
122
|
+
* • attempt limiter (Redis) unreachable → 503 `{ error: 'authentication_unavailable' }`
|
|
123
|
+
* (fail closed, not verified, no write)
|
|
124
|
+
*
|
|
125
|
+
* 401 is reserved for a missing/invalid session (Phase 9 #3): the client's refresh
|
|
126
|
+
* wrapper refreshes and replays on a bearer 401, so a credential mismatch must not use it.
|
|
127
|
+
* • success → 204 (no body; counter reset)
|
|
116
128
|
*/
|
|
117
129
|
changePassword(authorization: string | undefined, body: {
|
|
118
130
|
currentPassword?: string;
|
|
@@ -156,10 +168,25 @@ export declare class AccountController {
|
|
|
156
168
|
private replaceRecoveryCodes;
|
|
157
169
|
/**
|
|
158
170
|
* Verify a presented `code` against ANY of the caller's CONFIRMED device secrets
|
|
159
|
-
* (TOTP)
|
|
160
|
-
*
|
|
171
|
+
* (TOTP) through the replay-guarded login verifier: the secrets are bundled
|
|
172
|
+
* newline-joined (the `OrTotpVerifier` contract) and an accepted code is claimed in
|
|
173
|
+
* Redis, so it cannot be reused on this or any other MFA path inside the window. A
|
|
174
|
+
* Redis error on the claim → 503 (fail closed; never an unguarded accept, never a
|
|
175
|
+
* fall-through to the recovery-code check). Guarded verifier not wired → `false`.
|
|
176
|
+
* NEVER logs the code.
|
|
161
177
|
*/
|
|
162
178
|
private verifyAgainstAnyDevice;
|
|
179
|
+
/**
|
|
180
|
+
* The shared second-factor proof for the code-gated account routes (Phase 8 #1),
|
|
181
|
+
* mirroring `/auth/mfa`: the attempt is counted against `mfa:pid:<principalId>` BEFORE
|
|
182
|
+
* any TOTP or recovery-code verification (Phase 9 #10: atomic increment-first, so
|
|
183
|
+
* parallel guesses cannot all pass; a locked caller never triggers the Argon2 recovery
|
|
184
|
+
* scan) → a TOTP from any confirmed device, else a recovery code (consumed) → a failed
|
|
185
|
+
* proof keeps its count (400), a good one resets the counter. Locked → 429. A Redis
|
|
186
|
+
* error on the gate fails closed (503), as does the TOTP replay claim; an attempt that
|
|
187
|
+
* ended in such an error (no verdict) is given back.
|
|
188
|
+
*/
|
|
189
|
+
private requireMfaProof;
|
|
163
190
|
/**
|
|
164
191
|
* `GET /account/mfa/devices` — list the caller's CONFIRMED authenticator devices.
|
|
165
192
|
* `mfaEnabled` reflects whether any confirmed device exists. Pending (unconfirmed)
|
|
@@ -225,7 +252,7 @@ export declare class AccountController {
|
|
|
225
252
|
* and additionally wipes the account's recovery codes.
|
|
226
253
|
*
|
|
227
254
|
* Outcomes: 401; store absent → 501; wrong code → 400 (no remove); not found/owned →
|
|
228
|
-
* 404; success → 204.
|
|
255
|
+
* 404; MFA budget exhausted → 429; limiter/replay guard unreachable → 503; success → 204.
|
|
229
256
|
*/
|
|
230
257
|
removeDevice(authorization: string | undefined, id: string, body: {
|
|
231
258
|
code?: string;
|
|
@@ -236,6 +263,7 @@ export declare class AccountController {
|
|
|
236
263
|
* — a TOTP from any confirmed device OR an existing recovery code.
|
|
237
264
|
*
|
|
238
265
|
* Outcomes: 401; store/recovery/hasher absent → 501; MFA off → 400; wrong code → 400;
|
|
266
|
+
* MFA budget exhausted → 429; limiter/replay guard unreachable → 503;
|
|
239
267
|
* success → 200 `{ recoveryCodes }`.
|
|
240
268
|
*/
|
|
241
269
|
regenerateRecoveryCodes(authorization: string | undefined, body: {
|
|
@@ -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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"}
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
* `DELETE /account/mfa/devices/:id` (code-gated), `POST
|
|
40
40
|
* /account/mfa/recovery-codes/regenerate` (code-gated).
|
|
41
41
|
*
|
|
42
|
-
* ── NO ORACLE ──
|
|
43
|
-
* (
|
|
44
|
-
*
|
|
42
|
+
* ── NO ORACLE ── no-token / bad token is one generic 401. A wrong current password is a
|
|
43
|
+
* 400 (Phase 9 #3; never 401, which clients read as "token expired" and replay), the
|
|
44
|
+
* same for a missing stored hash. Passwords/hashes are NEVER logged.
|
|
45
45
|
*/
|
|
46
46
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
47
47
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -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;
|
|
@@ -82,14 +83,22 @@ const TOTP_ISSUER = 'Astram';
|
|
|
82
83
|
*/
|
|
83
84
|
const FALLBACK_OTPAUTH_LABEL = 'account';
|
|
84
85
|
/**
|
|
85
|
-
* The SINGLE generic 401 for EVERY unauthenticated
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
86
|
+
* The SINGLE generic 401 for EVERY unauthenticated path here (no token, no binder,
|
|
87
|
+
* resolve→null). Deliberately non-distinguishing — never leaks whether the principal
|
|
88
|
+
* exists. Never used for a credential mismatch by an authenticated caller (Phase 9 #3):
|
|
89
|
+
* clients treat a bearer 401 as an expired token and refresh + replay.
|
|
89
90
|
*/
|
|
90
91
|
function unauthorized() {
|
|
91
92
|
throw new common_1.HttpException({ error: 'unauthorized' }, common_1.HttpStatus.UNAUTHORIZED);
|
|
92
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* The 400 for a wrong (or unset) current password on change-password (Phase 9 #3). The
|
|
96
|
+
* caller is authenticated and acting on their own account, so naming the field is safe;
|
|
97
|
+
* a missing stored hash gives the same answer (no "has no password" oracle).
|
|
98
|
+
*/
|
|
99
|
+
function invalidCurrentPassword() {
|
|
100
|
+
throw new common_1.HttpException({ error: 'invalid_current_password' }, common_1.HttpStatus.BAD_REQUEST);
|
|
101
|
+
}
|
|
93
102
|
/** The honest 501 when a required seam is unplugged (fail-closed, no mutation). */
|
|
94
103
|
function notAvailable() {
|
|
95
104
|
throw new common_1.HttpException({ error: 'not_available' }, common_1.HttpStatus.NOT_IMPLEMENTED);
|
|
@@ -103,6 +112,38 @@ function notAvailable() {
|
|
|
103
112
|
function badRequest() {
|
|
104
113
|
throw new common_1.HttpException({ error: 'invalid_request' }, common_1.HttpStatus.BAD_REQUEST);
|
|
105
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* The 429 for a caller whose proof budget is exhausted (Phase 8 #1). These routes are
|
|
117
|
+
* self-scoped and already authenticated, so the throttle signal reveals nothing about
|
|
118
|
+
* another account; a 401 here would read as "session expired" to the client's refresh logic.
|
|
119
|
+
*/
|
|
120
|
+
function tooManyRequests() {
|
|
121
|
+
throw new common_1.HttpException({ error: 'too_many_requests' }, common_1.HttpStatus.TOO_MANY_REQUESTS);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The retryable 503 when the replay guard or the attempt limiter (Redis) cannot answer:
|
|
125
|
+
* fail closed, no mutation.
|
|
126
|
+
*/
|
|
127
|
+
function retryableUnavailable() {
|
|
128
|
+
throw new common_1.HttpException({ error: 'authentication_unavailable' }, common_1.HttpStatus.SERVICE_UNAVAILABLE);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Phase 9 #10 — count this attempt BEFORE verifying anything (atomic increment-first).
|
|
132
|
+
* Over budget → 429; limiter unreachable → 503 (fail closed). No limiter wired → no-op.
|
|
133
|
+
*/
|
|
134
|
+
async function consumeAttempt(limiter, key) {
|
|
135
|
+
if (limiter == null)
|
|
136
|
+
return;
|
|
137
|
+
let allowed;
|
|
138
|
+
try {
|
|
139
|
+
({ allowed } = await limiter.consume(key));
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
retryableUnavailable();
|
|
143
|
+
}
|
|
144
|
+
if (!allowed)
|
|
145
|
+
tooManyRequests();
|
|
146
|
+
}
|
|
106
147
|
/**
|
|
107
148
|
* The 422 for a semantically invalid profile edit — an empty change (`no_changes`)
|
|
108
149
|
* or a malformed email (`invalid_email`). Distinct from {@link badRequest}: this is
|
|
@@ -123,7 +164,10 @@ let AccountController = class AccountController {
|
|
|
123
164
|
profileWriter;
|
|
124
165
|
mfaDevices;
|
|
125
166
|
sessionRestrictions;
|
|
126
|
-
|
|
167
|
+
loginTotpVerifier;
|
|
168
|
+
mfaLimiter;
|
|
169
|
+
loginLimiter;
|
|
170
|
+
constructor(binder, hasher, writer, sweeper, recoveryCodes, totpVerifier, profileResolver, profileWriter, mfaDevices, sessionRestrictions, loginTotpVerifier, mfaLimiter, loginLimiter) {
|
|
127
171
|
this.binder = binder;
|
|
128
172
|
this.hasher = hasher;
|
|
129
173
|
this.writer = writer;
|
|
@@ -134,6 +178,9 @@ let AccountController = class AccountController {
|
|
|
134
178
|
this.profileWriter = profileWriter;
|
|
135
179
|
this.mfaDevices = mfaDevices;
|
|
136
180
|
this.sessionRestrictions = sessionRestrictions;
|
|
181
|
+
this.loginTotpVerifier = loginTotpVerifier;
|
|
182
|
+
this.mfaLimiter = mfaLimiter;
|
|
183
|
+
this.loginLimiter = loginLimiter;
|
|
137
184
|
}
|
|
138
185
|
/**
|
|
139
186
|
* Resolve a human-readable otpauth account LABEL for the caller (email, else display
|
|
@@ -220,8 +267,16 @@ let AccountController = class AccountController {
|
|
|
220
267
|
* • no/invalid token → 401 (generic)
|
|
221
268
|
* • newPassword shorter than the minimum → 422 `{ error: 'weak_password' }`
|
|
222
269
|
* • writer/hasher seam absent → 501 `{ error: 'not_available' }`
|
|
223
|
-
* • wrong current password →
|
|
224
|
-
*
|
|
270
|
+
* • wrong current password → 400 `{ error: 'invalid_current_password' }`
|
|
271
|
+
* (no write; counted; also for no stored hash)
|
|
272
|
+
* • account login budget exhausted → 429 `{ error: 'too_many_requests' }`
|
|
273
|
+
* (not verified, no write)
|
|
274
|
+
* • attempt limiter (Redis) unreachable → 503 `{ error: 'authentication_unavailable' }`
|
|
275
|
+
* (fail closed, not verified, no write)
|
|
276
|
+
*
|
|
277
|
+
* 401 is reserved for a missing/invalid session (Phase 9 #3): the client's refresh
|
|
278
|
+
* wrapper refreshes and replays on a bearer 401, so a credential mismatch must not use it.
|
|
279
|
+
* • success → 204 (no body; counter reset)
|
|
225
280
|
*/
|
|
226
281
|
async changePassword(authorization, body) {
|
|
227
282
|
const claims = await this.requireClaims(authorization);
|
|
@@ -240,14 +295,38 @@ let AccountController = class AccountController {
|
|
|
240
295
|
}
|
|
241
296
|
const writer = this.writer;
|
|
242
297
|
const hasher = this.hasher;
|
|
298
|
+
// Phase 8 #8: the current-password check spends the SAME per-account budget as
|
|
299
|
+
// password login (`login:pid:<principalId>`, shared counter): checked BEFORE the
|
|
300
|
+
// verify (a locked account never reaches the hasher), a wrong current password records
|
|
301
|
+
// a failure, and a success resets it — exactly as a completed login does. Locked → 429
|
|
302
|
+
// (Phase 9 #3), like the MFA account routes: the caller is authenticated, so the
|
|
303
|
+
// throttle reveals nothing about another account, and a 401 would make the client
|
|
304
|
+
// refresh and replay the request.
|
|
305
|
+
//
|
|
306
|
+
// Phase 9 #10: the attempt is counted FIRST (atomic), so parallel guesses cannot all
|
|
307
|
+
// slip past a read-only check; a wrong password simply keeps its count. A Redis error
|
|
308
|
+
// fails closed (503).
|
|
309
|
+
const loginKey = (0, canonical_hint_js_1.loginHintKey)('', claims.id);
|
|
310
|
+
await consumeAttempt(this.loginLimiter, loginKey);
|
|
243
311
|
// Verify the current password: read the stored hash BY PRINCIPAL ID via the writer
|
|
244
312
|
// seam's `getPasswordHash` (change-password holds the principal id from the token,
|
|
245
313
|
// whereas CredentialResolver keys by identityHint). A missing hash or a mismatch is
|
|
246
|
-
// the SAME
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
314
|
+
// the SAME 400 (no oracle), and no write happens. NOT 401 (Phase 9 #3): the session is
|
|
315
|
+
// valid, and the client's refresh wrapper treats a bearer 401 as an expired token —
|
|
316
|
+
// refresh + replay, which counted every typo twice against the shared login budget.
|
|
317
|
+
let matches;
|
|
318
|
+
try {
|
|
319
|
+
const stored = await writer.getPasswordHash(claims.id);
|
|
320
|
+
matches = stored !== null && (await hasher.verify(stored, currentPassword));
|
|
321
|
+
}
|
|
322
|
+
catch (err) {
|
|
323
|
+
// No verdict (store/hasher fault): give the attempt back, then surface the error.
|
|
324
|
+
await this.loginLimiter?.refund(loginKey);
|
|
325
|
+
throw err;
|
|
250
326
|
}
|
|
327
|
+
if (!matches)
|
|
328
|
+
invalidCurrentPassword(); // the consumed attempt stays counted
|
|
329
|
+
await this.loginLimiter?.reset(loginKey);
|
|
251
330
|
const newHash = await hasher.hash(newPassword);
|
|
252
331
|
await writer.setPassword(claims.id, newHash);
|
|
253
332
|
// Revoke the caller's OTHER sessions (keep the current one live). Best-effort:
|
|
@@ -339,16 +418,56 @@ let AccountController = class AccountController {
|
|
|
339
418
|
}
|
|
340
419
|
/**
|
|
341
420
|
* Verify a presented `code` against ANY of the caller's CONFIRMED device secrets
|
|
342
|
-
* (TOTP)
|
|
343
|
-
*
|
|
421
|
+
* (TOTP) through the replay-guarded login verifier: the secrets are bundled
|
|
422
|
+
* newline-joined (the `OrTotpVerifier` contract) and an accepted code is claimed in
|
|
423
|
+
* Redis, so it cannot be reused on this or any other MFA path inside the window. A
|
|
424
|
+
* Redis error on the claim → 503 (fail closed; never an unguarded accept, never a
|
|
425
|
+
* fall-through to the recovery-code check). Guarded verifier not wired → `false`.
|
|
426
|
+
* NEVER logs the code.
|
|
344
427
|
*/
|
|
345
428
|
async verifyAgainstAnyDevice(store, principalId, code) {
|
|
429
|
+
if (this.loginTotpVerifier === undefined || this.loginTotpVerifier === null)
|
|
430
|
+
return false;
|
|
346
431
|
const secrets = await store.listConfirmedSecrets(principalId);
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
432
|
+
if (secrets.length === 0)
|
|
433
|
+
return false;
|
|
434
|
+
try {
|
|
435
|
+
const verdict = await this.loginTotpVerifier.verify({ factor: 'totp', payload: { code } }, secrets.join('\n'));
|
|
436
|
+
return verdict.satisfied;
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
retryableUnavailable();
|
|
350
440
|
}
|
|
351
|
-
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The shared second-factor proof for the code-gated account routes (Phase 8 #1),
|
|
444
|
+
* mirroring `/auth/mfa`: the attempt is counted against `mfa:pid:<principalId>` BEFORE
|
|
445
|
+
* any TOTP or recovery-code verification (Phase 9 #10: atomic increment-first, so
|
|
446
|
+
* parallel guesses cannot all pass; a locked caller never triggers the Argon2 recovery
|
|
447
|
+
* scan) → a TOTP from any confirmed device, else a recovery code (consumed) → a failed
|
|
448
|
+
* proof keeps its count (400), a good one resets the counter. Locked → 429. A Redis
|
|
449
|
+
* error on the gate fails closed (503), as does the TOTP replay claim; an attempt that
|
|
450
|
+
* ended in such an error (no verdict) is given back.
|
|
451
|
+
*/
|
|
452
|
+
async requireMfaProof(store, principalId, code) {
|
|
453
|
+
const mfaKey = `mfa:pid:${principalId}`;
|
|
454
|
+
await consumeAttempt(this.mfaLimiter, mfaKey);
|
|
455
|
+
let proven;
|
|
456
|
+
try {
|
|
457
|
+
const byTotp = await this.verifyAgainstAnyDevice(store, principalId, code);
|
|
458
|
+
proven =
|
|
459
|
+
byTotp ||
|
|
460
|
+
(this.recoveryCodes !== undefined &&
|
|
461
|
+
this.recoveryCodes !== null &&
|
|
462
|
+
(await this.recoveryCodes.consume(principalId, code)));
|
|
463
|
+
}
|
|
464
|
+
catch (err) {
|
|
465
|
+
await this.mfaLimiter?.refund(mfaKey);
|
|
466
|
+
throw err;
|
|
467
|
+
}
|
|
468
|
+
if (!proven)
|
|
469
|
+
badRequest(); // the consumed attempt stays counted
|
|
470
|
+
await this.mfaLimiter?.reset(mfaKey);
|
|
352
471
|
}
|
|
353
472
|
/**
|
|
354
473
|
* `GET /account/mfa/devices` — list the caller's CONFIRMED authenticator devices.
|
|
@@ -490,7 +609,7 @@ let AccountController = class AccountController {
|
|
|
490
609
|
* and additionally wipes the account's recovery codes.
|
|
491
610
|
*
|
|
492
611
|
* Outcomes: 401; store absent → 501; wrong code → 400 (no remove); not found/owned →
|
|
493
|
-
* 404; success → 204.
|
|
612
|
+
* 404; MFA budget exhausted → 429; limiter/replay guard unreachable → 503; success → 204.
|
|
494
613
|
*/
|
|
495
614
|
async removeDevice(authorization, id, body) {
|
|
496
615
|
const claims = await this.requireClaims(authorization);
|
|
@@ -504,14 +623,9 @@ let AccountController = class AccountController {
|
|
|
504
623
|
const store = this.mfaDevices;
|
|
505
624
|
const code = typeof body?.code === 'string' ? body.code : '';
|
|
506
625
|
// Proof BEFORE the delete: a TOTP from any confirmed device (incl. the one being
|
|
507
|
-
// removed — legit for the last-device disable) OR a valid recovery code.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
this.recoveryCodes !== undefined &&
|
|
511
|
-
this.recoveryCodes !== null &&
|
|
512
|
-
(await this.recoveryCodes.consume(claims.id, code));
|
|
513
|
-
if (!byTotp && !byRecovery)
|
|
514
|
-
badRequest();
|
|
626
|
+
// removed — legit for the last-device disable) OR a valid recovery code. Rate-limited
|
|
627
|
+
// and replay-guarded like `/auth/mfa` (Phase 8 #1).
|
|
628
|
+
await this.requireMfaProof(store, claims.id, code);
|
|
515
629
|
const removed = await store.remove(claims.id, id);
|
|
516
630
|
if (!removed) {
|
|
517
631
|
throw new common_1.HttpException({ error: 'not_found' }, common_1.HttpStatus.NOT_FOUND);
|
|
@@ -529,6 +643,7 @@ let AccountController = class AccountController {
|
|
|
529
643
|
* — a TOTP from any confirmed device OR an existing recovery code.
|
|
530
644
|
*
|
|
531
645
|
* Outcomes: 401; store/recovery/hasher absent → 501; MFA off → 400; wrong code → 400;
|
|
646
|
+
* MFA budget exhausted → 429; limiter/replay guard unreachable → 503;
|
|
532
647
|
* success → 200 `{ recoveryCodes }`.
|
|
533
648
|
*/
|
|
534
649
|
async regenerateRecoveryCodes(authorization, body) {
|
|
@@ -551,11 +666,9 @@ let AccountController = class AccountController {
|
|
|
551
666
|
// MFA must be on — else there's nothing to protect / no code source → generic 400.
|
|
552
667
|
if ((await store.countConfirmed(claims.id)) === 0)
|
|
553
668
|
badRequest();
|
|
554
|
-
// Proof: a TOTP from any confirmed device OR an existing recovery code
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (!byTotp && !byRecovery)
|
|
558
|
-
badRequest();
|
|
669
|
+
// Proof: a TOTP from any confirmed device OR an existing recovery code (rate-limited
|
|
670
|
+
// and replay-guarded like `/auth/mfa`, Phase 8 #1).
|
|
671
|
+
await this.requireMfaProof(store, claims.id, code);
|
|
559
672
|
const recoveryCodes = await this.replaceRecoveryCodes(claims.id, recoveryStore, hasher);
|
|
560
673
|
return { recoveryCodes };
|
|
561
674
|
}
|
|
@@ -664,7 +777,13 @@ exports.AccountController = AccountController = __decorate([
|
|
|
664
777
|
__param(8, (0, common_1.Inject)(tokens_js_2.SHIELD_MFA_DEVICE_STORE)),
|
|
665
778
|
__param(9, (0, common_1.Optional)()),
|
|
666
779
|
__param(9, (0, common_1.Inject)(tokens_js_2.SHIELD_SESSION_RESTRICTIONS)),
|
|
667
|
-
|
|
780
|
+
__param(10, (0, common_1.Optional)()),
|
|
781
|
+
__param(10, (0, common_1.Inject)(tokens_js_2.SHIELD_LOGIN_TOTP_VERIFIER)),
|
|
782
|
+
__param(11, (0, common_1.Optional)()),
|
|
783
|
+
__param(11, (0, common_1.Inject)(tokens_js_2.SHIELD_MFA_LIMITER)),
|
|
784
|
+
__param(12, (0, common_1.Optional)()),
|
|
785
|
+
__param(12, (0, common_1.Inject)(tokens_js_2.SHIELD_LOGIN_LIMITER)),
|
|
786
|
+
__metadata("design:paramtypes", [Function, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object])
|
|
668
787
|
], AccountController);
|
|
669
788
|
/**
|
|
670
789
|
* A deliberately-minimal email shape check (non-empty, a single `@`, a `.` in the
|
|
@@ -379,11 +379,12 @@ export declare class AuthController {
|
|
|
379
379
|
* {@link MfaStepUp} (same `OrTotpVerifier` + replay guard as the authenticator, principal
|
|
380
380
|
* minted by the identity broker).
|
|
381
381
|
*
|
|
382
|
-
* - `mfa:pid:<principalId>`
|
|
383
|
-
* step-up is never
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
382
|
+
* - the attempt is counted against `mfa:pid:<principalId>` BEFORE verifying (Phase 9
|
|
383
|
+
* #10, atomic increment-first): over budget → generic 401 and the step-up is never
|
|
384
|
+
* consulted; limiter unreachable → 503 (fail closed).
|
|
385
|
+
* - a rejected code stays counted; the attempt whose count equals the threshold audits
|
|
386
|
+
* `auth.mfa.locked` ONCE (no notify — pre-auth, see recovery lockout).
|
|
387
|
+
* - a transient outage → 503, the attempt is given back (never counted).
|
|
387
388
|
* - success → establish + bind, then reset the TOTP counter AND the login counter (the
|
|
388
389
|
* login counter is reset only here, once authentication fully succeeded).
|
|
389
390
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAeH,OAAO,KAAK,EACV,aAAa,EAIb,cAAc,EAGd,gBAAgB,EAEjB,MAAM,cAAc,CAAC;AA+BtB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAOjE,OAAO,EAA2C,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE3F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"auth.controller.d.ts","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqDG;AAeH,OAAO,KAAK,EACV,aAAa,EAIb,cAAc,EAGd,gBAAgB,EAEjB,MAAM,cAAc,CAAC;AA+BtB,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AACvF,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAOjE,OAAO,EAA2C,KAAK,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE3F,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,iCAAiC,CAAC;AAOtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,yCAAyC,CAAC;AAGjD,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,KAAK,EAAE,kBAAkB,EAAsB,MAAM,cAAc,CAAC;AAC3E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AAC7F,OAAO,KAAK,EAEV,YAAY,EACZ,aAAa,EACb,aAAa,EACb,UAAU,EACV,cAAc,EACd,cAAc,EAEf,MAAM,UAAU,CAAC;AA8GlB;;;;;GAKG;AACH,qBACa,cAAc;IAGvB,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU;IAO3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAO7B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAOjC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAC;IAIpC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAIjC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAI7B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAIlC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAIhC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAKhC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAC;IAOlC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAY/B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAM9B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAMhC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAIjC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAQjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC;IAK/B,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAKnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAO9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAS3B,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAAC;IAWvC,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAC;IAStC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IAYnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC;IAMjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAK5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAK3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC;IAGvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;IAM3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC;IAMzB,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAMhC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC;gBA3LpB,aAAa,EAAE,aAAa,EAE5B,gBAAgB,EAAE,gBAAgB,EAClC,UAAU,EAAE,eAAe,EAO3B,WAAW,CAAC,EAAE,kBAAkB,YAAA,EAOhC,eAAe,CAAC,EAAE,eAAe,YAAA,EAOjC,kBAAkB,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA,EAI9C,eAAe,CAAC,GAAE,eAAe,GAAG,IAAI,aAAA,EAIxC,WAAW,CAAC,GAAE,WAAW,GAAG,IAAI,aAAA,EAIhC,gBAAgB,CAAC,GAAE,gBAAgB,GAAG,IAAI,aAAA,EAI1C,cAAc,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAItC,cAAc,CAAC,EAAE,cAAc,YAAA,EAK/B,gBAAgB,CAAC,EAAE,MAAM,YAAA,EAOzB,aAAa,CAAC,GAAE,iBAAiB,GAAG,IAAI,aAAA,EAYxC,YAAY,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAMpC,cAAc,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAMtC,eAAe,CAAC,GAAE,mBAAmB,GAAG,IAAI,aAAA,EAI5C,eAAe,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAQvC,aAAa,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAKrC,iBAAiB,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAKzC,YAAY,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAOpC,SAAS,CAAC,GAAE,SAAS,GAAG,IAAI,aAAA,EAS5B,qBAAqB,CAAC,GAAE,qBAAqB,GAAG,IAAI,aAAA,EAWpD,oBAAoB,CAAC,GAAE,oBAAoB,GAAG,IAAI,aAAA,EASlD,iBAAiB,CAAC,GAAE,iBAAiB,GAAG,IAAI,aAAA,EAY5C,eAAe,CAAC,GAAE,6BAA6B,GAAG,IAAI,aAAA,EAMtD,UAAU,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAKlC,SAAS,CAAC,GAAE,SAAS,GAAG,IAAI,aAAA,EAK5B,KAAK,CAAC,GAAE,KAAK,GAAG,IAAI,aAAA,EAGpB,SAAS,CAAC,GAAE,qBAAqB,GAAG,IAAI,aAAA,EAMxC,OAAO,CAAC,GAAE,SAAS,MAAM,EAAE,GAAG,IAAI,aAAA,EAMlC,cAAc,CAAC,GAAE,IAAI,CAAC,kBAAkB,EAAE,iBAAiB,CAAC,GAAG,IAAI,aAAA,EAMnE,mBAAmB,CAAC,GAAE,uBAAuB,GAAG,IAAI,aAAA;IAGvE;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IASpB;;;;;OAKG;YACW,WAAW;IAiEzB;;;;;;;;;;OAUG;YACW,eAAe;IAgB7B;;;;;;;;;;;;OAYG;YACW,mBAAmB;IAIjC;;;;;;;;OAQG;IACH;;;;;;;;OAQG;YACW,iBAAiB;YAcjB,sBAAsB;IAcpC;;;;;;;;;;OAUG;YACW,gBAAgB;IAY9B;;;;;;;;OAQG;YACW,uBAAuB;IAarC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;;;;;;;;OAUG;IACH;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK;YAIC,aAAa;IAM3B;;;;OAIG;IAIG,KAAK,CACD,IAAI,EAAE,YAAY,EAIpB,EAAE,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC;IA0JzB;;;;;;;;;;;;;;;;;;OAkBG;IAIG,EAAE,CAA2B,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmCvF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IAIG,WAAW,CACP,IAAI,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,EACb,aAAa,CAAC,EAAE,MAAM,GAC/C,OAAO,CAAC;QAAE,iBAAiB,EAAE,MAAM,CAAC;QAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,CAAC;IAqFjF;;;;;;;;;;;;;;;;;;;OAmBG;IAIG,cAAc,CACV,IAAI,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,EAKjC,EAAE,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgEhB;;;;;;;;;;OAUG;YACW,oBAAoB;IAqClC;;;;;;;;;OASG;YACW,gBAAgB;IAwC9B;;;;;;;;;;;;OAYG;IAIG,aAAa,CACT,IAAI,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAEhD,EAAE,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAsFhB;;;;;;;;;;;;;;;;OAgBG;IAIG,GAAG,CAAS,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,cAAc,CAAC;IA0B5D;;;;;;;;;;;;;;OAcG;YACW,eAAe;IA8C7B;;;;;;;;;;;;;;;OAeG;YACW,uBAAuB;IAuGrC;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAoC1B;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,OAAO,CAAC,mBAAmB;IAuC3B;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,gBAAgB;IAIxB;;;;;;;;;;OAUG;IAIG,OAAO,CAAS,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC;IAkEpE;;;;OAIG;YACW,cAAc;IAe5B;;;;;;;;;;;;;;;;;;;;OAoBG;YACW,cAAc;IAgE5B;;;;OAIG;IAIG,MAAM,CAAS,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAexD;;;;;OAKG;YACW,iBAAiB;CAOhC"}
|