Phase 19: Local Auth (No-OIDC Mode) #23

Merged
luckberg merged 78 commits from gsd/phase-19-local-auth-no-oidc-mode into main 2026-06-18 06:25:00 -04:00
Showing only changes of commit 4cf2ad4bff - Show all commits
+7 -2
View File
@@ -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);