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
34 changes: 34 additions & 0 deletions docs/backend/api-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,23 @@ Authenticated Compass-defined Google attach/reconnect endpoint:
}
```

`POST /api/auth/google/connect` request example:

```json
{
"thirdPartyId": "google",
"clientType": "web",
"redirectURIInfo": {
"redirectURIOnProviderDashboard": "https://example.com/day",
"redirectURIQueryParams": {
"code": "oauth-authorization-code",
"scope": "openid email profile",
"state": "opaque-state"
}
}
}
```

Behavior:

- intended only for users who already have an active Compass session
Expand All @@ -121,6 +138,23 @@ Behavior:
Compass user
- marks Google sync metadata for restart and starts background sync

Conflict contract (Google account already owned by another Compass user):

- status code: `409 CONFLICT`
- payload shape (BaseError client payload):

```json
{
"result": "User not connected",
"message": "Google account is already connected to another Compass user"
}
```

Operational notes:

- conflict exits before credential persistence, so no sync restart is triggered
- clients should treat this as an ownership conflict and keep the current Compass session

### SuperTokens-managed auth endpoints (runtime)

Files:
Expand Down
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 @@ -262,6 +262,29 @@ Primary files:
- `packages/web/src/auth/hooks/oauth/useConnectGoogle.ts`
- `packages/web/src/common/repositories/event/event.repository.util.ts`

### Connect-Later Ownership Conflict Triage

If `POST /api/auth/google/connect` returns `409` while a user is trying to
connect Google from an existing password session:

1. confirm whether the Google account is already linked by checking for an
existing Compass user with the same `google.googleId`
2. verify backend conflict payload:
- `result: "User not connected"`
- `message: "Google account is already connected to another Compass user"`
3. verify no reconnect side effects were applied for the current session user:
- no new Google credential write
- no metadata transition to `sync.importGCal = "RESTART"`
- no reconnect/import websocket lifecycle (`IMPORT_GCAL_START` /
`IMPORT_GCAL_END`) for that failed request

Expected operator action:

- treat as ownership protection, not as an OAuth transport failure
- have the user authenticate into the Compass account that already owns that
Google identity (or disconnect/recover ownership through an explicit support
path)

## User Metadata Shape Used By Socket And UI

`UserMetadata` includes Google connection state alongside sync state:
Expand Down
29 changes: 29 additions & 0 deletions docs/features/password-auth-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,35 @@ When a logged-in password user chooses `Connect Google Calendar`:
This path does not call SuperTokens `signInUpPOST` and does not depend on
SuperTokens account linking.

### Google connect conflict contract

If a logged-in user attempts to connect a Google account that is already linked
to a different Compass user, backend connect intentionally fails with a conflict
instead of reassigning ownership.

Source path:

- `googleAuthService.connectGoogleToCurrentUser(...)`

Response contract:

- status: `409 CONFLICT`
- payload shape:

```json
{
"result": "User not connected",
"message": "Google account is already connected to another Compass user"
}
```

Operational implications:

- no Google credentials are persisted for the current session user on conflict
- metadata sync flags are not set to `"RESTART"` for that failed request
- clients should keep the current Compass session and prompt users to sign in
with the account that already owns the Google connection

### Email/password sign-up and sign-in

The `EmailPassword` recipe is overridden in two places.
Expand Down
35 changes: 32 additions & 3 deletions docs/manual-testing/auth-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,35 @@ land in the same Compass account rather than creating a duplicate account.
- Existing Compass data remains visible.
- No duplicate or empty account is created.

## Scenario 9: Session-Expired Re-Auth
## Scenario 9: Connect Conflict (Google Account Already Linked Elsewhere)

### UX

If a logged-in user tries to connect a Google account that already belongs to a
different Compass user, the connect action should fail safely without replacing
or mutating the current account session.

### Steps

1. Prepare two distinct Compass users (User A and User B).
2. Connect Google account G to User A and confirm success.
3. Log out User A.
4. Log in as User B (email/password session is easiest for setup).
5. Trigger `Connect Google Calendar`.
6. Complete OAuth with the same Google account G.
7. Observe network and UI behavior after OAuth returns.

### Expected Results

- `POST /api/auth/google/connect` returns `409`.
- Response payload includes:
- `result: "User not connected"`
- `message: "Google account is already connected to another Compass user"`
- User B remains signed in as User B (session is not replaced).
- User B's existing Compass data remains visible.
- Google connection status for User B does not transition to connected/importing.

## Scenario 10: Session-Expired Re-Auth

### UX

Expand All @@ -238,7 +266,7 @@ When a previously authenticated session becomes invalid, the app should guide th
- Clicking `Sign in` opens the login modal.
- Re-authenticating restores normal app usage.

## Scenario 10: Logout And Persisted Gate State
## Scenario 11: Logout And Persisted Gate State

### UX

Expand Down Expand Up @@ -270,7 +298,8 @@ If time is limited, run these checks before shipping auth changes:
6. `Connect Google Calendar` works from an authenticated password session without losing existing Compass data.
7. After connect-later, logged-out Google sign-in lands in the same Compass account.
8. Session expiry opens the login modal from the toast.
9. Logging out preserves the rollout gate for that browser session.
9. Connect conflict returns `409` and does not change active Compass session.
10. Logging out preserves the rollout gate for that browser session.

## Current Caveats

Expand Down
Loading