[AZ-556] [AZ-557] Unify login errors + share MFA lockout pipeline

AZ-556 collapses every /login rejection (unknown email, wrong password,
disabled account, lockout, per-account rate limit) to a single opaque
InvalidCredentials (70) → 401 response. Timing equalised by a new
Security.VerifyDummy using the same Argon2id parameters. Audit log keeps
the rejection category internally (login_failed_unknown_email,
login_failed_disabled).

AZ-557 wires /login/mfa into the existing per-account lockout +
rate-limit pipeline. MFA failures now feed UserService's shared failure
accounting (RegisterMfaFailedLogin → RegisterFailedLoginCore) and
CountRecentFailedLogins aggregates both login_failed and
mfa_login_failed rows. Successful TOTP / recovery resets the counter.

Deprecated five legacy ExceptionEnum members (NoEmailFound,
WrongPassword, UserDisabled, AccountLocked, LoginRateLimited) — kept
defined for cross-workspace verifier compatibility during the
deprecation window.

E2E coverage updated: AuthTests (byte-identical body assertion +
disabled-account audit row), LoginRateLimitTests, PasswordHashingTests,
SecurityTests, plus four new MfaLoginTests (AC1, AC2, AC5, AC7).

Code review verdict: PASS_WITH_WARNINGS (batch_06_cycle2_review.md).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 09:56:00 +03:00
parent ebde2b2d25
commit 4bf2e689cb
16 changed files with 537 additions and 100 deletions
@@ -1,41 +0,0 @@
# Deferred Jira transitions — cycle-2 hotfix batch 5
- **Timestamp**: 2026-05-14T09:36:00+03:00
- **Blocked operation**: Jira ticket status transitions for AZ-552, AZ-553, AZ-554, AZ-555
- **Reason**: Atlassian MCP availability was not probed during the batch-5 implement
session (operator chose to continue in the same conversation across the Step 9→10
session boundary; verifying the MCP before tracker writes would have consumed
additional context budget). Deferring to the start of batch 6 lets both batches'
transitions replay in one MCP-availability check.
## Payload to replay
For each of the four tickets below, attempt **both** transitions in order. Failure
of any single transition fills a new leftover entry for that specific ticket and
proceeds. If the MCP itself is unreachable, follow the tracker.mdc Tracker
Availability Gate (Choose A/B/C/D — Retry / Continue in `tracker: local`).
| Ticket | From | Via | To | Comment to add on transition |
|--------|-------|-------------------|--------------|-------------------------------|
| AZ-552 | To Do | "Start work" | In Progress | "Batch 5 implement started (commit a77b3f8..f369153)." |
| AZ-552 | In Progress | "Ready for QA" | In Testing | "Batch 5 landed locally (commit f369153). AC coverage: 4/4 (1 exec + 3 deploy-rehearsal skip). Awaiting autodev Step 11 Run Tests gate." |
| AZ-553 | To Do | "Start work" | In Progress | "Batch 5 implement started (commit f369153)." |
| AZ-553 | In Progress | "Ready for QA" | In Testing | "Batch 5 landed locally (commit f369153). AC coverage: 5/5 (1 exec + 4 deploy-rehearsal skip)." |
| AZ-554 | To Do | "Start work" | In Progress | "Batch 5 implement started (commit f369153)." |
| AZ-554 | In Progress | "Ready for QA" | In Testing | "Batch 5 landed locally (commit f369153). AC coverage: 5/5 (1 exec — Development smoke + 4 Production-only / restart-test skip)." |
| AZ-555 | To Do | "Start work" | In Progress | "Batch 5 implement started (commit f369153)." |
| AZ-555 | In Progress | "Ready for QA" | In Testing | "Batch 5 landed locally (commit f369153). AC coverage: 5/5 (4 exec README/env consistency + 1 fresh-operator-dry-run skip)." |
## Verification commits (visible in `git log --oneline -10` on `dev`)
- `a77b3f8` — Cycle-2 documentation refresh (AZ-529, AZ-530)
- `1bdbe8c` — Cycle-2 security audit reports (AZ-529, AZ-530)
- `d2b5308` — Cycle-2 hotfix task intake (AZ-552..AZ-557, 11 pts)
- `f369153` — Cycle-2 hotfix: deploy/infra chain (AZ-552, AZ-553, AZ-554, AZ-555)
## Replay obligation
Per `.cursor/rules/tracker.mdc` Leftovers Mechanism, the next `/autodev` step 0
must attempt this replay before progressing. On success, delete this file. On
per-ticket failure, leave the entry and update its timestamp + reason for the
specific ticket(s) that failed.