fix(auth): normalize email case in login - #185
Merged
Merged
Conversation
`POST /api/auth/login` was the only email-taking handler that did not normalize its input. `register`/`verify`/`resend-code`/`forgot-password`/ `reset-password` all do `req.email.trim().to_lowercase()`, and registration stores the lowercased form. `users.email` has no `COLLATE NOCASE`, so SQLite `=` is case-sensitive and any case-varied input (e.g. a mobile keyboard auto-capitalizing the first letter) failed with 401 "unauthorized" even though the account exists and the password is correct. Add the same normalization to `login` before the lookup, and a regression test that registers with a mixed-case email, verifies it, then asserts that both the canonical lowercase and the mixed-case variant can log in.
8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /api/auth/loginwas the only email-taking handler that did not normalize its input:register/verify/resend-code/forgot-password/reset-passwordall doreq.email.trim().to_lowercase(), and registration stores the lowercased form. Sinceusers.emailisTEXT NOT NULL UNIQUEwith noCOLLATE NOCASE, SQLite=is case-sensitive, so any case-varied input was treated as "user does not exist" → 401, even though the account exists and the password is correct.This is reachable in practice:
ui/index.htmlusestype="email"withoutautocapitalize="none", so a mobile keyboard auto-capitalizes the first letter and the user is locked out of their own account with a correct password — whileforgot-password(which does normalize) still finds the account and returns "rate limited".Related Issue
None — no open issue describes this. Verified
closingIssuesReferencesis empty on purpose; nothing is fabricated.Changes
login(src/routes/mod.rs) with the sametrim().to_lowercase()the five sibling handlers already use, then pass it todao::verify_user_password. The 401 message is deliberately unchanged and still does not distinguish "no such user" from "wrong password" (that would reintroduce an account-enumeration oracle this module otherwise avoids).COLLATE NOCASE, which would need a migration and change UNIQUE semantics).Tests
cargo test全部通过 — 168 passed / 0 failed (baseline 167 + the new test).cargo fmt --check通过login_normalizes_email_case: registers with a mixed-case email (asserting it is stored as the lowercase canonical form), verifies it, then asserts both the canonical lowercase login and the mixed-case variant return 200. The canonical-lowercase assertion is a positive control so the test cannot pass by accepting anything.A/B of the test itself — with only the handler change reverted, the new test fails exactly at the property assertion:
With the fix in place it passes. So the test genuinely pins the defect rather than the fix.
Checklist
fix/login-email-normalizefix(auth): normalize email case in login