Skip to content

Enforce Single Compass User Per Email Across Auth Flows #1585

@tyler-dane

Description

@tyler-dane

Feature Description

Enforce Single Compass User Per Email Across Auth Flows

Summary

Prevent duplicate user records from being created for the same normalized email and make Google connect fail visibly when it cannot proceed. The implementation assumes the database is already clean: there is at most one Compass user record per email, and no legacy duplicate-merge path is needed.

Key Changes

  • Update the backend auth path so email is the canonical identity key for Compass users, centered around:
  • Change upsertUserFromAuth to resolve by normalized email before creating a new Compass user record.
    • If the current external user id already owns that email’s Compass user, update it.
    • If a Compass user already exists for that email under a different external user id, reuse that Compass user instead of creating another record.
    • Preserve existing Google metadata unless fresh Google credentials are explicitly being written.
  • Reuse the existing “swap session to the Compass user” pattern so auth flows land on the canonical Compass user id when SuperTokens returned a different recipe user for the same email.
    • Apply this to email/password signUpPOST
    • Apply this to email/password signInPOST
    • Keep the existing Google signInUpPOST remap behavior aligned with the same canonical-user rule
  • Adjust Google auth mode resolution so a Google login for an email that already has a Compass user is treated as linking/signing into that existing user, not a new signup.
  • Keep connectGoogleToCurrentUser simple under the clean-DB assumption:
    • If the current Compass user owns that email and the Google account is not linked elsewhere, connect and restart sync.
    • If the Google account is already connected to a different Compass user, return a conflict error.
    • Do not add any duplicate-user merge, transfer, or cleanup behavior.

Frontend / UX

  • Update packages/web/src/auth/hooks/google/useConnectGoogle/useConnectGoogle.ts so /auth/google/connect failures are surfaced directly in the connect flow.
  • Show a toast with the backend-provided error message when connect fails.
  • Keep the user on the current screen and preserve the current session on failure.
  • Leave unexpected OAuth popup/network failures on the existing fallback path, but do not allow the account-connect conflict to fail silently.

API / Type Changes

  • Add a typed auth/connect error payload shape with:
    • message
    • code
  • Add a frontend helper to read API error messages from Axios error responses.
  • Keep success request/response shapes unchanged for /signinup and /auth/google/connect.

Test Plan

  • Backend unit tests:
    • Email/password signup with an email that already has a Compass user reuses that user and does not create a second record.
    • Email/password signin with an existing same-email Compass user reuses that user.
    • Google sign-in/up with an email that already has an email/password Compass user links into that user instead of creating a second one.
    • connectGoogleToCurrentUser succeeds for a clean same-user connect and restarts sync.
    • connectGoogleToCurrentUser returns a conflict when the Google account is connected to another Compass user.
  • Frontend tests:
    • useConnectGoogle shows a toast with the server message when /auth/google/connect rejects.
    • Failed connect remains visible to the user instead of silently resetting state.
    • Successful connect still refreshes metadata and triggers fetch.
  • Scenario test:
    • Start from a clean DB with one Compass user per email, reproduce the intended Google-signup and later email/password auth flows, and verify only one Compass user record exists for that email throughout.

Assumptions

  • The database is clean: no existing duplicate Compass user records share the same normalized email.
  • Email uniqueness is based on trimmed, lowercased email.
  • “Only 1 user per email” is enforced at the application-flow level in this change; no legacy data migration or duplicate cleanup is included.
  • Error UX is toast-only.

Use Case

No response

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendWork related to improving the Compass API. More than 70% of the PR should be backend focused.

    Projects

    Status

    Ready

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions