F5.5 brute-force hardening:
- internal/twofa.Check consume is now a conditional UPDATE (WHERE id AND
two_factor_pending_code_hash) reporting rows affected: two concurrent
verifications of the same code on different instances both match the digest,
but only the first conditional UPDATE can affect a row — the loser sees 0
rows and fails MissingOrExpired, so one code authorizes exactly ONE operation
across instances (the per-user mutex only serialized within one process).
- /login lockout is now indistinguishable from a wrong password: a locked
account returns the same uniform 401 'invalid credentials' and burns the same
constant-time bcrypt compare (via the shared semaphore), removing the
account-existence oracle and lockout-probing signal of the old 429.
- Lockout tiers escalate 15m (5+) / 30m (7+) / 60m (10+): an attacker who keeps
guessing past each unlock makes the lock LONGER, raising the repeat-DoS
effort while the response stays uniform.