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" sectionCHECKLIST.md— section C (email + code form)CLAUDE.md— references toCODE_PEPPER, SMTP,codestable,/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_namecolumn toinvites - drops the
codestable
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 liston the VPS shows the existingquka93@gmail.comwith no name in square brackets (NULL is fine — legacy row).node bin/invite.js add someone@example.com AliceshowsInvited 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_limitstable — still used by the login endpoint. - Didn't change
src/middleware.js—rateLimit()andrequireAuth()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/inviteHTTP endpoint — invites still happen only via the CLI, as before.