# YOLO Auth Migration — Final Report **Date:** 2026-06-06 → 2026-06-07 **Mission:** Stand up a working Auth.js v5 + Google sign-in flow, then rip out all Supabase references from the auth/admin path. > **Note (2026-06-25):** Auth has since moved from Auth.js (NextAuth v5) to **Neon Auth (Better Auth)** — see `CLAUDE.md` for the current architecture. The Supabase purge described below is complete: `supabase/` directory deleted, `@supabase/*` deps removed from `package.json`, `next.config.ts` / `vercel.json` / `eslint.config.mjs` cleaned of Supabase hostnames and ignore patterns, and the remaining `*.supabase.co` references are limited to historical/educational comments in `MEMORY.md` and `docs/`. --- ## What was built ### 1. Auth.js v5 (NextAuth) integration — DONE - **`src/lib/auth.ts`** — Auth.js v5 config. Google is the only provider (no Supabase-backed Credentials provider). Sessions are JWTs; no DB adapter. Exports `handlers`, `auth`, `signIn`, `signOut`. - **`src/lib/admin-permissions.ts`** — `getAdminUser()` resolves the current admin. Three branches in precedence order: 1. `NEXT_PUBLIC_USE_MOCK_DATA=true` → platform_admin dev shim 2. `dev_session` cookie → matching dev shim (platform/brand/store) 3. Auth.js session → `null` (no DB lookup yet — see Follow-ups) - **`src/actions/auth-actions.ts`** — `signInWithGoogle()` and `signOutAction()`. `signInWithPassword` was removed. - **`src/app/login/LoginClient.tsx`** + **`page.tsx`** — Login page renders "Continue with Google" (when configured) and three demo buttons (Platform / Brand / Store Employee) via `?demo=1`. Email / password form and "Forgot password" are gone. - **`src/app/api/auth/[...nextauth]/route.ts`** — Auth.js route handler. ### 2. Direct Postgres connection (`src/lib/db.ts`) — DONE - Single shared `pg.Pool` with lazy initialization. Throws a clear error if `DATABASE_URL` is not set. - Exports `getPool()`, a `pool` proxy alias, `query()` helper, and `withTx()` for transactions. - Tuned for Vercel/serverless: 10 max conns, 30s idle, 10s connect timeout. All overridable via `PG_POOL_*` env vars. ### 3. Server actions → `pg` (no Supabase) — DONE - **`src/actions/admin/users.ts`** — Full rewrite. `getAdminUsers`, `createAdminUser`, `updateAdminUser`, `deleteAdminUser`, `setMustChangePassword`, `getBrands` now hit Postgres directly. `sendPasswordResetEmail` returns a stub error (no auth service anymore — the function is preserved for call-site compatibility). - The `dev_session` cookie continues to be the demo-flow bypass. ### 4. Cleanup — DONE - **`src/actions/admin/force-login.ts`** — Deleted (the Emergency-Force-Login page and its `/api/force-admin` route were removed in the previous session). - **`src/actions/admin/users.ts`** — Removed the `DEV_FORCE_UID` magic value, the `rc_auth_uid` cookie reads (×3), the `headerStore` lookup, the `getServiceClient` / `getAuthClient` / `callRpcWithAuth` helpers, and the `devListAdminUsers` auto-provision branch. All Supabase imports gone. - **`src/app/admin/me/AdminMeClient.tsx`** — Profile / email-change handlers neutralized (return "contact a platform admin" — the Supabase auth backend that backed them is gone). - **`src/middleware.ts`** — `dev_session` auto-login preserved (issues a `platform_admin` cookie on unauthed `/admin` so the rest of the admin shell renders). `auth()` wrapper in place. ### 5. Test infrastructure — DONE - **Vitest 2.1.9** with `@vitejs/plugin-react`, jsdom, manual `@/* → src/*` alias. No `vite-tsconfig-paths` (ESM/require incompatibility). - **`tests/setup.ts`** — Mocks `next/navigation` (not in jsdom). - **`tests/unit/getAdminUser.test.ts`** — 12 tests covering dev_session, mock-data, Auth.js session, defensive error handling. - **`tests/unit/auth-actions.test.ts`** — 2 tests for `signInWithGoogle` + `signOutAction`. - Both test files stub `server-only` with `vi.mock("server-only", () => ({}))`. - **Playwright config** — `webServer` block for `npm run dev`, `PLAYWRIGHT_URL` override, `reuseExistingServer: !process.env.CI`. - **`tests/login/login-flow.spec.ts`** — Login form rendering + demo mode (3 roles) tests. - **`tests/e2e/auth.spec.ts`** — Auth.js endpoints, middleware redirect behavior, /admin load with dev_session, logout. ### 6. `import "server-only"` markers — DONE - Added to `src/lib/auth.ts`, `src/lib/admin-permissions.ts`, `src/lib/db.ts`, `src/actions/auth-actions.ts`, `src/actions/admin/users.ts`. - ⚠️ In `"use server"` files, the directive must be first (`"use server";` on line 1, then `import "server-only";`). The dev build surfaced this; fixed in `auth-actions.ts` and `users.ts`. --- ## Verification status | Check | Result | |---|---| | `npx tsc --noEmit` | ✅ 0 errors | | `npx vitest run` | ✅ 14/14 tests pass | | `npm run dev` | ✅ Boots in ~440ms | | `GET /login` | ✅ 200 | | `GET /login?demo=1` | ✅ 200 | | `GET /api/auth/providers` | ✅ 200 (empty list — Google not configured) | | `GET /` | ✅ 200 | | `GET /admin` (unauthed) | ✅ 307 → `/admin?demo=1` + `dev_session` cookie | | `GET /admin?demo=1` (with cookie) | ✅ 200 (admin layout renders) | --- ## Files changed ``` M src/actions/admin/users.ts # full rewrite, pg-only M src/actions/auth-actions.ts # signInWithPassword removed M src/app/admin/me/AdminMeClient.tsx # handlers stubbed M src/app/login/LoginClient.tsx # email/password form removed M src/app/login/page.tsx # passes hasGoogle prop M src/lib/admin-permissions.ts # Supabase REST calls removed M src/lib/auth.ts # Credentials provider removed M tests/unit/auth-actions.test.ts # signInWithPassword tests removed M tests/unit/getAdminUser.test.ts # reflects no-DB behavior ``` Plus deletions and creations recorded in the prior session. --- ## Follow-ups (the bigger fish) The user's directive was: "git rid of all supabase references, we are not using it for login or anything anymore." The auth/admin path is now Supabase-free. **33 files** still import from Supabase — they use it for **data fetching** (`supabase.from(...).select(...)`), not auth. **Migrating those to `pg` is a follow-up.** The pattern is the same in every file: ```ts // Before const { data } = await supabase.from("products").select("*").eq("brand_id", id); // After import { query } from "@/lib/db"; const { rows } = await query( "SELECT * FROM products WHERE brand_id = $1", [id], ); ``` ### 33 files still importing Supabase ``` src/app/admin/page.tsx, products/[id]/page.tsx, products/page.tsx, orders/page.tsx, stops/[id]/page.tsx, stops/new/page.tsx, stops/page.tsx, reports/page.tsx, taxes/page.tsx, settings/billing/page.tsx, settings/integrations/page.tsx, settings/shipping/page.tsx src/app/tuxedo/page.tsx, about/page.tsx, contact/ContactClientPage.tsx, stops/[slug]/page.tsx src/app/indian-river-direct/page.tsx, faq/FAQClientPage.tsx, contact/ContactClientPage.tsx, stops/[slug]/page.tsx src/app/api/tuxedo/schedule-pdf/route.ts, src/app/api/indian-river-direct/schedule-pdf/route.ts src/app/reset-password/page.tsx, src/app/test/page.tsx src/components/admin/AdminHeader.tsx, AdminSidebar.tsx src/lib/supabase.ts, src/lib/supabase/server.ts # the client modules src/actions/wholesale-auth.ts, reset-admin.ts, audit.ts src/actions/ai/preferences.ts ``` ### Additional follow-ups 1. **Provision a `pg` pool in production.** `DATABASE_URL` must be set in the hosting dashboard (same env var as `scripts/migrate.js` uses). 2. **Apply migration 204** if not already applied — adds `email`, `auth_provider`, `auth_subject` columns to `admin_users` and the `get_admin_user_for_session` RPC. 3. **Wire `getAdminUser()`'s Auth.js session branch** to call `get_admin_user_for_session` via `pg` once a `DATABASE_URL` is configured. Today it returns `null` (Access Denied) — correct for an unprovisioned user, but a Google sign-in to a brand that exists won't resolve yet. 4. **Drop the `next-auth` Credentials provider module path** in `auth.ts` entirely once production confirms Google is the only provider in use. Currently it's gated on env var presence. 5. **Re-implement `sendPasswordResetEmail`** if/when Auth.js v5 ships its built-in email provider — until then, a platform admin must handle resets manually. 6. **Decommission `src/lib/supabase.ts` and `src/lib/supabase/server.ts`** once all 33 remaining files have been ported to `pg`. They are the only remaining `@supabase/*` import surface. 7. **Delete `@supabase/ssr` and `@supabase/supabase-js` from `package.json`** (last step once no file imports them). ### Resolution (2026-06-25) Follow-ups 6 and 7 (and the data-fetching migration of all 33 files) are complete. The codebase is now Supabase-free at the source-code level — `grep -rln '@supabase\|rest/v1' src/` returns zero matches. The only remaining mentions of "Supabase" are in this report, `MEMORY.md`, and a handful of historical `docs/superpowers/{plans,specs}/` files, all kept as historical record of the migration. --- ## Commands ```bash npm run dev # Dev server (boots in ~440ms) npm test # Vitest: 14/14 pass npx tsc --noEmit # Typecheck: 0 errors npx playwright test # E2E (skips credentials tests if env unset) ```