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
12 changes: 12 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ _Avoid_: non-Google user
**Google-connected user**:
An authenticated user with usable Google credentials stored by the backend.

**Google authorization**:
The Google approval step that lets Compass sign a user in with Google or connect
Google Calendar to an existing Compass session.
_Avoid_: Google login mode

**Google authorization intent**:
The user's Compass purpose for a Google authorization: Google sign-in/up or
Google Calendar connect/reconnect.
_Avoid_: auth mode

**Google revoked**:
The state where Google access is no longer usable and Google-origin data should
be pruned, ignored, or reconnected.
Expand Down Expand Up @@ -164,6 +174,8 @@ during Import or Public watch notification handling.
**Events** without becoming a **Google-connected user**.
- A **Google-connected user** can import from Google and mirror eligible Compass
event changes to Google.
- A **Google authorization** must preserve its **Google authorization intent**
instead of inferring the user's goal from the later session state.
- **Public watch notifications** are separate from browser API and **SSE**
traffic; browser traffic can be local, but Google webhook posts need public
HTTPS when continuous sync is expected.
Expand Down
10 changes: 4 additions & 6 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ Internal documentation for engineers and agents working in the Compass repo.

Start with [AGENTS.md](../AGENTS.md) for repo rules and command defaults. Use this index for codebase shape, subsystem behavior, and acceptance runbooks.

## How docs get published

