@astram/guard 0.10.0 → 0.10.1
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.
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `AttemptLimiter` — a guard-owned, Redis-backed
|
|
3
|
-
* anti-brute-force on the auth surface (spec §5).
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* (
|
|
2
|
+
* `AttemptLimiter` — a guard-owned, Redis-backed FIXED-WINDOW attempt counter for
|
|
3
|
+
* anti-brute-force on the auth surface (spec §5). The window starts at the first counted
|
|
4
|
+
* attempt (`PEXPIRE windowMs`) and is not extended by later ones; the counter resets when
|
|
5
|
+
* it expires or on a success. One class, one instance per DI token with its own threshold:
|
|
6
|
+
* `SHIELD_LOGIN_LIMITER` (`login:pid:<id>`, shared by `/auth/login` and change-password),
|
|
7
|
+
* the per-IP login limiter, `SHIELD_MFA_LIMITER` (`mfa:pid:<id>`, `/auth/mfa` TOTP and the
|
|
8
|
+
* code-gated account MFA routes), `SHIELD_RECOVERY_LIMITER` (`/auth/mfa` recovery codes),
|
|
9
|
+
* and the forgot/reset-password limiters.
|
|
7
10
|
*
|
|
8
11
|
* ── CONTRACT ─────────────────────────────────────────────────────────────────
|
|
9
12
|
* consume(key) → Phase 9 #10, the gate for credential-verifying routes (login,
|
|
@@ -18,7 +21,7 @@
|
|
|
18
21
|
* `false` if AT/over it (locked out for the window).
|
|
19
22
|
* recordFailure(key) → increment the counter; on the FIRST failure in a window,
|
|
20
23
|
* set the window TTL (`PEXPIRE windowMs`) so the counter
|
|
21
|
-
* auto-expires (a
|
|
24
|
+
* auto-expires (a fixed window from the first failure).
|
|
22
25
|
* RETURNS the new count so a caller can detect the LOCKOUT
|
|
23
26
|
* TRANSITION (count === maxAttempts is the exact edge where
|
|
24
27
|
* the NEXT check() flips to `false`) and act ONCE on it — e.g.
|
|
@@ -41,9 +44,12 @@
|
|
|
41
44
|
* count we couldn't write just means the window is a touch more lenient — never a
|
|
42
45
|
* thrown error into the request path).
|
|
43
46
|
*
|
|
44
|
-
* ──
|
|
45
|
-
*
|
|
46
|
-
* locked-out account is indistinguishable from a bad credential.
|
|
47
|
+
* ── WHAT A LOCK ANSWERS ── the limiter itself is silent; the caller decides. On the
|
|
48
|
+
* unauthenticated `/auth/*` routes a lock is the SAME generic 401 as a wrong password /
|
|
49
|
+
* wrong code, so a locked-out account is indistinguishable from a bad credential. On the
|
|
50
|
+
* authenticated `/account/*` routes (change-password, MFA device remove, recovery-code
|
|
51
|
+
* regenerate) the caller already holds a session, so a lock answers 429 (Phase 8 #1,
|
|
52
|
+
* Phase 9 #3).
|
|
47
53
|
*
|
|
48
54
|
* ADR-0023: guard owns the infra adapter (ioredis); the class is a thin, injectable
|
|
49
55
|
* collaborator the controller consumes via a DI token (`SHIELD_LOGIN_LIMITER` /
|
|
@@ -73,7 +79,7 @@ export interface AttemptVerdict {
|
|
|
73
79
|
export declare class AttemptLimiterUnavailableError extends Error {
|
|
74
80
|
constructor();
|
|
75
81
|
}
|
|
76
|
-
/** Per-limiter tuning: the failure ceiling and the
|
|
82
|
+
/** Per-limiter tuning: the failure ceiling and the fixed-window length (ms). */
|
|
77
83
|
export interface AttemptLimiterConfig {
|
|
78
84
|
/** Number of failures within the window that trips the lockout (check → false). */
|
|
79
85
|
readonly maxAttempts: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attempt-limiter.d.ts","sourceRoot":"","sources":["../../../src/auth/rate-limit/attempt-limiter.ts"],"names":[],"mappings":"AAAA
|
|
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,10 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* `AttemptLimiter` — a guard-owned, Redis-backed
|
|
4
|
-
* anti-brute-force on the auth surface (spec §5).
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* (
|
|
3
|
+
* `AttemptLimiter` — a guard-owned, Redis-backed FIXED-WINDOW attempt counter for
|
|
4
|
+
* anti-brute-force on the auth surface (spec §5). The window starts at the first counted
|
|
5
|
+
* attempt (`PEXPIRE windowMs`) and is not extended by later ones; the counter resets when
|
|
6
|
+
* it expires or on a success. One class, one instance per DI token with its own threshold:
|
|
7
|
+
* `SHIELD_LOGIN_LIMITER` (`login:pid:<id>`, shared by `/auth/login` and change-password),
|
|
8
|
+
* the per-IP login limiter, `SHIELD_MFA_LIMITER` (`mfa:pid:<id>`, `/auth/mfa` TOTP and the
|
|
9
|
+
* code-gated account MFA routes), `SHIELD_RECOVERY_LIMITER` (`/auth/mfa` recovery codes),
|
|
10
|
+
* and the forgot/reset-password limiters.
|
|
8
11
|
*
|
|
9
12
|
* ── CONTRACT ─────────────────────────────────────────────────────────────────
|
|
10
13
|
* consume(key) → Phase 9 #10, the gate for credential-verifying routes (login,
|
|
@@ -19,7 +22,7 @@
|
|
|
19
22
|
* `false` if AT/over it (locked out for the window).
|
|
20
23
|
* recordFailure(key) → increment the counter; on the FIRST failure in a window,
|
|
21
24
|
* set the window TTL (`PEXPIRE windowMs`) so the counter
|
|
22
|
-
* auto-expires (a
|
|
25
|
+
* auto-expires (a fixed window from the first failure).
|
|
23
26
|
* RETURNS the new count so a caller can detect the LOCKOUT
|
|
24
27
|
* TRANSITION (count === maxAttempts is the exact edge where
|
|
25
28
|
* the NEXT check() flips to `false`) and act ONCE on it — e.g.
|
|
@@ -42,9 +45,12 @@
|
|
|
42
45
|
* count we couldn't write just means the window is a touch more lenient — never a
|
|
43
46
|
* thrown error into the request path).
|
|
44
47
|
*
|
|
45
|
-
* ──
|
|
46
|
-
*
|
|
47
|
-
* locked-out account is indistinguishable from a bad credential.
|
|
48
|
+
* ── WHAT A LOCK ANSWERS ── the limiter itself is silent; the caller decides. On the
|
|
49
|
+
* unauthenticated `/auth/*` routes a lock is the SAME generic 401 as a wrong password /
|
|
50
|
+
* wrong code, so a locked-out account is indistinguishable from a bad credential. On the
|
|
51
|
+
* authenticated `/account/*` routes (change-password, MFA device remove, recovery-code
|
|
52
|
+
* regenerate) the caller already holds a session, so a lock answers 429 (Phase 8 #1,
|
|
53
|
+
* Phase 9 #3).
|
|
48
54
|
*
|
|
49
55
|
* ADR-0023: guard owns the infra adapter (ioredis); the class is a thin, injectable
|
|
50
56
|
* collaborator the controller consumes via a DI token (`SHIELD_LOGIN_LIMITER` /
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/guard",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Astram framework — the enforcement guard + real-world adapters (the 'hands'). One global deny-by-default gate every request passes through (ADR-0002), classification decorators, boot-pool assertion (ADR-0018), the forRoot() registration seam, and the DB/Redis/crypto adapters that connect the sealed core to a real backend. Depends on @astram/core (ADR-0003, ADR-0023).",
|
|
6
6
|
"type": "commonjs",
|