Phase 19: Local Auth (No-OIDC Mode) #23
@@ -97,6 +97,29 @@ export function resetLoginAttempts(username: string): void {
|
||||
loginAttempts.delete(username.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* IN-03: evict stale rate-limit entries to bound the in-memory map size.
|
||||
*
|
||||
* An entry is stale (and safe to drop) when it is neither inside an active rate-limit
|
||||
* window nor inside an active lockout TTL:
|
||||
* - locked-out entries expire LOCKOUT_TTL_MS after lockedAt
|
||||
* - non-locked entries expire once their lockedUntil window has passed
|
||||
*
|
||||
* Called opportunistically at the top of each login request. Dropping an entry is
|
||||
* behaviourally identical to the entry having naturally expired, so eviction never
|
||||
* weakens the brute-force defense — it only reclaims memory for identities no longer
|
||||
* under active rate-limiting.
|
||||
*/
|
||||
function evictStaleLoginAttempts(now: number): void {
|
||||
for (const [k, v] of loginAttempts) {
|
||||
const lockoutExpired = v.lockedOut ? now - v.lockedAt >= LOCKOUT_TTL_MS : true;
|
||||
const windowExpired = now >= v.lockedUntil;
|
||||
if (lockoutExpired && windowExpired) {
|
||||
loginAttempts.delete(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-computed dummy hash used to run verifyPassword on unknown-username paths
|
||||
// (timing defense — prevents timing-oracle username enumeration, RESEARCH Pitfall 2).
|
||||
// WR-03: hashPassword is now async. Kick off the computation once at module load and keep
|
||||
@@ -115,6 +138,9 @@ localAuthRouter.post('/local/login', zValidator('json', loginSchema, noEchoHook)
|
||||
const key = username; // loginSchema .trim()s the username; the map key matches resetLoginAttempts
|
||||
|
||||
const now = Date.now();
|
||||
// IN-03: opportunistically reclaim memory from entries that are no longer actively
|
||||
// rate-limited or locked out. Cheap at household scale; bounds the map under input churn.
|
||||
evictStaleLoginAttempts(now);
|
||||
const attempt = loginAttempts.get(key);
|
||||
|
||||
// 423: account locked (>= LOCKOUT_FAILURES failures). CR-04: the lockout auto-expires
|
||||
|
||||
Reference in New Issue
Block a user