Skip to content

Replace Supabase Auth with a self-hosted auth layer (email/password + Google SSO) #6

Description

@AlaskanTuna

Supabase Auth cannot be retained in isolation — Auth lives inside the project that pauses, so keeping it means keeping a project that will idle-pause again in seven days. The good news from inspection: the coupling is thin and well-localised. The backend has exactly one seam (requireAuth in backend/src/middleware/auth.ts calls supabase.auth.getUser(token)), and the frontend never touches Supabase tables at all — only supabase.auth.* in useAuth.tsx plus getSession() in api/client.ts and useChat.ts. The profiles RLS policy is therefore defence-in-depth only; quota is already enforced server-side by checkQuota. The genuinely hard part is identity preservation: Project.userId foreign-keys Supabase UUIDs, and 2FA-free email+password users carry bcrypt hashes that should be re-homed so existing testers are not locked out.

Scope: backend/src/middleware/auth.ts, backend/src/config/supabase.ts, backend/src/services/userService.ts, backend/src/routes/, backend/src/config/env.ts, prisma/schema.prisma, frontend/src/lib/supabase.ts, frontend/src/hooks/useAuth.tsx, frontend/src/api/client.ts, frontend/src/hooks/useChat.ts, frontend/src/components/auth/GoogleSignInButton.tsx, frontend/src/pages/SignInPage.tsx, frontend/src/pages/SignUpPage.tsx, .env.example

Depends on: #3, #4

Checklist

  • Add the auth provider's Prisma models to prisma/schema.prisma and generate a migration against Neon
  • Import the staged auth.users rows from task 5 into the new user table preserving each id UUID verbatim, mapping email, email_confirmed_at, and the bcrypt encrypted_password into the provider's expected columns
  • Confirm the bcrypt hash format is accepted as-is; if it is not, plan a forced password-reset email for existing accounts rather than silently breaking sign-in, and say so in the issue
  • Replace the body of requireAuth in backend/src/middleware/auth.ts with the new session verification, keeping the req.user = { id, email } contract byte-identical so no route handler changes
  • Mount the auth handler routes on the Express app; keep enable_confirmations-equivalent behaviour ON (email verification required before sign-in) to match today's supabase/config.toml
  • Wire Google OAuth using the existing GOOGLE_OAUTH_CLIENT_ID / GOOGLE_OAUTH_SECRET — and add the new callback URL to the authorized redirect URIs in Google Cloud Console; the current entry points at https://<project>.supabase.co/auth/v1/callback and will 400 otherwise
  • Preserve OAuth identity linking so a user who signed up by email and later uses Google lands on the same account (today's enable_manual_linking = false behaviour)
  • Rewrite frontend/src/lib/supabase.ts into a thin auth client and update the six call sites in frontend/src/hooks/useAuth.tsx (getSession, onAuthStateChange, signInWithPassword, signUp, signInWithOAuth, signOut) plus getSession() in api/client.ts and useChat.ts
  • Preserve the OAuth error-toast handling added on 05/05/26 in useAuth.tsx (identity_already_exists, access_denied, server_error) — map the new provider's error codes onto the same toasts
  • Preserve the sign-out cache clear that fixed the cross-user project-leak regression (Phase 11 §5 Test 3 exists specifically to guard it)
  • Preserve profiles / user_tier quota reads through userService.ts; RLS is gone, so confirm every read path goes through the backend and none was relying on the client's own row-level filter
  • Update frontend/src/pages/__tests__/SignInPage.test.tsx and SignUpPage.test.tsx mocks, plus backend/src/services/__tests__/userService.test.ts
  • Drop the temporary auth.users staging table from task 5
  • verify: pnpm test green and pnpm typecheck clean
  • verify: a rescued existing user signs in with their original password and sees their original projects — this is the identity-preservation proof
  • verify: a brand-new signup receives a verification email, cannot sign in before verifying, and can after
  • verify: Google SSO completes end-to-end and lands on the dashboard
  • verify: signing in as user A, signing out, then signing in as user B shows zero of A's projects
  • verify: quota enforcement still returns 429 with the full summary JSON when the daily tier limit is hit

Priority: Critical · Phase 12 task 7 of the maintainer's migration plan.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0-criticalApp is down / blocks everything elseauthAuth, SSO, sessions, email verificationmigrationMoving off a provider

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions