Add OpenID Connect as an alternative login method. Users can sign in
via an external identity provider (e.g., Authentik, Keycloak, Google).
- OIDC settings configured in admin UI (discovery URL, client ID/secret,
redirect URI, button label, enable/disable toggle)
- PKCE-based authorization code flow with state and nonce validation
- Admin can manually link any user's OIDC identity (sub/issuer fields)
- Self-service linking: logged-in users can link/unlink their own account
- SSO button conditionally shown on login page when OIDC is enabled
- Username in header now clickable to open profile for all users
- Callback errors/success communicated via URL hash fragments
Password reset: users with a registered email can request a reset link
from the login screen. A one-hour signed token is emailed via SMTP;
clicking the link opens a set-new-password form. Tokens are hashed
(SHA-256) before storage and invalidated after use.
SMTP settings: admin-only panel in the Users modal lets admins
configure host, port, encryption, credentials, and from address.
Settings persisted in a new key-value settings table. The SMTP
password is never returned to the client.
Users: email field added to the create/edit form and stored in a new
users.email column. Email is used for password reset lookup.
Add Account: admins now have a + button in the header that opens the
existing setup wizard to add additional checking accounts.
Schema: adds password_reset_tokens and settings tables with automatic
runtime migrations for existing databases.
Content-Security-Policy: add header with default-src 'self',
unsafe-inline for styles (needed for JS-generated inline style attrs),
and data: for embedded logo/signature images.
JSON body limit: reduce from 10mb to 2mb (logo cap is 512KB base64).
Session maxAge: now configurable via SESSION_MAX_AGE_HOURS env var
(default 168h / 7 days). Documented in .env.example.
Password strength: centralize validation in auth.js and raise the bar
to 10+ characters with at least one letter and one non-letter. Applied
consistently to all four password-setting paths (initial setup,
login change-password, admin create user, admin edit user).
mark-printed: was only checking the first check's account — now fetches
all check IDs upfront, verifies they all exist and share the same
account, then checks editor access once on that account.
PDF generation: was authorizing against the client-supplied account_id
but fetching checks by ID without confirming they belong to that account
— now rejects any check ID whose account_id doesn't match.
Role/account-assignment changes: active sessions for the affected user
are now deleted immediately via json_extract on the sessions table, so
demotions take effect at once rather than at session expiry (up to 7d).
- 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
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