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.
- Fix account settings modal overflow: add max-height to .modal, make
.modal-body flex/scrollable, widen #acct-settings-modal to 620px
- Add role column to user_accounts (editor|viewer) with migration;
existing assignments promoted to editor
- New isEditorForAccount() in auth middleware for per-account write checks
- Replace global requireEditor with per-account checks in checks.js,
deposits.js, pdf.js, deposit-pdf.js, qbo-import.js
- GET /api/accounts now returns user_role per account
- users.js returns {account_id, role} per assignment; POST/PUT accept
accounts as [{id, role}]
- Frontend: state.accountRole tracks effective role for active account;
applyRoleUI and renderRow use it; user management shows role dropdown
per account assignment
Two-tab modal: "Checks to Print" parses QBO Transaction List / Check
Detail CSV exports; "Deposits" parses QBO Deposit Detail CSV exports
and groups by date. Both tabs show a preview before confirming import.