# BetterAuth Migration Checklist This document covers the manual steps required to complete the Clerk → BetterAuth migration in both development and production. **What's automated** (runs via `scripts/migrate.mjs` on every deploy, idempotent): - Schema migrations (sessions, accounts, verifications tables; nullable clerk_id; email_verified column) - Convert `teams.owner_id` and `commissioners.user_id` from Clerk IDs → `users.id` UUIDs - Mark all Clerk users as `email_verified = true` - Create `accounts` records for Google and Discord OAuth users (via Clerk API) **What's manual** (one-time steps you do by hand): - Export user CSV from Clerk (for password hash migration) - Run the password migration script - Update OAuth redirect URIs in Google/Discord consoles - Set env vars in production --- ## Development ### Step 1 — Add environment variables Add these to your `.env` file: ``` BETTER_AUTH_SECRET="" BETTER_AUTH_URL="http://localhost:5173" GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" DISCORD_CLIENT_ID="" DISCORD_CLIENT_SECRET="" # Keep temporarily for the automated OAuth migration (remove after confirmed working) CLERK_SECRET_KEY="" ``` Remove once confirmed working: `VITE_CLERK_PUBLISHABLE_KEY`, `CLERK_WEBHOOK_SECRET`, `DEV_ADMIN_CLERK_ID` ### Step 2 — Apply the database schema migration ```bash npm run db:migrate ``` Adds the `sessions`, `accounts`, and `verifications` tables; makes `clerk_id` nullable; adds `email_verified`. ### Step 3 — Add OAuth redirect URIs for localhost In **Google Cloud Console** (APIs & Services → Credentials → your OAuth client): - Add `http://localhost:5173/api/auth/callback/google` to Authorized redirect URIs In **Discord Developer Portal** (your app → OAuth2): - Add `http://localhost:5173/api/auth/callback/discord` to Redirects ### Step 4 — Run the automated data migration ```bash node scripts/migrate.mjs ``` This converts owner IDs, creates Google/Discord OAuth accounts, and marks users as email-verified. It reads from the Clerk API using your `CLERK_SECRET_KEY`. ### Step 5 — Migrate email/password users This requires exporting password hashes from Clerk (they're only in the CSV export, not the API). 1. Go to **Clerk dashboard → Users → Export** and download the CSV 2. Save it as `exported_users.csv` in the project root (it's gitignored) 3. Run: ```bash node scripts/migrate-clerk-passwords.mjs ``` 4. **Delete `exported_users.csv` immediately after** — it contains bcrypt password hashes ### Step 6 — Set yourself as admin `DEV_ADMIN_CLERK_ID` no longer works. Set admin access directly in the database: ```sql UPDATE users SET is_admin = true WHERE email = 'your@email.com'; ``` ### Step 7 — Verify sign-in works - **Google / Discord**: sign in immediately — OAuth accounts were migrated automatically - **Email/password**: sign in with the same password as before — bcrypt hashes were migrated - Check that league ownership and commissioner access still works After verifying, remove `CLERK_SECRET_KEY` from `.env`. --- ## Production > Do Steps 1–3 before merging to main. The data migration (Step 4) and OAuth account creation run automatically on deploy. ### Step 1 — Add production environment variables Add to your production environment (server docker-compose / secrets manager): ``` BETTER_AUTH_SECRET="" BETTER_AUTH_URL="https://brackt.com" GOOGLE_CLIENT_ID="" GOOGLE_CLIENT_SECRET="" DISCORD_CLIENT_ID="" DISCORD_CLIENT_SECRET="" # Keep temporarily — needed for the automated OAuth migration on first deploy CLERK_SECRET_KEY="" ``` ### Step 2 — Add OAuth redirect URIs for production In **Google Cloud Console**: - Add `https://brackt.com/api/auth/callback/google` to Authorized redirect URIs In **Discord Developer Portal**: - Add `https://brackt.com/api/auth/callback/discord` to Redirects ### Step 3 — Migrate email/password users (before deploying) The password migration runs against the production database directly — it does NOT go through Docker. 1. Go to **Clerk dashboard → Users → Export** and download the CSV for production users 2. Save as `exported_users.csv` in the project root (gitignored) 3. Run against the production database: ```bash DATABASE_DIRECT_URL="" CLERK_SECRET_KEY="" node scripts/migrate-clerk-passwords.mjs ``` 4. **Delete `exported_users.csv` immediately** ### Step 4 — Deploy Merge to `main`. The GitHub Actions deploy will: 1. Build and push the Docker image 2. SSH to the server and run `docker compose up --no-deps migrate` 3. `scripts/migrate.mjs` runs — applies schema migrations, then (because `CLERK_SECRET_KEY` is set): - Converts `teams.owner_id` and `commissioners.user_id` to `users.id` UUIDs - Marks users as `email_verified` - Creates `accounts` records for Google and Discord OAuth users 4. The app container starts with the new BetterAuth code ### Step 5 — Verify production sign-in - Google and Discord users: sign in immediately - Email/password users: sign in with the same password as before - Spot-check that league ownership and commissioner access are intact ### Step 6 — Remove Clerk credentials Once everything is confirmed, remove from the production environment: ``` CLERK_SECRET_KEY VITE_CLERK_PUBLISHABLE_KEY CLERK_WEBHOOK_SECRET DEV_ADMIN_CLERK_ID ``` Delete the Clerk webhook endpoint from the Clerk dashboard (it no longer exists in the codebase). On the next deploy, `scripts/migrate.mjs` will log `"CLERK_SECRET_KEY not set — skipping Clerk account migration"` and skip gracefully. --- ## Notes ### Why email/password migration is a separate manual step Clerk's REST API does not expose password hashes — they're only available in the CSV dashboard export. The automated deploy script (`scripts/migrate.mjs`) handles OAuth accounts via the API, but password hashes must be exported manually and migrated with `scripts/migrate-clerk-passwords.mjs`. The bcrypt hashes from Clerk are directly compatible with BetterAuth — no re-hashing is needed. Users sign in with the same password as before. ### What if a user signed up with both Google and email/password? Both an OAuth account and a credential account will be created for them. BetterAuth handles this correctly — the user can sign in with either method. ### Future cleanup After the migration is stable (a week or two after production deploy), open a follow-up PR to: 1. Remove the `clerk_id` column from `database/schema.ts` and run `npm run db:generate` 2. Remove `CLERK_SECRET_KEY` handling from `scripts/migrate.mjs` 3. Delete `scripts/migrate-clerk-passwords.mjs`