Skip to content

fix(auth): enforce lockout and move email-confirm to post-auth in Login#186

Open
davidortinau wants to merge 1 commit into
mainfrom
op-fix-login-lockout-email-confirm
Open

fix(auth): enforce lockout and move email-confirm to post-auth in Login#186
davidortinau wants to merge 1 commit into
mainfrom
op-fix-login-lockout-email-confirm

Conversation

@davidortinau
Copy link
Copy Markdown
Owner

What

Two intertwined security bugs in the login path:

Bug 1 — Pre-authentication email auto-confirm

AuthEndpoints.cs (lines 135–142) was calling ConfirmEmailAsync before verifying the password. Any anonymous caller who knew a registered email address could POST /api/auth/login with any password and the targeted account's email would be silently auto-confirmed as a side-effect — leaking that the address is registered and bypassing the email-verification requirement without user consent.

Fix: The email-confirmation block is removed entirely. After a successful password check, we do a read-only IsEmailConfirmedAsync and return 401 if unconfirmed — no mutation, no information leak.

Bug 2 — Identity lockout bypass

Both AuthEndpoints.cs and ServerAuthService.cs called UserManager.CheckPasswordAsync directly. That method verifies the password hash but does not record failed attempts or enforce account lockout, so an attacker could brute-force any account's password despite the lockout policy configured in Program.cs.

Fix: Replaced with a manual lockout guard around the existing CheckPasswordAsync call (avoids introducing a SignInManager dependency into the minimal-API endpoint):

  1. IsLockedOutAsync — return HTTP 429 immediately if locked out
  2. AccessFailedAsync — increment the failure counter on a wrong password
  3. ResetAccessFailedCountAsync — reset on success

Same pattern applied to ServerAuthService.cs.

Files changed

File Change
src/SentenceStudio.Api/Auth/AuthEndpoints.cs Lockout guard + email-confirm block moved/simplified
src/SentenceStudio.WebApp/Auth/ServerAuthService.cs Lockout guard added
tests/SentenceStudio.Api.Tests/IdentityAuthTests.cs 2 regression tests added

Regression tests added

  • Login_WrongPassword_DoesNotAutoConfirmEmail — asserts EmailConfirmed stays false after a failed login
  • Login_ExcessiveFailures_TriggersLockout — asserts the account is locked (HTTP 429) after 5 wrong-password attempts

Verification

  • dotnet build src/SentenceStudio.Api/0 errors
  • All IdentityAuthTests fail pre-existing (missing IChatClient DI in test host, 19 failures on main before this branch); the new test failures share the same root cause — the assertions themselves are not reached.

AuthEndpoints.cs:
- Remove pre-authentication email auto-confirm (was a side-effect
  mutation exposed to unauthenticated callers with any email address)
- Check IsLockedOutAsync before attempting password verification;
  return 429 if the account is locked out
- On a failed CheckPasswordAsync, call AccessFailedAsync so failed
  attempts are recorded and eventually trigger lockout
- On success, call ResetAccessFailedCountAsync then re-check
   returning 401 without confirming email,IsEmailConfirmedAsync
  so the check is purely read-only and safe for any caller

ServerAuthService.cs:
- Same lockout guard: IsLockedOutAsync before CheckPasswordAsync,
  AccessFailedAsync on failure, ResetAccessFailedCountAsync on success

IdentityAuthTests.cs:
- Add Login_WrongPassword_DoesNotAutoConfirmEmail regression test
- Add Login_ExcessiveFailures_TriggersLockout regression test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant