- 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
- Add .dockerignore: a local .env, the live SQLite database in data/, .git,
and node_modules were previously copied into the published image by COPY
- Run the app as the unprivileged node user; pre-create /app/data with
matching ownership so named volumes inherit it
- Set NODE_ENV=production in the image
- Document the one-time volume chown needed when upgrading existing
deployments
- Require account access on POST /api/pdf/preview: any authenticated user
could previously render any account's MICR line (routing and account
number) regardless of role
- Re-validate QBO import records server-side on confirm (date format,
positive amounts, integer check numbers) instead of trusting client JSON
- Make PUT /api/users/:id atomic: validate all fields up front, then apply
every change in a single transaction (a validation failure could
previously leave a half-applied update)
- Block demoting the last remaining admin
- Validate routing numbers as exactly 9 digits on account setup and update
- Cap upload sizes (50 MB .mdb, 10 MB CSV) and add a JSON error handler so
oversized uploads and bad JSON return clean 4xx errors instead of HTML
stack traces
- Disable the X-Powered-By header
- 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
- Shift check numbers right (0.16->0.28") so they clear the row number labels
- Darken label text (#444->##111) and disclaimer text (#666->#333)
- Move 'DEPOSIT TICKET' header and depositor/bank block down 0.12" on front page
- Vertically center the check grid on the back page
- Reposition 'ADDITIONAL CHECK LISTING' title relative to centered grid
- Deposit panel now pre-fills all 30 check slots on open (new and existing)
- Remove button maintains 30-slot minimum by appending a blank row
- Add Row button hidden at <30, visible at 30-59, disabled at 60
- Deposit slip PDF splits items: first 30 on front, up to 30 more on back
- Front page gains a FROM REVERSE row carrying back-page subtotal when needed
- Back page renders with same column positions/width as front, titled
'ADDITIONAL CHECK LISTING', numbered rows 31-60, and
'Forward to other side / TOTAL $' footer
Closes#10, closes#11
- Switch #deposit-panel to 100dvh so iOS browser chrome doesn't clip the Save button
- Move overflow-y:auto to #deposit-panel-body so the actions footer stays pinned
- Replace window.open(blob:) with <a download> click to fix PDF opening in Firefox iOS
(Firefox iOS blocks blob: URL navigation in new tabs)
Closes#7, closes#9
- Draw 1/8" grid overlay on layout editor canvas
- Anchor MICR second transit symbol at 2 59/64" from left
- Clamp draggable fields to printing safe zone (11/64" sides, 13/64" top, 0.5" bottom)
- Render dashed safe-zone outline on layout canvas
Convert the users/admin modal into a dedicated settings page with
left sidebar navigation and spacious content panels. Hash-based
routing (#settings/users, #settings/smtp, etc.) enables browser
back-button support and direct URL access. Admin-only tabs are
hidden for non-admin users.
Add per-account check position setting (top/middle/bottom/3-per-page)
so checks print in a specific slot on the page. Fix logos never
appearing on checks or in the layout editor — the Logo layout field
was missing from the default seed data and existing accounts.
Update to reflect multi-account support, OIDC/SSO login, deposit
slips, QBO import, visual layout editor, SMTP password reset, and
env var configuration including all OIDC variables.
OIDC configuration now comes from environment variables instead of
the database settings table. This is more natural for Docker/compose
deployments where secrets live in .env files.
Env vars: OIDC_ENABLED, OIDC_DISCOVERY_URL, OIDC_CLIENT_ID,
OIDC_CLIENT_SECRET, OIDC_REDIRECT_URI, OIDC_BUTTON_LABEL.
Also adds detailed [oidc] console logging throughout the authorize,
callback, and link flows to aid debugging connection issues.
Removes the OIDC settings UI section from the admin modal and the
GET/PUT /api/settings/oidc endpoints.
The TOTAL row in the deposit slip grid was only showing the sum of
check items (checksTotal) instead of the full deposit total which
includes currency + coin - cash back.
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
- Modal enlarged to min(1400px, 96vw) × 92vh
- Canvas fills full width; field controls moved to a strip below
- Inch rulers on top and left: major ticks at every inch (labeled),
half-inch, quarter-inch, and eighth-inch subdivisions
- Layout editor button hidden on mobile/portrait screens (canvas
requires landscape space to be usable)
- SVG canvas showing all layout fields scaled to check dimensions
- Click or dropdown to select a field; drag to reposition
- Sidebar shows X/Y coordinates in decimal inches with fraction
equivalents (¼", ½", ¹⁄₁₆", etc.)
- End X/Y inputs appear for Line and Graph fields
- Nudge buttons move selected field by ¹⁄₁₆" per click
- Auto-saves on drag end; debounced save on input/nudge changes
- Visible toggle hides fields from PDF without deleting them
- Admin-only Reset to Default wipes and re-seeds the layout
- Accessible to editor+ role via ⊞ button in account header
- One-time migration (layout_reset_v1) deletes all layout_fields for every
account and re-seeds with the default layout, ensuring .mdb-imported accounts
get the same clean check style as wizard-created ones.
- Remove the -0.25" slot 0 y-offset that was pushing Company Name and Check
Number above the top of the page on the first check.
- Consolidate layout field seed logic into database.js (seedLayoutFields),
removing the duplicate in app.js.
Accounts created before the layout seeder was deployed had no layout_fields
rows, so PDFs rendered blank (MICR only). The startup migration in database.js
now seeds default layout fields for any account with zero rows, idempotently
via INSERT OR IGNORE. Also adds Company Name3/Company Name4 cases to
resolveFieldValue so company3/company4 render correctly.
New accounts created through the setup wizard previously had no
layout_fields rows, so PDFs rendered only the MICR line. Now
seedDefaultLayoutFields() is called after INSERT to populate a
standard check layout (company block, payee, amount box, memo,
signature line, MICR-compatible positions) so the PDF is usable
without a .mdb import.
The Content-Security-Policy header (default-src 'self') blocked inline
onclick attributes, silently preventing the Edit and Delete buttons in
the user management modal from firing. Replaced with data-id attributes
and a delegated click listener on the users-list container.