Commit Graph

2 Commits

Author SHA1 Message Date
a4355e1ef3 refactor(auth): drop JWT_EXPIRY env var (family TTL is canonical)
The JWT lifetime is now pinned to SESSION_TTL_SECONDS (30 days)
inside issueSessionCookie, so JWT_EXPIRY had no effect after the
prior commits. Removing the field from Config and the env read in
loadConfig lets the TypeScript compiler verify no caller was still
relying on it.

Per the family invariant ('coherence across apps is a feature'),
the per-app override is intentionally gone — a deploy with stale
JWT_EXPIRY in .env will now silently use the 30-day family default
regardless of the value.

Test fixtures (Config literals in setup.ts, login-handler.test.ts,
lockout-service.test.ts) and config.test.ts assertions updated to
match the new shape.
2026-05-09 10:17:04 -07:00
ad36b23061 feat(auth): add shared login handler with constant-time clamp
All checks were successful
Deploy to Homelab / deploy (push) Successful in 18s
Single attemptLogin() orchestrates lockout check, bcrypt verify (against
real or dummy hash), success/failure logging, and a configurable minimum
response-time clamp. Both login routes will share this — no duplication.

Adds three logger events for the operator's escalation pipeline:
AUTH_LOCKOUT_TRIGGERED, AUTH_LOCKED_ATTEMPT, and AUTH_RATE_LIMITED.
fail2ban filters can pick these up to escalate persistent attackers from
in-app lockout to IP ban.

Constant-time defense: unknown users still pay bcrypt cost (via dummy hash)
and the clamp ensures locked-vs-unknown-vs-wrong-password aren't
distinguishable by response time. Username is canonicalized (lowercase + trim)
before lookup so attackers can't bypass lockout via case variation.
2026-05-03 03:34:15 -07:00