Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions docs/features/google-sync-and-websocket-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,29 @@ The backend determines auth mode based on server-side state, and the client only

Note: Frontend reconnect intent is no longer used for routing. The server is the source of truth for auth mode selection.

### Connect Google Later (After Password-Only Usage)

When a user authenticates with email/password first and connects Google later
from an existing session:

1. the web client sends Google OAuth with
`shouldTryLinkingWithSessionUser: true` (explicit in-session linking intent)
2. `AccountLinking` allows linking because a session exists
3. backend `googleSignup()` attaches Google credentials to the existing Compass user
4. `syncCompassEventsToGoogle(userId)` backfills eligible Compass-only base events
that do not have `gEventId` yet
5. for recurring events, the same backfill pass updates instance provider data so
recurrence instances map to the new Google base event id
6. background import/watch sync is then restarted

Operational constraints:

- backfill only includes non-someday base events without existing `gEventId`
- each backfilled event is written to Google first, then Compass is updated with
returned provider ids
- restart still follows the same metadata/socket lifecycle (`IMPORT_GCAL_START`,
metadata transitions, `IMPORT_GCAL_END`)

Primary files:

- `packages/backend/src/common/middleware/supertokens.middleware.ts`
Expand Down
62 changes: 19 additions & 43 deletions docs/features/password-auth-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ This flow adds first-party auth on top of the existing Google OAuth setup:
- sign up with email and password
- sign in with email and password
- forgot/reset password
- email verification
- account linking so Google and password auth for the same verified email resolve to the same Compass user
- account linking for explicit in-session Google connect from an authenticated password session
- SuperTokens user-to-Compass-user mapping via Mongo `ObjectId` external ids

Primary files:
Expand All @@ -35,7 +34,7 @@ SuperTokens is configured so that:
- backend user upserts always write against the session user id (`response.session.getUserId()`)
- Google sign-in/up uses `google.googleId` lookup for existing Compass users before creating a new id

Important constraint: automatic account linking is currently disabled, so matching emails across Google and password auth are **not** auto-merged by middleware configuration.
Important constraint: account linking is only enabled when an active session exists. Logged-out sign-in/up paths do not automatically merge same-email Google and password identities.

## Web Entry Points

Expand All @@ -50,7 +49,6 @@ Supported URL entry points:
- `?auth=signup`
- `?auth=forgot`
- `?auth=reset&token=...`
- `?auth=verify&token=...`

The temporary feature gate currently comes from `useAuthFeatureFlag()`:

Expand Down Expand Up @@ -91,6 +89,9 @@ Files:
- `EmailVerification`
- `Session`

`EmailVerification` is currently initialized on the web client, but there is no
first-class `?auth=verify` modal flow in the auth UI.

At runtime it:

- checks whether a session already exists
Expand Down Expand Up @@ -124,7 +125,6 @@ This keeps Google sign-in and password sign-in aligned after the backend session
- `signUp`
- `forgotPassword`
- `resetPassword`
- `verifyEmail`

`useAuthFormHandlers.ts` owns the actual submits.

Expand All @@ -138,12 +138,6 @@ This keeps Google sign-in and password sign-in aligned after the backend session

On success it calls `completeAuthentication()` with the resolved email and closes the modal.

After the session is created, the web client immediately calls
`EmailVerification.sendVerificationEmail()`.

That keeps signup usable without blocking the first session, while still making
same-email Google linking safe once the email is verified.

### Log in

`handleLogin()` calls `EmailPassword.signIn()` with:
Expand Down Expand Up @@ -173,48 +167,30 @@ Important detail:

That prevents the reset flow from breaking if the URL changes while the modal is open.

On success, the modal switches to `loginAfterReset`, which renders the login form and a success status message:
On success, the modal switches to `loginAfterReset`, which renders the login
form and a success status message:

- `"Password reset successful. Log in with your new password."`

### Verify email

Verification starts from a link shaped like:

- `/day?auth=verify&token=...`

It uses the same token-preservation pattern as reset password:

- `useAuthUrlParam()` opens the modal from the query param
- `AuthModal` captures the first verification token it sees
- `handleVerifyEmail()` restores that token into the URL before calling
`EmailVerification.verifyEmail()`

On success, the modal returns to the login view with a success state.
On invalid or expired tokens, the modal shows retry guidance instead of leaving
the user stuck on a blank or broken state.

## Backend Runtime Flow

`initSupertokens()` configures these recipes:

- `AccountLinking`
- `ThirdParty`
- `EmailPassword`
- `EmailVerification`
- `Dashboard`
- `Session`
- `UserMetadata`

### Account linking

`AccountLinking.init()` is configured to:
`AccountLinking.init()` only enables automatic linking when an active session is already present:

- decline automatic linking when the new account has no email
- automatically link verified same-email accounts when there is no active session
- allow linking without email verification when the user is already authenticated in-session
- with an active session, linking is allowed (`shouldAutomaticallyLink: true`) and does not require extra verification (`shouldRequireVerification: false`)
- without an active session, automatic linking is disabled (`shouldAutomaticallyLink: false`)

This means middleware-level automatic linking is disabled for all sign-in/up paths.
In practice, this supports the explicit in-session "Connect Google Calendar" flow, while logged-out auth flows do not auto-merge identities by email.

### Google sign-in/up

Expand Down Expand Up @@ -272,20 +248,19 @@ For repeated auth on the same session user id, this writes to the existing Compa

Current behavior in `supertokens.middleware.ts`:

- all environments rewrite the incoming SuperTokens reset link into Compass app URL shape
- all environments rewrite the incoming SuperTokens verification link into Compass app URL shape
- `test` environment logs the rewritten link and skips provider delivery
- all environments rewrite incoming SuperTokens password-reset links into Compass app URL shape
- `test` environment logs the rewritten reset link and skips provider delivery
- non-test environments pass the rewritten link to SuperTokens' original email sender (`originalImplementation.sendEmail`)
- if the incoming link has no `token` query param, backend keeps the original link unchanged

The rewritten link shape comes from `buildResetPasswordLink()` and
`buildEmailVerificationLink()`.
The rewritten reset link shape comes from `buildResetPasswordLink()`.

The host/origin portion is taken from backend env (`FRONTEND_URL`), and the
route is always `/day`.

- Reset: `http://localhost:9080/day?auth=reset&token=...`
- Verify: `http://localhost:9080/day?auth=verify&token=...`
- Reset: `http://[REDACTED]/day?auth=reset&token=...`

Email verification links are not currently rewritten into `?auth=verify` by backend middleware.

## Event And Sync Behavior After Password Auth

Expand Down Expand Up @@ -313,4 +288,5 @@ events stranded outside Google Calendar.

- The rollout gate is not limited to `lastKnownEmail`; any `?auth=` URL currently enables the auth UI.
- Reset password links always target the `/day` route and require a valid `FRONTEND_URL` in backend env.
- With automatic account linking disabled, same-email Google and email/password identities are not merged automatically.
- Automatic account linking is only enabled when an active session exists. Logged-out same-email Google/password identities are not auto-merged.
- Dated-route redirects preserve existing query params (including `auth=verify`), but `useAuthUrlParam()` only handles `login`, `signup`, `forgot`, and `reset`.
Loading