# Exbyte Depot — Waitlist server + admin Collects waitlist emails from the landing page (deduplicated) and gives you a password-protected admin panel to browse them and export to CSV. ## Stack - **express** — HTTP + static landing - **better-sqlite3** — storage; `UNIQUE COLLATE NOCASE` on email → no duplicates, ever - **express-rate-limit** — brute-force protection on the login endpoint - **node:crypto** — scrypt password hashing + HMAC-signed session cookies (no extra deps) ## Setup ```bash cd waitlist-server npm install cp .env.example .env npm run gen-secret # → paste into SESSION_SECRET in .env npm run set-password # type a password → paste the ADMIN_PASS_HASH line into .env # set ADMIN_USER in .env (default: admin) npm start ``` - Landing: http://localhost:8787/ - Admin: http://localhost:8787/admin ## How it works - The landing form POSTs `{ email }` to **`POST /api/waitlist`**. - Invalid emails are rejected; a repeat email is silently deduped (no dup row). - A hidden honeypot field (`website`) traps bots. - **`/admin`** requires login. Session is a signed, HttpOnly, SameSite=Strict cookie (Secure in production), valid 8h. Wrong logins are rate-limited (8 / 15 min / IP). - **Export CSV**: `GET /admin/export.csv` streams all signups (UTF-8 + BOM for Excel). ## Security notes - No plaintext password anywhere — only a scrypt hash in `.env` (git-ignored). - Refuses to start without `ADMIN_USER`, `ADMIN_PASS_HASH`, and a strong `SESSION_SECRET`. - Security headers set (CSP, X-Frame-Options: DENY, nosniff, no-referrer). - Behind a reverse proxy, set `TRUST_PROXY=1` so rate-limiting and stored IPs are correct, and terminate TLS at the proxy (cookies become `Secure` when `NODE_ENV=production`). ## Deploy (Linux VPS, sketch) 1. `NODE_ENV=production`, real `.env`, `npm ci --omit=dev`. 2. Run under systemd (or pm2); put nginx/Caddy in front for TLS + `TRUST_PROXY=1`. 3. Point your domain at it; the landing is served from `../landing` (or set `LANDING_DIR`). ## Data SQLite file at `data/waitlist.db` (git-ignored). Back it up to keep your list.