You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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.
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 (
requireAuthinbackend/src/middleware/auth.tscallssupabase.auth.getUser(token)), and the frontend never touches Supabase tables at all — onlysupabase.auth.*inuseAuth.tsxplusgetSession()inapi/client.tsanduseChat.ts. TheprofilesRLS policy is therefore defence-in-depth only; quota is already enforced server-side bycheckQuota. The genuinely hard part is identity preservation:Project.userIdforeign-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.exampleDepends on: #3, #4
Checklist
prisma/schema.prismaand generate a migration against Neonauth.usersrows from task 5 into the new user table preserving eachidUUID verbatim, mappingemail,email_confirmed_at, and the bcryptencrypted_passwordinto the provider's expected columnsrequireAuthinbackend/src/middleware/auth.tswith the new session verification, keeping thereq.user = { id, email }contract byte-identical so no route handler changesenable_confirmations-equivalent behaviour ON (email verification required before sign-in) to match today'ssupabase/config.tomlGOOGLE_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 athttps://<project>.supabase.co/auth/v1/callbackand will 400 otherwiseenable_manual_linking = falsebehaviour)frontend/src/lib/supabase.tsinto a thin auth client and update the six call sites infrontend/src/hooks/useAuth.tsx(getSession,onAuthStateChange,signInWithPassword,signUp,signInWithOAuth,signOut) plusgetSession()inapi/client.tsanduseChat.tsuseAuth.tsx(identity_already_exists,access_denied,server_error) — map the new provider's error codes onto the same toastsprofiles/user_tierquota reads throughuserService.ts; RLS is gone, so confirm every read path goes through the backend and none was relying on the client's own row-level filterfrontend/src/pages/__tests__/SignInPage.test.tsxandSignUpPage.test.tsxmocks, plusbackend/src/services/__tests__/userService.test.tsauth.usersstaging table from task 5pnpm testgreen andpnpm typecheckcleanPriority: Critical · Phase 12 task 7 of the maintainer's migration plan.