feat: harden PUT /users/me — validate display name, re-verify on email change - #145
Merged
Conversation
…l change Two problems in the profile-update path, found while looking at first_name. The real one: changing your email did not un-verify the account. Since email_verified_at now gates the AI-cost endpoints and drives the stale-signup sweep, a user could verify a throwaway address, swap in an address nobody ever proved, and keep "verified" status. The address change now clears verification and mails a fresh code to the NEW address; when verification isn't live the new address is auto-verified instead, mirroring what signup does in that mode. Email and verification state move in one statement so an account can never be left verified on an unproved address. Also on that path: - first_name was unvalidated free text on an unbounded text column, at both signup and update. It now allows any Unicode letter plus the punctuation real names use (Mary-Jane, O'Brien, J. R., José, 李) and rejects digits, emoji, symbols and control characters, capped at 50. Deliberately NOT profanity-checked: the name is only ever shown back to its owner, so a filter would buy no moderation and would reject people named Dick or Fanny. - A taken email returned 500. It now returns 409, using the sentinel the repo was already mapping. - Email length is capped at 254 (RFC 5321); the column is unbounded text. - Re-typing your own address in different case no longer counts as a change, so it can't cost you your verified status. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0194PdH4wDTnz5SWfzyoKagc
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
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.
Started as the
first_namefollow-up.first_nameturned out to be the smaller problem.The real bug: an email change didn't un-verify the account
email_verified_atis what gates the AI-cost endpoints (as of this morning's flip) and what the stale-signup sweep keys off. ButPUT /users/meswapped the address and left the verified flag set — so the flow below kept "verified" status on an address nobody ever proved:PUT /users/me {"email": "anything@else.com"}→ address changes,email_verified_atstays set.That makes
email_verified_atmean "this account once proved some address," which is not what it's gating on.Fix: changing the address clears verification and mails a fresh code to the new address (best-effort, exactly like signup). When verification isn't live, the new address is auto-verified instead — mirroring signup in that mode, since nothing would ever send a code. Email and verification state now move in a single
UPDATE, so an account can't be left verified on an unproved address even if something fails midway.Also on that path
first_namewas unvalidated free text on an unboundedtextcolumn, at both signup and update. Now: any Unicode letter plus the punctuation real names use —Mary-Jane,O'Brien,J. R.,José,Zoë,李— and nothing else. Digits, emoji, symbols and control characters are rejected (so no newline-stuffed or 5,000-character names), capped at 50.ErrEmailTaken, the handler just wasn't checking it.Verification
go test ./... -count=1green. New tests cover the un-verify, the auto-verify-when-disabled path, the case-only no-op, the 409, and theValidateFirstNametable (includingJosé/李/O'Brienpassing and emoji/newline/digits failing).Two things I checked rather than assumed:
MockUserRepohands back the same pointer it stores, so a naive test would assert on the service's in-memory mutation and pass even if it never wrote to the repo. Tests pass a copy (sessionCopy) so the "did it persist?" assertions are real. Reverting the un-verify logic makesTestUpdateUser_EmailChangeUnverifiesAccountfail with "stored account still verified on an address nobody proved" — confirmed.StartVerificationrefuses to send to an account that still looks verified, and mailsuser.Email— soUpdateUserupdates the in-memory user, or the code would go to the old address.Note
TestVideoImport_NativeVideoUsedflaked once during a full-suite run here —waitForVideoJobpolls an async job against a hard 3s wall-clock deadline (import_video_test.go:187). Unrelated to this change (passes 3/3 isolated, 3/3 in the full package, suite green), but it can redden CI at random. Happy to fix separately.🤖 Generated with Claude Code
https://claude.ai/code/session_0194PdH4wDTnz5SWfzyoKagc