Skip to content

fix(auth): show "account already exists" on duplicate email signup#4855

Merged
waleedlatif1 merged 2 commits into
stagingfrom
waleedlatif1/duplicate-email-account-takeover
Jun 3, 2026
Merged

fix(auth): show "account already exists" on duplicate email signup#4855
waleedlatif1 merged 2 commits into
stagingfrom
waleedlatif1/duplicate-email-account-takeover

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • After the better-auth 1.3.12→1.6.11 upgrade, signing up with an already-registered email hit better-auth's generic anti-enumeration response (fake success, no DB write) when requireEmailVerification is on — so the signup form's existing USER_ALREADY_EXISTS handler never fired and the user was silently routed to /verify with no code (looked like "it took over the first account")
  • Added a pre-create existence check in the auth before hook: for /sign-up/email, if the email already exists, throw USER_ALREADY_EXISTS so the inline "An account with this email already exists. Please sign in instead." message shows
  • Consistent with how login ("No account found with this email") and social sign-in already reveal account existence; runs regardless of EMAIL_VERIFICATION_ENABLED so behavior is uniform across environments

Notes

  • No duplicate account or takeover was ever possible — the DB enforces unique on both email and normalized_email. This is a UX-consistency fix; the check mirrors better-auth's own email.toLowerCase() lookup
  • Rare Gmail dot/+ alias (only with SIGNUP_EMAIL_VALIDATION_ENABLED) and true concurrent-submit races still fall to the DB unique constraint (correct outcome, generic error) rather than the inline message

Type of Change

  • Bug fix

Testing

Tested manually: with email verification enabled, signing up with an existing email now shows "An account with this email already exists. Please sign in instead." instead of routing to the verify screen.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 3, 2026 1:46am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented Jun 3, 2026

PR Summary

Low Risk
Small auth middleware addition with no change to password handling or account creation rules; slightly increases email enumeration consistency with existing login messaging.

Overview
After the better-auth upgrade, duplicate email sign-up with email verification enabled could look like success and send users to /verify without a usable code, because the library’s anti-enumeration path never surfaced USER_ALREADY_EXISTS to the client.

This change adds a before hook on /sign-up/email that lowercases the submitted email, checks the user table, and throws APIError with code USER_ALREADY_EXISTS when a row already exists—so the signup form’s existing handler can show “An account with this email already exists. Please sign in instead.” The check runs regardless of EMAIL_VERIFICATION_ENABLED, aligning duplicate-email UX with login and social flows. Account safety still relies on DB unique constraints; this is a pre-create UX fix, not a new authorization model.

Reviewed by Cursor Bugbot for commit cc005e5. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 3, 2026

Greptile Summary

This PR fixes a UX regression introduced by the better-auth 1.3.12→1.6.11 upgrade: when requireEmailVerification is enabled, signing up with a duplicate email now triggers better-auth's anti-enumeration path (fake success, no DB write), silently routing the user to /verify instead of showing the existing "account already exists" error message.

  • Adds a pre-create DB existence check inside the before auth hook for the exact /sign-up/email path; if the email is already registered, throws USER_ALREADY_EXISTS so the signup form's inline error message displays correctly.
  • Uses email.toLowerCase() to mirror better-auth's lookup; edge cases (Gmail dot/+ aliases, true concurrent submissions) still correctly fall through to the DB unique constraint.

Confidence Score: 5/5

Safe to merge — the change is a narrow, well-gated pre-create check that only runs on an exact path match and only adds a SELECT before better-auth's normal flow.

The hook is correctly scoped with an exact path equality check, the DB query is minimal (SELECT id only, LIMIT 1), email normalization matches better-auth's own convention, and the error code wires directly into the existing client-side handler. The acknowledged edge cases (Gmail dot/+ aliases, concurrent submissions) still fail safely at the DB unique constraint.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/lib/auth/auth.ts Adds a targeted pre-create existence check in the before hook with exact path matching, lowercase normalization, and correct error code; no logic issues identified.

Sequence Diagram

sequenceDiagram
    participant Client as Signup Form
    participant Hook as before Hook
    participant DB as Database
    participant BA as better-auth

    Client->>Hook: "POST /sign-up/email {email, password}"
    Note over Hook: path === '/sign-up/email' && body.email?
    Hook->>DB: "SELECT id FROM user WHERE email = email.toLowerCase() LIMIT 1"
    alt Email already exists
        DB-->>Hook: [existingUser]
        Hook-->>Client: APIError UNPROCESSABLE_ENTITY / USER_ALREADY_EXISTS
        Note over Client: Shows "An account with this email already exists."
    else Email is new
        DB-->>Hook: []
        Hook->>BA: continue to better-auth sign-up logic
        BA->>DB: INSERT user (enforced by DB unique constraint)
        BA-->>Client: Success / redirect to /verify
    end
Loading

Reviews (2): Last reviewed commit: "fix(auth): use exact path match for dupl..." | Re-trigger Greptile

Comment thread apps/sim/lib/auth/auth.ts Outdated
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit cc005e5. Configure here.

@waleedlatif1 waleedlatif1 merged commit 1cb5a16 into staging Jun 3, 2026
14 checks passed
@waleedlatif1 waleedlatif1 deleted the waleedlatif1/duplicate-email-account-takeover branch June 3, 2026 01:54
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