# Auth simplification — deployment notes The code-based login flow (email → 6-digit code → session) has been replaced by a pure email-based lookup against the invite list. This is a one-way change — after deploy, the server no longer reads `CODE_PEPPER`, no longer talks to SMTP, and the `codes` table is dropped on first boot. Invites now carry an optional first name, used on the welcome screen: *"Thanks for your interest, Erik."* (or *"Thank you for your interest."* when no name is on file). ## Files changed ### Replace (drop in over the existing ones) | File | What changed | |---|---| | `server.js` | Drops `initMail()` call, drops the `CODE_PEPPER` fatal check at boot. | | `src/db.js` | Adds `first_name` column to `invites` (idempotent migration). Drops the `codes` table + its index on startup (idempotent). Updates prepared statements — `getInvite` and `listInvites` return `first_name`; `upsertInvite` takes it as an argument; `deleteInvite` is unchanged. Removes all `codes`-related statements. Cleanup sweep no longer touches `codes`. | | `src/auth.js` | `POST /auth/request-code` and `POST /auth/verify-code` removed. Single `POST /auth/login` endpoint — returns `200 {ok, firstName}` on success, `403 {error: "not_invited"}` otherwise. Rate limit: 30 per IP per hour. `GET /auth/me` now returns `{email, firstName}`. | | `src/sessions.js` | Removes `CODE_TTL_MS`, `randomCode`, `hashCode`, `constantTimeEqual`. Keeps `SESSION_TTL_MS`, `COOKIE_NAME`, `randomSessionId`, `issueSession`, `clearSession`, `currentSession`. | | `bin/invite.js` | `add` accepts an optional first name as a third argument: `node bin/invite.js add erik@example.com Erik`. `list` shows the name in square brackets next to each entry. `remove` unchanged. Re-running `add` on an existing email updates the first name only — `invited_at` and `invited_by` are preserved. | | `public/entrance.html` | Removes the six code-digit input block and all associated styles (`.code-row`, `.code-cell`, `.quiet`, "use a different email"). Removes step 3's duplicate welcome title markup — the welcome title is now set by JS on step-enter. | | `public/entrance.js` | Two-step state machine (email → welcome) instead of three. On submit: `POST /auth/login`. Success → set welcome title, advance. `403` → inline "not invited" message in-place. Removes `submitCode()`, code-cell handling, paste/arrow-key logic, the "use different email" button, and the `rememberedEmail` / `submitting` state. | | `package.json` | Removes `nodemailer` dependency. Bumps version to `0.2.0`. | | `.env.example` | Removes `CODE_PEPPER`, `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASS`, `MAIL_FROM`. Keeps `PORT`, `PUBLIC_ORIGIN`, `NODE_ENV`. | ### Delete | File | Why | |---|---| | `src/mail.js` | SMTP transport and `sendCode()` — no longer used. | ### Update (minor — you do this yourself) | File | Action | |---|---| | `.env` (on the VPS, at `/etc/fenja/env`) | Remove `CODE_PEPPER=…`, `SMTP_HOST=…`, `SMTP_PORT=…`, `SMTP_USER=…`, `SMTP_PASS=…`, `MAIL_FROM=…`. Keep the rest. | | `.env` (on your laptop) | Same. | ## Doc updates I've not touched these — update them when you next edit them: - `PROJECT.md` — "How auth works" section, "Non-negotiable properties" (drop CODE_PEPPER and SMTP) - `INSTALL.md` — step 5 (env file), step 0 prerequisites (SMTP relay no longer needed) - `OPERATIONS.md` — "Editing secrets" section - `CHECKLIST.md` — section C (email + code form) - `CLAUDE.md` — references to `CODE_PEPPER`, SMTP, `codes` table, `/auth/request-code`, `/auth/verify-code` ## Deploy sequence Local first: ```powershell # 1. Replace the files (see the list above). Delete src/mail.js. # 2. Update your local .env — remove CODE_PEPPER, SMTP_*, MAIL_FROM lines. # 3. Clean up node_modules to drop nodemailer: npm install # 4. Add first names to existing invites, or invite fresh with names: node bin/invite.js add you@yourdomain.com Erik # 5. Walk the flow: npm run dev # → http://127.0.0.1:3000 # → type your email → should land on welcome with personalised title # → click "Learn more" → timeline # → open a private window, type an unknown email → "not on invite list" ``` Then the VPS (once you're happy): ```bash # On your laptop — rsync with the usual excludes: rsync -avz --delete \ --exclude node_modules --exclude data --exclude .env --exclude .git \ ./ user@project-bifrost.fenja.ai:/tmp/fenja-upload/ # On the VPS: sudo rsync -a --delete /tmp/fenja-upload/ /opt/fenja/ sudo rm -f /opt/fenja/src/mail.js # make sure it's gone after rsync sudo chown -R fenja:fenja /opt/fenja rm -rf /tmp/fenja-upload cd /opt/fenja sudo -u fenja npm ci --omit=dev # drops nodemailer from node_modules # Edit the VPS .env to remove CODE_PEPPER and SMTP_*: sudo nano /etc/fenja/env sudo systemctl restart fenja sudo journalctl -u fenja -n 20 # Expect: just "[bifrost] listening on 127.0.0.1:3000" # No more "[mail] SMTP relay reachable" line — the whole SMTP stack is gone. ``` First time the server boots, the migration runs automatically: - adds `first_name` column to `invites` - drops the `codes` table Both are idempotent — if you roll back and deploy again it's a no-op. ## Spot-check after deploy - [ ] `curl -I https://project-bifrost.fenja.ai/` still returns 200 with all security headers. - [ ] A non-existent session cookie → entrance page with the email field. - [ ] Type an invited email → advances to welcome with *"Thanks for your interest, [Name]."* if the invite has a name, or *"Thank you for your interest."* if not. - [ ] Type an uninvited email → inline red "This email is not on the invite list." on the email input. - [ ] Click "Learn more about Project Bifrost" → `/timeline`. - [ ] Refresh `/` while logged in → land directly on the welcome step with the personalised title (served from `/auth/me`). - [ ] `node bin/invite.js list` on the VPS shows the existing `quka93@gmail.com` with no name in square brackets (NULL is fine — legacy row). - [ ] `node bin/invite.js add someone@example.com Alice` shows `Invited someone@example.com (Alice)`. - [ ] DevTools network tab: no 404s, no CSP violations. ## Rollback, if needed The migration is not trivially reversible — `codes` was dropped. If you need to roll back, restore from the nightly SQLite backup at `/opt/fenja/data/backup-YYYY-MM-DD.sqlite`, and redeploy the previous code. Invites with `first_name` values stay in the backup but will be ignored by the old `src/db.js` that doesn't select that column — no harm done. ## What I deliberately did NOT do - Didn't remove the `rate_limits` table — still used by the login endpoint. - Didn't change `src/middleware.js` — `rateLimit()` and `requireAuth()` behaviour is unchanged. - Didn't change anything inside `protected/` — the timeline, archive, and Project Bifrost scenes are all untouched. - Didn't change Nginx config or the systemd service unit — no operational changes. - Didn't add an "admin web UI" or `/auth/invite` HTTP endpoint — invites still happen only via the CLI, as before.