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:
- 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
Feature Description
Enforce Single Compass User Per Email Across Auth Flows
Summary
Prevent duplicate
userrecords 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 Compassuserrecord per email, and no legacy duplicate-merge path is needed.Key Changes
packages/backend/src/user/services/user.service.tspackages/backend/src/common/middleware/supertokens.middleware.handlers.tspackages/backend/src/auth/services/google/google.auth.service.tsupsertUserFromAuthto resolve by normalized email before creating a new Compass user record.signUpPOSTsignInPOSTsignInUpPOSTremap behavior aligned with the same canonical-user ruleconnectGoogleToCurrentUsersimple under the clean-DB assumption:Frontend / UX
packages/web/src/auth/hooks/google/useConnectGoogle/useConnectGoogle.tsso/auth/google/connectfailures are surfaced directly in the connect flow.API / Type Changes
messagecode/signinupand/auth/google/connect.Test Plan
connectGoogleToCurrentUsersucceeds for a clean same-user connect and restarts sync.connectGoogleToCurrentUserreturns a conflict when the Google account is connected to another Compass user.useConnectGoogleshows a toast with the server message when/auth/google/connectrejects.userrecord exists for that email throughout.Assumptions
userrecords share the same normalized email.Use Case
No response
Additional Context
No response