Skip to content

Add Google OAuth single-user gate - #26

Draft
RenJeka wants to merge 7 commits into
mainfrom
claude/google-oauth2-integration-hbfn7d
Draft

RenJeka wants to merge 7 commits into
mainfrom
claude/google-oauth2-integration-hbfn7d

Conversation

@RenJeka

@RenJeka RenJeka commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Що це

Замок доступу до дашборду через Google-логін — пускає лише власника (allowlist email у .env). Це НЕ перехід у мультикористувацький режим: жодної таблиці users, жодного user_id у даних, single-user інваріант із CLAUDE.md лишається. OAuth тут — замок на вході + сесійна httpOnly-кукі.

План: docs/plans/google-oauth-gate.md.

Потік

  1. Гейт-екран із кнопкою Google (@react-oauth/google) → Google ID-token.
  2. POST /api/auth/google { credential } → сервер верифікує (google-auth-library, aud+email_verified), звіряє з ALLOWED_EMAILS.
  3. Дозволений → підписаний сесійний JWT у httpOnly-кукі olx_session. Інакше 403.
  4. Глобальний onRequest-замок на /api/* (крім /api/auth/*, /health) → 401 без валідної кукі.
  5. Фронт: GET /api/auth/me200 показує застосунок, 401 → гейт. Будь-який 401 від API повертає на гейт.

Зміни

Сервер (server/src/auth/): config.ts (env + кукі-флаги + fail-fast), plugin.ts (fastify-plugin, non-encapsulated; @fastify/cookie+@fastify/jwt; verifyGoogleIdToken; глобальний замок), routes.ts (/api/auth/google|me|logout). index.ts: import './env' першим, assertAuthConfigured(), CORS credentials:true, auth до доменних роутів.

Фронт (web/src/auth/): useAuth.ts (useSession/useLogin/useLogout, реакція на подію 401), AuthGate.tsx (екран входу). base.ts: credentials:'include' + VITE_API_BASE + подія 401. main.tsx: GoogleOAuthProvider. Header: кнопка «Вийти».

Схема БД не чіпається. Оновлено docs/architecture.md, docs/structure.md, .env.example (server + web).

Деплой (Render, cross-site)

Фронт і API на різних доменах → кукі Secure+SameSite=None, CORS з явним WEB_ORIGIN. Потрібен OAuth Client ID у Google Cloud Console (Authorized JavaScript origins = домен фронта + http://localhost:5173). Env: GOOGLE_CLIENT_ID, ALLOWED_EMAILS, SESSION_SECRET, AUTH_COOKIE_SECURE=true, VITE_GOOGLE_CLIENT_ID, VITE_API_BASE.

Перевірено

  • npm run build (server tsc + web tsc/vite) — зелений.
  • Сервер smoke (curl): AUTH_DISABLED=true/api/projects 200 (без регресу); auth on → /health 200, /api/projects 401, /api/auth/me 401, /api/auth/google без тіла 400, зі сміттєвим токеном 401; відсутні ключі → fail-fast при старті.

⚠️ Лишилось вручну: реальний логін своїм Google-акаунтом у браузері (потрібен справжній Client ID) — потоку з реальним ID-token я автоматично не перевіряв.

🤖 Generated with Claude Code


Generated by Claude Code

RenJeka and others added 7 commits June 23, 2026 16:03
…Turso)

Phase 0: swap the DB access mechanism only — business logic, schema, and the
OLX collection method are unchanged. libSQL is SQLite-compatible (single code
path: file: locally, Turso URL in prod), enabling Render + Turso deploy.

- db.ts: createClient + initDb (executeMultiple schema.sql) + thin async
  helpers dbGet/dbAll/dbRun; drop the historical migration scaffold
  (addColumnIfMissing/migrateListingsTable/backfill/PRAGMA/WAL) — schema.sql
  already holds every column.
- env.ts: load server/.env before db.ts reads TURSO_* (imported first in db.ts).
- Whole DB layer is async: every .get/.all/.run -> await dbGet/dbAll/dbRun;
  interactive db.transaction('write') for read->decide->write (upsert,
  statusEngine, analysis/relevance/aiPicks commit); db.batch('write') for pure
  write sets (cascade delete, sort swaps, filtered_out recompute, migratePostedAt).
- index.ts: host 0.0.0.0, CORS origin from WEB_ORIGIN, await initDb before listen.
- CLI entrypoints (scan.ts, migratePostedAt.ts) call initDb.
- .env.example: TURSO_DATABASE_URL/TURSO_AUTH_TOKEN/WEB_ORIGIN.
- docs: architecture.md/structure.md updated; render-turso-phase0.md marked done.

Verified locally against libSQL file:: build green; /health ok; live OLX scan
(GraphQL) -> upsert+dedup; coverage-window disable; manual PATCH override;
CLI scan; cascade-delete/recompute via batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AX5fZLCKjxyASaMAxMxxum
Beginner-friendly walkthrough: Turso DB setup, Render Web Service (backend) with
build/start commands and env vars, Render Static Site (frontend) with /api/*
rewrite (same-origin, no CORS), verification, free-tier cold-start notes, and
troubleshooting. No code changes — frontend keeps relative /api fetch; the
Render rewrite proxies it to the backend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AX5fZLCKjxyASaMAxMxxum
Gate access to the dashboard behind Google login (allowlist of one email)
without turning the app multi-user — no users table, no user_id, no data
partitioning. Auth is a lock on the front door plus a session cookie.

Server (server/src/auth/):
- config.ts: env (GOOGLE_CLIENT_ID/ALLOWED_EMAILS/SESSION_SECRET), cookie
  flags (prod cross-site Secure+SameSite=None, local http Lax), fail-fast
  assertAuthConfigured, AUTH_DISABLED dev bypass.
- plugin.ts: fastify-plugin (non-encapsulated) registering @fastify/cookie
  and @fastify/jwt, verifyGoogleIdToken via google-auth-library, global
  onRequest lock on /api/* (skips /health, /api/auth/*, CORS preflight).
- routes.ts: POST /api/auth/google, GET /api/auth/me, POST /api/auth/logout.
- index.ts: import ./env first, assertAuthConfigured, CORS credentials,
  register auth before domain routes.

Frontend (web/src/auth/):
- useAuth.ts: useSession/useLogin/useLogout, reacts to global 401 event.
- AuthGate.tsx: login screen with GoogleLogin, wraps the app.
- base.ts: credentials: 'include', VITE_API_BASE prefix, 401 -> gate event.
- main.tsx: GoogleOAuthProvider. Header: logout button.

DB schema untouched. Docs + .env examples updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wZmcZ6mmoQa5rv9gCa1KX
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3f209069-a5ae-4bbd-a805-f414a3d38e3e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/google-oauth2-integration-hbfn7d

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants