Phase 19: Local Auth (No-OIDC Mode) #23
@@ -135,9 +135,14 @@ localAuthRouter.post('/local/login', zValidator('json', loginSchema, noEchoHook)
|
||||
// Increment the counter even on 429 so continued brute-force accumulates toward lockout.
|
||||
if (live && live.count >= RATE_WINDOW_FAILURES && now < live.lockedUntil) {
|
||||
live.count += 1;
|
||||
live.lockedUntil = now + RATE_WINDOW_SECS * 1000;
|
||||
// WR-06: do NOT extend lockedUntil here. This request was itself REJECTED by the window;
|
||||
// re-arming the cooldown on every blocked attempt let an attacker who keeps hammering the
|
||||
// endpoint slide the window forward forever, so a legitimate user behind the same identity
|
||||
// could never get back in even after pausing. The window stays anchored to when it was
|
||||
// first armed (in the failure path below); it expires on schedule regardless of rejected
|
||||
// traffic. The lockout (423) still triggers once the failure count crosses the threshold.
|
||||
live.lockedOut = live.count >= LOCKOUT_FAILURES;
|
||||
if (live.lockedOut) live.lockedAt = now;
|
||||
if (live.lockedOut && live.lockedAt === 0) live.lockedAt = now;
|
||||
loginAttempts.set(key, live);
|
||||
if (live.lockedOut) {
|
||||
return c.json({ error: 'Account locked' }, 423);
|
||||
|
||||
Reference in New Issue
Block a user