diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index e55dfeb..8dc7768 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -76,6 +76,9 @@ Main Process (Node.js) - `src/main/db/schema.ts` — 6 tables (clients, projects, tasks, checkpoints, notes, taskComments) - `src/renderer/routes/` — File-based routing (TanStack Router auto-generates `routeTree.gen.ts`) - `src/renderer/components/ui/` — shadcn/ui primitives (new-york theme, neutral colors) +- `src/main/auth/auth-manager.ts` — Login, register, logout, OAuth flow (singleton) +- `src/main/auth/backup-key.ts` — Device-specific AES-256 backup key (safeStorage, not password-derived) +- `src/main/ai/token.ts` — Two-tier token storage: safeStorage + electron-store fallback **Non-obvious details**: - `electron-trpc` is NOT used — custom IPC bridge in `ipc.ts` + `ipcLink.ts` because electron-trpc bundles tRPC v10 internals @@ -85,6 +88,13 @@ Main Process (Node.js) - Timestamps are milliseconds (JavaScript `Date.getTime()`), not ISO strings - Notes auto-embed to LanceDB on create/update (fire-and-forget, errors swallowed) +**Google OAuth (adiuvAI side)**: +- `adiuvai://` is NOT accepted by Google as a redirect URI — Google only accepts `http://localhost` or `https://`. The API backend exposes `GET /auth/oauth/google/web-callback` which receives the Google redirect and immediately bounces to `adiuvai://oauth/callback?...`. The redirect URI registered in Google Cloud Console points to the backend, not the Electron app. +- `app.requestSingleInstanceLock()` is required for the `second-instance` event to fire on Windows/Linux. If it returns `false`, call `app.quit()` immediately (another instance is already running). +- In dev (`process.defaultApp === true`), `setAsDefaultProtocolClient('adiuvai')` must include `[path.resolve(process.argv[1])]` as the third argument so the OS protocol registration includes the entry script. +- `loginWithOAuth` uses `fetch()` directly (not `this.get()`) because the authorize endpoint is public — `get()` throws when not authenticated. +- The backup key in `backup-key.ts` is stored in `encryptedTokens` under the key `backup_key`, reusing `getToken/setToken` from `token.ts`. It is device-bound and never password-derived, so social-login users can use backup features without issue. + --- ## api (FastAPI Backend) @@ -172,6 +182,15 @@ FastAPI app (app/main.py) - **CORS includes `app://`**: Electron uses custom `app://` protocol, not http/https - **Vector search on encrypted data is not semantic**: Backend derives deterministic 32-dim floats from blob SHA-256 for storage/search — a known trade-off +**Google OAuth (api side)**: +- OAuth routes live in `app/api/routes/auth.py`: `GET /auth/oauth/{provider}/authorize`, `POST /auth/oauth/{provider}/callback`, `GET /auth/oauth/{provider}/web-callback` (bounces to deep link, excluded from OpenAPI schema). +- Provider abstraction in `app/auth/oauth_providers.py` — `GoogleOAuthProvider` uses `httpx` directly (no `authlib`). PKCE S256 is implemented manually via `generate_pkce_pair()`. +- `_pending_states` dict in `routes/auth.py` is **in-memory** — works for single-process dev but does not survive restarts and does not scale to multiple workers. Replace with Redis in production. +- `users.password_hash` is **nullable** — social-only users have `password_hash=None`. `await db.flush()` is required before creating a linked `OAuthAccount` to populate `new_user.id` before commit. +- `OAUTH_REDIRECT_URI` must point to the **API backend** (e.g. `https://api.adiuvai.com/...`), not the website domain. `adiuvai.com` is a static site with no server-side routing. +- **Unverified email + existing account = 409**: if `email_verified=False` and the email is already registered, the callback returns 409. Without this guard, branch 3 would attempt to INSERT a duplicate email and crash with a DB constraint violation (500). +- **Testing OAuth routes**: mock `GoogleOAuthProvider.exchange_code` and `get_userinfo` with `patch.object(..., new=AsyncMock(...))` — works because FastAPI instantiates a new provider per request. Use `monkeypatch.setattr(settings, "GOOGLE_AUTH_CLIENT_ID", ...)` to simulate configured credentials without restarting the app. + ### Tier System | Feature | Free | Pro | Power | Team |