feat(auth): replace invite temp passwords with emailed set-password links - #335
Open
prajjwalkumar17 wants to merge 1 commit into
Open
feat(auth): replace invite temp passwords with emailed set-password links#335prajjwalkumar17 wants to merge 1 commit into
prajjwalkumar17 wants to merge 1 commit into
Conversation
…inks Invites previously generated a temporary password and both returned it in the HTTP response (InviteMemberResponse.password) and emailed it in plaintext — flagged in the forgot-password security review as the invite flow's biggest weakness once a reset flow existed. - New users are now created with an unusable random bcrypt hash and email_verified=false; the invite email carries a single-use set-password link (reset-code machinery: SHA-256-keyed Redis entry, 7-day TTL) redeemed on the existing /reset-password page, which also marks the mailbox verified - Invite email is sent before any DB write: delivery failure fails the invite cleanly (500, nothing persisted); membership-insert failure compensates by deleting the user row and the code - Release deployments without an active email client now fail the invite instead of silently creating an unreachable account (debug builds keep the no_email dev flow, which logs the link) - user_pwreset_at TTL raised to cover the 7-day invite codes so a password change still kills every previously emailed link - no_email release logging keys on starts_with, closing a merchant-name-controlled subject collision that could route a live set-password link into release logs - login now checks the password before disclosing EmailNotVerified, removing a verification-status/existence oracle - InviteMemberResponse.password removed; MembersPage shows an "invitation sent" banner instead of credentials; InviteUserTemplate replaced by InviteSetPasswordTemplate; docs updated Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
invite_memberpreviously generated a temporary password for new users and both returned it in the HTTP response body (InviteMemberResponse.password) and emailed it in plaintext. This was flagged during the forgot-password security review as a pre-existing weakness to fix once a reset flow existed — which #333 provides. This PR removes passwords from the invite flow entirely: invitees now receive a single-use set-password link.How the new flow works
email_verified = false— the account cannot be logged into until the invitee sets a password.POST /auth/reset-passwordredemption (which also marks the mailbox verified), but with a 7-day TTL since invitees may not open the email same-day.500and persists nothing (admin just retries). A membership-insert failure compensates by deleting both the user row and the code. A lost invite email is recoverable via the normal forgot-password flow.InviteMemberResponse.passwordis gone; the Members page shows an "invitation sent" banner instead of a credentials card.InviteUserTemplate(credentials email) is replaced byInviteSetPasswordTemplate(CTA link + 7-day/single-use footer). Existing-user invites (membership add + notification email) are unchanged.Hardenings from the review pass
This PR was reviewed by a multi-agent adversarial pass (3 reviewer dimensions, every finding independently verified); all confirmed findings are fixed here:
user_pwreset_at's TTL now covers the invite-code lifetime, so a password change/reset still invalidates every previously emailed link — without this, a stale invite link could regain takeover power after the revocation key expired.no_email_client) now fail the invite instead of "succeeding" while delivering no link (debug builds keep the dev flow, where the stub logs the URL).no_emailstub's release-logged branch now matchesstarts_with("confirm your email"), closing a path where an admin-chosen merchant name could steer a live set-password link into release logs.loginnow verifies the password before disclosingEmailNotVerified, so probing an invited-but-unredeemed account returns the same generic401as a nonexistent one.Testing
cargo checkclean on bothmysqlandpostgresfeature paths;tscclean.200→ invitee logs in with their chosen password,email_verified = true, membership present → link replay400→ re-invite409 AlreadyMember→ wrong-password probes against an unverified invited account and a nonexistent account return byte-identical responses.Docs
docs/api-refs/auth-and-onboarding.mdx— invite section rewritten: new response shape, link semantics, and the active-email-client requirement for release deployments.🤖 Generated with Claude Code