- Prepare the user_accounts role lookup once in the auth middleware; it
runs on nearly every authenticated request
- Prepare session store get/set/destroy/purge statements once in the
constructor instead of per request
- Prepare the per-check SELECT once per PDF job instead of once per check
- Fix session store expiry: cookie.maxAge is already in milliseconds, so
stored sessions outlived the cookie by 1000x
- Regenerate the session ID on login, first-run setup, and OIDC login to
prevent session fixation
- Mark session cookies Secure on TLS connections (secure: 'auto') and add
TRUST_PROXY support for reverse-proxy deployments
- Build password reset links from APP_BASE_URL instead of the Host header
to prevent reset-link poisoning
- Rate-limit forgot-password requests (5 per IP per 15 minutes)
- Strip OIDC debug logging that leaked authorization codes, subject IDs,
and emails to logs
Three-tier user model: admin (all accounts, all actions), editor
(assigned accounts, read/write), viewer (assigned accounts, read-only).
Backend:
- express-session with custom SQLite session store (no extra packages)
- bcryptjs for password hashing
- src/middleware/auth.js: requireAuth, requireAdmin, requireEditor,
canAccessAccount helpers
- src/routes/auth.js: login, logout, /me, setup-needed, change-password
- src/routes/users.js: full CRUD + account assignments (admin only)
- All API routes protected; /api/accounts filtered by user access;
write routes gated by requireEditor; admin-only routes locked down
Frontend:
- Login overlay (full-page) with first-run admin-setup flow
- Role-based UI: admin-only elements hidden for non-admins; edit/delete
and PDF buttons hidden for viewers; account switcher shows only
accessible accounts for non-admins
- Users modal (admin only): user list with role badges, create/edit/delete
users, set account access via checkboxes
- Change-password section available to all logged-in users
- apiFetch redirects to login on 401