Markdown files in this `docs/` directory are automatically mirrored to [docs.compasscalendar.com](https://docs.compasscalendar.com). A GitHub Action detects any push to `main` that touches `docs/**` and syncs the changes to the doc site. Just edit any of the markdown in `docs/` and the doc site will update itself upon merge.

## Start Here

- [Repo Architecture](./architecture/repo-architecture.md)
Expand Down Expand Up @@ -56,9 +60,3 @@ User-visible behavior runbooks for manual verification and expected outcomes:
- [Recurring Events](./acceptance/recurring-events.md)
- [Shortcuts](./acceptance/shortcuts.md)
- [Tasks](./acceptance/tasks.md)

## How docs get published

Markdown files in this `docs/` directory are automatically mirrored to [docs.compasscalendar.com](https://docs.compasscalendar.com). A GitHub Action detects any push to `main` that touches `docs/**` and syncs the changes to the doc site. Just edit any of the markdown in `docs/` and the doc site will update itself upon merge.


12 changes: 6 additions & 6 deletions docs/acceptance/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,21 +132,21 @@ The forgot-password flow should avoid leaking whether an email exists. The reset

### UX

The auth modal should still allow Google sign in from a logged-out state. A successful Google flow should authenticate the user. Closing the popup should behave like cancellation, not like a hard auth failure.
The auth modal should still allow Google sign in from a logged-out state. A successful Google redirect flow should authenticate the user and return to Compass.

### Steps

1. Open the auth modal from `/day?auth=login`.
2. Select `Continue with Google`.
3. Complete Google OAuth successfully.
3. Complete the Google authorization redirect with the intended Google account.
4. Log out.
5. Start Google sign in again, but close the popup before finishing.
5. Start Google sign in again, but cancel at Google before finishing.

### Expected Results

- A successful Google sign-in authenticates the user and returns them to the app.
- Closing the popup clears the loading state.
- Popup cancellation does not leave the app stuck in an auth error state.
- The Google authorization redirect returns to Compass through `/auth/google/callback`.
- A successful Google sign-in authenticates the user and returns them to the saved app path.
- A canceled Google redirect returns to Compass and shows a recoverable auth error.

## Scenario 6: Password-Only Compass Usage Before Google Connect

Expand Down
10 changes: 5 additions & 5 deletions docs/acceptance/google-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ A password-authenticated user can connect Google Calendar from inside the app us
1. Sign up or log in with email/password. Do not connect Google.
2. Create at least one Compass event so there is pre-existing data.
3. Open the command palette (Cmd+K) and select Connect Google Calendar, or click the Google status icon in the sidebar.
4. Complete the Google OAuth popup with the intended Google account.
4. Complete the Google authorization redirect with the intended Google account.
5. Return to Compass and observe the sidebar status icon.
6. Reload the page.

### Expected Results

- The OAuth popup opens and closes cleanly without redirecting away from the app shell.
- The Google authorization redirect returns to Compass through `/auth/google/callback`.
- The sidebar status transitions away from NOT_CONNECTED into an importing state.
- Pre-existing Compass events remain visible on the calendar.
- The network flow uses `POST /api/auth/google/connect`, not the logged-out sign-in path.
Expand All @@ -72,7 +72,7 @@ After connecting Google, Compass imports all events from the user's Google calen
### Steps

1. Connect Google Calendar (see Scenario 1), or start with an account that has `importGCal` flagged for restart.
2. Observe the header immediately after the OAuth popup closes.
2. Observe the header immediately after the Google authorization redirect returns.
3. Continue using the app normally while the import runs (navigate to different dates, create a Compass event).
4. Wait for the header spinner to disappear.
5. Check the calendar for newly imported Google events.
Expand Down Expand Up @@ -207,12 +207,12 @@ After revocation, the user can reconnect Google using the same flow as the initi

1. Complete Scenario 7 so the connection is in the NOT_CONNECTED state.
2. Open the command palette and select Connect Google Calendar.
3. Complete the Google OAuth popup.
3. Complete the Google authorization redirect.
4. Wait for the import to complete.

### Expected Results

- The OAuth popup opens and closes without error.
- The Google authorization redirect returns to Compass without error.
- The import spinner appears in the header.
- Google events repopulate the calendar after import completes.
- The sidebar status returns to HEALTHY.
Expand Down
6 changes: 3 additions & 3 deletions docs/development/feature-file-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Use this document to find the first files to inspect for common Compass changes.

- Session initialization and SuperTokens wiring: `packages/web/src/auth/session/SessionProvider.tsx`
- User profile bootstrap: `packages/web/src/auth/context/UserProvider.tsx`
- Google OAuth app flow: `packages/web/src/auth/hooks/google/useGoogleAuth/useGoogleAuth.ts`, `packages/web/src/auth/hooks/google/useGoogleAuthWithOverlay/useGoogleAuthWithOverlay.ts`
- Google OAuth provider wrapper: `packages/web/src/auth/hooks/google/useGoogleLogin/useGoogleLogin.ts`
- Popup-cancel classification for Google OAuth: `packages/web/src/auth/google/google-oauth-error.util.ts`
- Google authorization app flow: `packages/web/src/auth/google/authorization`
- Google redirect callback: `packages/web/src/views/GoogleAuthCallback/GoogleAuthCallback.tsx`
- Google authorization intent storage: `packages/web/src/auth/google/authorization/google-authorization.storage.ts`
- Auth schemas: `packages/web/src/auth/schemas/auth.schemas.ts`
- Backend auth routes: `packages/backend/src/auth/auth.routes.config.ts`
- Backend auth controllers/services: `packages/backend/src/auth/controllers`, `packages/backend/src/auth/services`
Expand Down
2 changes: 1 addition & 1 deletion docs/development/troubleshoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ If that pre-connect local sync fails, connect is intentionally aborted and the u

What this means operationally:

- this is a local-to-cloud event migration failure, not an OAuth popup failure
- this is a local-to-cloud event migration failure, not a Google redirect failure
- backend `connectGoogleToCurrentUser` is not called for that attempt
- no Google import restart should be observed from that click

Expand Down
54 changes: 48 additions & 6 deletions docs/features/password-auth-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,33 @@ Design intent:
- logged-in Google attach is an authenticated Compass backend flow
- logout is decoupled from Google state and succeeds even when no Google account
is linked
- Google authorization uses a full-page redirect. Both Google sign-in/up and
Google Calendar connect/reconnect return to `/auth/google/callback`.
- Before leaving for Google, the web app stores a short-lived Google
authorization intent and same-origin return path in `sessionStorage`, keyed by
the OAuth `state` value. The callback validates that state, finishes the saved
intent, removes it, and then returns the user to the page that started the
flow. If the saved return path is missing or unsafe, the callback falls back
to `/day`.
- The backend accepts only the configured Compass Google callback URL as the
OAuth redirect URI when exchanging a Google code. The callback URL is derived
from backend `FRONTEND_URL` plus `/auth/google/callback`.
- The callback page is intentionally transitional: it shows a simple completion
status, finishes or fails the saved Google authorization intent, and navigates
back into the app.
- Google authorization no longer uses a blocking overlay. The callback page is
the only Google authorization loading surface.
- When a user first signs up or signs in, Compass should sync local events the
user created themselves, but should not sync seeded demo events such as
"Morning standup" or "Try Compass" into the new account.
- Seeded demo events should be marked only in browser IndexedDB. The marker is
used to skip demo events during local-event sync and must not be sent to the
backend or stored as account event data.
- Editing a seeded demo event does not make it a user-created event for sync
purposes; it should still be skipped.
- Logged-out Google sign-in keeps the shared post-auth completion behavior for
now: after the session is created, Compass syncs local events to the account
and warns if those events remain device-local.

## Web Entry Points

Expand Down Expand Up @@ -262,19 +289,31 @@ does not return a new one.

When a logged-in password user chooses `Connect Google Calendar`:

1. the web client completes the Google popup flow
2. `useConnectGoogle()` sends the auth-code payload to
1. the web client syncs pending local events to the server
2. if local-event sync succeeds, the web client redirects through Google and
returns to
`/auth/google/callback`
3. the callback sends the auth-code payload to
`POST /api/auth/google/connect`
3. `connectGoogleToCurrentUser()` exchanges the code for Google tokens
4. backend verifies the Google account is not already owned by a different
4. `connectGoogleToCurrentUser()` exchanges the code for Google tokens
5. backend verifies the Google account is not already owned by a different
Compass user
5. backend persists Google credentials onto the current Compass user
6. backend marks metadata sync flags as `"RESTART"` and restarts sync in the
6. backend persists Google credentials onto the current Compass user
7. backend marks metadata sync flags as `"RESTART"` and restarts sync in the
background

This path does not call SuperTokens `signInUpPOST` and does not depend on
SuperTokens account linking.

Redirect implementation should include focused tests for:

- matching OAuth `state` before completing the callback
- routing Google sign-in/up and Google Calendar connect/reconnect to the correct
backend endpoint
- rejecting unsafe return paths and falling back to `/day`
- using the configured `/auth/google/callback` URL when exchanging Google codes
- syncing user-created local events while skipping demo-marked local events

### Google connect conflict contract

If a logged-in user attempts to connect a Google account that is already linked
Expand Down Expand Up @@ -403,3 +442,6 @@ session-linking failure mode.
- A Google account can belong to only one Compass user. In-session connect
returns a conflict if the Google account is already attached elsewhere.
- Dated-route redirects preserve existing query params (including `auth=verify`), but `useAuthUrlParam()` only handles `login`, `signup`, `forgot`, and `reset`.
- Future UX question: first-time Google sign-in may need a choice before syncing
anonymous local events into the account, especially when those events are demo
or placeholder data.
24 changes: 4 additions & 20 deletions docs/frontend/frontend-runtime-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,13 @@ Once a user has ever authenticated, the app records that fact in local auth-stat

When a user re-authenticates with Google, auth-state utilities also clear any in-memory "Google revoked" flag so normal remote sync can resume.

## Google OAuth Popup Cancellation Semantics
## Google Authorization Redirect

Files:

- `packages/web/src/auth/hooks/google/useGoogleAuth/useGoogleAuth.ts`
- `packages/web/src/auth/hooks/google/useGoogleLogin/useGoogleLogin.ts`
- `packages/web/src/auth/google/google-oauth-error.util.ts`

The web auth flow intentionally treats popup-close outcomes as cancellation, not authentication failure.

Cancellation detection (`isGooglePopupClosedError`) returns true when any of these match:

- `type === "popup_closed"`
- `error`, `error_description`, or `message` equals `"popup_closed"` (case-insensitive)
- `error`, `error_description`, or `message` contains `"popup window closed"` (case-insensitive)

When cancellation is detected in the auth hooks:
Google sign-in/up and Google Calendar connect/reconnect leave Compass through a full-page Google redirect and return through `/auth/google/callback`.

- auth state is reset (`resetAuth`)
- OAuth overlay closes because `selectIsAuthenticating` becomes false
- generic auth failure state is not dispatched for that event
Before redirecting, the web app stores a short-lived authorization intent in `sessionStorage` keyed by OAuth `state`. The callback validates that state, finishes the saved intent, removes it, and returns the user to the original same-origin path or `/day`.

For non-cancellation errors, normal auth-failure handling still applies.
The old blocking overlay is not used for Google authorization.

## User Bootstrap

Expand Down
13 changes: 8 additions & 5 deletions docs/self-hosting/google-calendar.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ Authorized JavaScript origins:
http://localhost:9080

Authorized redirect URIs:
http://localhost:9080
http://localhost:9080/auth/google/callback
```

Compass sends the browser origin as the OAuth redirect URI. That means the redirect URI is the app origin itself, not a longer callback path.
Compass sends the dedicated Google callback page as the OAuth redirect URI.
That means the redirect URI includes `/auth/google/callback`.

This path doesn't make your local backend public. It's for sign-in and one-time import only.

Expand Down Expand Up @@ -74,17 +75,19 @@ Local setups do not create a public HTTPS URL, so they can't receive these. You

For a public server install, create a Google OAuth client with **Web application** as the client type.

Use your public Compass origin for both OAuth fields:
Use your public Compass origin for JavaScript origins and the Compass callback
page for redirect URIs:

```text
Authorized JavaScript origins:
https://cal.example.com

Authorized redirect URIs:
https://cal.example.com
https://cal.example.com/auth/google/callback
```

Replace `https://cal.example.com` with your own Compass URL. Do not add `/api`, `/auth/callback`, or another path to the redirect URI.
Replace `https://cal.example.com` with your own Compass URL. Do not add `/api`
to the redirect URI.

Also check these in Google Cloud:

Expand Down
Loading