diff --git a/README.md b/README.md index dfb3937..8e0ebdc 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,9 @@ A modern trading card game collection manager built with Next.js and Neon Databa ```env POSTGRES_URL="postgresql://your-username:your-password@your-host/your-database" JWT_SECRET="" + # Required for `npm run setup-db` — used once to hash the initial admin password. + # Set in .env.local for local dev, or as a CI secret if you run setup from CI. + ADMIN_INITIAL_PASSWORD="" # Optional — exercise the rate limiter locally. Without them, `lib/rate-limit.js` # warn-and-no-ops in dev. In production these are auto-provisioned by the # Vercel Upstash Marketplace integration. @@ -51,6 +54,7 @@ A modern trading card game collection manager built with Next.js and Neon Databa KV_REST_API_TOKEN="" ``` `JWT_SECRET` is **required** — `lib/auth-secret.js` throws at import time if it's unset. + `ADMIN_INITIAL_PASSWORD` is **required** for `npm run setup-db` — the script exits with code 1 if it's unset. 4. **Set up the database** ```bash @@ -112,11 +116,29 @@ tcg-vault/ └── .env.local # Environment variables ``` -## 🔐 Default Admin Account +## 🔐 First-time admin setup -After running the database setup: -- **Email**: admin@tcgvault.com -- **Password**: admin123 +`npm run setup-db` creates a single admin user the first time it runs. The +password is read from the `ADMIN_INITIAL_PASSWORD` environment variable; the +script exits with code 1 (and does not open a database connection) if the +variable is unset or empty. + +- **Local dev:** set `ADMIN_INITIAL_PASSWORD` in `.env.local` before running + `npm run setup-db`. Use `openssl rand -base64 24` (or any other strong + source) to generate the value. +- **CI / Vercel:** set `ADMIN_INITIAL_PASSWORD` as a project secret if setup + ever runs from CI. The env var is **only** read by the seed script; runtime + auth uses the per-user password stored in the database. +- **Admin email:** the seed creates `admin@tcgvault.com`. Change the password + immediately after first login via the app's profile settings. + +> **Operators of envs that pre-date this change:** `npm run setup-db` is +> idempotent (`ON CONFLICT (email) DO NOTHING`) — re-running it with +> `ADMIN_INITIAL_PASSWORD` set will **not** rotate an existing admin row's +> password. If your environment was set up before this change and still has +> the prior weak default, rotate the password manually via the app +> after logging in, or wait for the queued `rotate-default-admin` follow-up +> convoy. ## 🤝 Contributing diff --git a/scripts/setup-neon-db.js b/scripts/setup-neon-db.js index f619b96..417780f 100644 --- a/scripts/setup-neon-db.js +++ b/scripts/setup-neon-db.js @@ -13,6 +13,18 @@ require('dotenv').config({ path: '.env.local' }); const { neon } = require('@neondatabase/serverless'); async function setupNeonDatabase() { + const adminPassword = process.env.ADMIN_INITIAL_PASSWORD; + if (!adminPassword || !adminPassword.trim()) { + console.error( + '❌ ADMIN_INITIAL_PASSWORD environment variable is not set.\n' + + '\n' + + ' Set it in .env.local for local dev, or as a CI secret if you run setup from CI.\n' + + ' Generate a strong password with: openssl rand -base64 24\n' + + ' See README.md → "First-time admin setup" for the full flow.\n' + ); + process.exit(1); + } + const sql = neon(process.env.POSTGRES_URL); try { @@ -128,7 +140,7 @@ async function setupNeonDatabase() { // Create admin user const bcrypt = require('bcryptjs'); - const hashedPassword = await bcrypt.hash('admin123', 12); + const hashedPassword = await bcrypt.hash(adminPassword, 12); await sql` INSERT INTO users (email, password, role) @@ -141,8 +153,7 @@ async function setupNeonDatabase() { console.log(''); console.log('📋 Database Details:'); console.log(' Database: Neon PostgreSQL'); - console.log(' Admin User: admin@tcgvault.com'); - console.log(' Admin Password: admin123'); + console.log(' Admin user ready (email: admin@tcgvault.com)'); console.log(''); console.log('🔧 Next Steps:'); console.log(' 1. Test the API endpoints');