Skip to content

Account settings: change avatar, username, email, and password - #5

Open
notAidven wants to merge 1 commit into
feature/auth-google-email-linkfrom
feature/account-settings
Open

Account settings: change avatar, username, email, and password#5
notAidven wants to merge 1 commit into
feature/auth-google-email-linkfrom
feature/account-settings

Conversation

@notAidven

Copy link
Copy Markdown
Owner

Stacked on #2 (feature/auth-google-email-link). Adds a cohesive Account settings section to the profile page so a signed-in user can change their avatar, username, email, and password. Each row has inline edit + validation + loading state + success/error feedback.

Zero Cloud Functions / no Blaze required — everything runs on the client Firebase Auth + Firestore SDKs (free tier).

Capabilities

  1. Avatar — reuses the poker AnimalPicker; saving writes profileAnimal to users/{uid} and calls refreshProfile() so the header avatar + profile update immediately.
  2. Username — same validation + uniqueness as signup (validateUsername 3–20 chars [a-zA-Z0-9_], lowercased index key, usernames/{name} uniqueness). The rename runs in one Firestore transaction: it checks usernames/{new} is free, deletes the old usernames/{old}, claims usernames/{new} ({uid, email}), and renames users/{uid}.username — so a name is never double-claimed and the old one is never orphaned. Taken names are rejected with a clear message. A case-only edit (e.g. mayaMaya) just updates the display name (same index key, so no release/claim).
  3. Email — uses verifyBeforeUpdateEmail, which sends a confirmation link to the new address; the account email only changes after the user opens that link. The UI states this clearly and tells them to keep using their current email until then. On the next load (fresh token), the app reconciles users/{uid}.email and the usernames lookup doc to the new lowercased address so username + password sign-in keeps working.
  4. Password
    • Password users: updatePassword after asking for the current password and re-authenticating first.
    • Google-only users (needsPasswordSetup): the password row delegates to the existing SetPasswordCard / linkEmailPassword flow (scrolls to + focuses it) instead of duplicating it.
    • Strength/length validated identically to signup (min 6 + confirmation match).

Shared re-authentication flow

One reusable reauthenticate(password?) helper on AuthContext, used by both the email and password changes:

  • password accounts → reauthenticateWithCredential(EmailAuthProvider.credential(...))
  • Google-only accounts → reauthenticateWithPopup(google)

Email change triggers it on auth/requires-recent-login (then retries automatically via the shared ReauthPrompt); password change always re-auths up front. Friendly messages route through authErrors.ts, including a new 'reauth' context so a wrong current password reads "Incorrect password." (not the login-style "username or password"). Handles auth/wrong-password, auth/invalid-credential, auth/email-already-in-use, auth/popup-closed-by-user, auth/requires-recent-login, etc.

firestore.rules change — NOT deployed (maintainer must deploy)

firestore.rules is updated but not deployed; please deploy it for the feature to work in production. All changes stay tightly scoped + secure:

  • users/{uid} update: email may only be set to the caller's verified request.auth.token.email (or left unchanged); username may change to any string (uniqueness/ownership enforced by the usernames index). Field whitelist + gamification validators are unchanged.
  • usernames/{name}: create unchanged (must not exist, own uid + token email); new delete only if the doc points to your own uid; new update only by the owner, uid immutable, email pinned to your own verified token email. A user can never point a name at an address they have not proven they own, nor touch another user's doc.

UX / a11y

  • Consistent with the felt "night" panels; controls disable while saving; explicit success confirmation; no em dashes.
  • Accessible: labeled inputs, aria-expanded/aria-controls disclosure toggles, role="alert" errors + role="status" success, focus moved to the first field on open.
  • Does not regress the existing SetPasswordCard or the set-password banner.

Verification (from web/)

  • npm run build → exit 0
  • npx vitest run → 64 passed (added tests for the email/password validators + the reauth error context)
  • node_modules/.bin/tsc -p tsconfig.app.json --noEmit → clean
  • node scripts/mvp-logic-check.mjs → all PASS
  • npm run lint → no new errors introduced (pre-existing react-hooks/react-refresh findings only)

Do not auto-merge.

Made with Cursor

Add a cohesive "Account settings" section to the profile with inline
edit + validation + loading + success/error feedback for four things,
all on the client Firebase Auth + Firestore SDKs (no Cloud Functions):

- Avatar: reuse the poker AnimalPicker, write profileAnimal; reflects in
  the header + profile immediately via refreshProfile.
- Username: same validation + uniqueness as signup. Atomically release
  usernames/{old} and claim usernames/{new} in one transaction and rename
  users/{uid}.username; taken names are rejected.
- Email: verifyBeforeUpdateEmail sends a confirm link to the NEW address
  (email changes only after they click it); reconcile users/{uid}.email +
  usernames lookup on next load. Re-auth on auth/requires-recent-login.
- Password: updatePassword for password users (ask current + reauth);
  Google-only users reuse the existing SetPasswordCard/linkEmailPassword
  flow (no duplication).

Add one shared reauthenticate() helper on AuthContext (password credential
or Google popup) used by both the email + password changes, with friendly
messages via authErrors.ts (new 'reauth' context).

firestore.rules (NOT deployed - maintainer must deploy): allow the owner to
change users/{uid}.username, set users/{uid}.email only to their verified
token email, delete a usernames doc they own, and update a usernames doc's
email to their own verified token email.

Co-authored-by: Cursor <cursoragent@cursor.com>
notAidven added a commit that referenced this pull request Jun 26, 2026
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