docs: update CLAUDE.md with Google OAuth architecture and gotchas
Document non-obvious details from Steps 1-5 implementation: - adiuvAI: deep-link protocol workaround, requestSingleInstanceLock, backup-key.ts device-bound key, loginWithOAuth fetch() vs get() - api: _pending_states in-memory limitation, nullable password_hash, OAUTH_REDIRECT_URI pointing to API not website, 409 unverified-email guard, OAuth testing patterns with AsyncMock + monkeypatch Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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 |
|
||||
|
||||
Reference in New Issue
Block a user