customer-presentation/AUTH_SIMPLIFICATION_NOTES.md
2026-04-23 10:38:37 +02:00

7 KiB

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:

# 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):

# 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.jsrateLimit() 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.