CSRF: upgrade session cookie sameSite from 'lax' to 'strict'.
Rate limiting: login endpoint now blocks an IP after 10 failed attempts
in a 15-minute window; resets on success. In-memory, no new dependency.
SESSION_SECRET: server exits at startup when NODE_ENV=production and
SESSION_SECRET is unset. docker-compose.yml updated to pass it via env;
.env.example added with generation instructions.
Security headers: add X-Content-Type-Options, X-Frame-Options, and
Referrer-Policy to all responses.
Sensitive data: routing_number and account_number are now omitted from
GET /api/account/:id responses for non-admin users.
Image size: logo upload capped at 512 KB in the account PUT handler.
Amount validation: checks (POST/PUT) and deposit items (POST/PUT) now
reject non-finite and non-positive amounts.
QBO import: uploaded file is rejected if its MIME type is not text or
a known CSV variant.
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