Commit Graph

46 Commits

Author SHA1 Message Date
steve 0b21f4ea3c feat(layout): add preview PDF button to layout editor
Generates a 3-up PDF with dummy check data (no real checks touched) so the
layout can be proofed without printing live checks.

Closes #12
2026-05-02 17:21:41 -06:00
steve f91fc7bd8a fix(deposits): bold check numbers and amounts for better print visibility 2026-04-28 10:28:08 -06:00
steve bb935acfa9 fix(deposits): check number spacing, darker text, header/title repositioning
- 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
2026-04-28 09:59:28 -06:00
steve 4a47394923 fix(deposits): darken deposit slip grid lines for scanner readability 2026-04-28 09:14:52 -06:00
steve 189ae53d34 feat(deposits): pre-fill 30 slots and add back-page overflow for 31-60 checks
- 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
2026-04-28 09:04:44 -06:00
steve a2de7e2d9d feat(layout): add grid, safe zone, and MICR anchor alignment
- 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
2026-04-13 08:06:23 -06:00
steve deb31d248f feat: add check position selector and fix logo not rendering
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.
2026-04-10 19:54:17 -06:00
steve da5d436432 feat: move OIDC settings to env vars and add debug logging
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.
2026-04-09 16:34:14 -06:00
steve dff5fd4156 fix: deposit slip total row now includes currency and coin
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.
2026-04-09 16:19:34 -06:00
steve 3b1a35b7f2 feat: add OIDC login with account linking
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
2026-04-09 14:48:50 -06:00
steve d70081159d feat: visual layout editor for check field positioning
- 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
2026-04-01 15:01:30 -06:00
steve 8a944d1d20 fix: reset all accounts to default layout; fix slot 0 top-field clipping
- 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.
2026-03-31 17:27:55 -06:00
steve 9897841666 fix: seed layout fields at startup for accounts with none; add Company Name3/4 mapping
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.
2026-03-31 17:01:06 -06:00
steve 368e41037f feat: seed default layout fields when creating account via wizard
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.
2026-03-31 16:20:02 -06:00
steve fc114d0ec6 feat: add password reset, SMTP settings, and Add Account button
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.
2026-03-31 10:21:49 -06:00
steve 444e24a191 Fix remaining critical, medium, and low security issues
IDOR (critical): GET /api/checks/:id and GET /api/deposits/:id now
verify the requesting user has access to the record's account before
returning data. Previously any authenticated user could fetch any
record by ID across accounts.

Printed check guard (critical): PUT and DELETE on checks now return
409 if the check has already been printed, enforcing the business rule
that printed checks are immutable. Previously the printed flag was
only enforced in the frontend.

PDF DoS (medium): checkIds array capped at 300 (100 pages × 3 per page).

QBO import DoS (medium): records array capped at 1000 per confirm call.

PDF error detail (medium): internal err.message no longer returned to
the client on PDF generation failure.

SESSION_SECRET (low): removed NODE_ENV=production condition — the
server now exits immediately on startup if SESSION_SECRET is unset
regardless of environment. Dev script updated to load .env via
node --env-file=.env so developers set it once in a local .env file.

Password hints (low): updated all three UI labels from "min 8 chars"
to "min 10 chars, include a digit or symbol" to match the actual
server-side validation.
2026-03-20 13:28:18 -06:00
steve bd3e66cd44 Fix low-level security issues
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).
2026-03-20 12:25:42 -06:00
steve 2939bfa608 Fix high and medium security vulnerabilities
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.
2026-03-20 08:21:23 -06:00
steve 0f00624e61 Fix three critical authorization vulnerabilities
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).
2026-03-19 22:44:11 -06:00
steve af88549ad8 Modal scroll fix; per-account editor/viewer roles
- 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
2026-03-18 23:31:23 -06:00
steve f827210a07 Implement user authentication and role-based access control
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
2026-03-18 22:55:17 -06:00
steve 1277fc4aad Fix delete account: also remove layout_fields rows before deleting account 2026-03-18 21:37:34 -06:00
steve e4feafa82b Add Delete Account button with confirmation modal
Red "Delete Account" button at top of Account Settings. Clicking opens
a second modal with an irreversibility warning and the account name,
requiring a second explicit confirmation before deletion. Deletes the
account row plus all associated checks and deposits. Redirects to the
setup wizard if no accounts remain.
2026-03-18 21:15:05 -06:00
steve 35a5d576ea Add QuickBooks Online CSV importer for checks and deposits
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.
2026-03-18 14:55:21 -06:00
steve c944c84939 Thinner check lines; second signature line toggle
- Halve all check line thicknesses (line_thick * 0.5)
- Add second_signature column to account table with runtime migration
- Account settings toggle: enables a second signature line drawn 0.25"
  above the primary signature line, matching its width and thickness
2026-03-18 14:04:15 -06:00
steve 964823c8b4 Check 1 position fix; remove comma from next check number display
- Move check slot 0 up 0.25" to match physical stock alignment;
  slots 1 and 2 are unaffected
- Remove toLocaleString() from next check number header display
  so no comma appears in the thousands place
2026-03-18 12:36:23 -06:00
steve 2f2646fb78 Add one-off next check number override
Pencil button next to "Next check: X" in the header opens a modal
with a warning about sequence gaps/duplicates. Saves via new
PUT /api/account/:id/check-no endpoint (sets current_check_no = next - 1).
2026-03-18 10:56:05 -06:00
steve 830a7ca686 Deposit slip: remove outer border rect, switch deposit total to Courier
- Remove the outer border rect around the main content area
- Change DEPOSIT TOTAL font from Helvetica-Bold to Courier to match
  the monospaced style used for other amount fields
- Update strip element Y positions for better spacing
2026-03-13 10:33:19 -06:00
steve a97897c30a Deposit slip: landscape page; strip deposit total combined to one line 2026-03-13 10:19:36 -06:00
steve 4a56f15fc6 Deposit slip: portrait layout explicit; MICR repositioned below deposit total 2026-03-13 09:58:30 -06:00
steve ac5670039a Check select-all, filtered total, deposit slip layout fixes
- Add select-all checkbox to checks table header; checks/unchecks all
  visible (filtered) rows; supports indeterminate state
- Add summary bar below checks toolbar showing count and total amount
  of filtered checks
- Deposit slip: output to full 8.5x11 letter page for trimming
- Deposit slip: remove beige left strip fill (white background)
- Deposit slip: remove vertical separator between depositor/bank info
- Deposit slip: stack bank info below account info instead of side-by-side
- Deposit slip: lower date underline position
- Deposit slip left strip: flip text orientation to read tilt-left
  (rotate 90 instead of -90; reposition all strip element anchors)
- Deposit slip left strip: center MICR and labels in strip width
- Deposit slip total: include decimal point in rotated digit amount
2026-03-13 09:48:21 -06:00
steve 4fb7fd209c Add deposit slip and report generation
- New Deposits tab with ledger: date, checks total, cash, deposit total, item count, status
- Slide-in deposit panel: date, currency, coin, cash back, dynamic check entry rows, live totals
- Save deposit, then generate Deposit Slip or Deposit Report PDF
- Deposit slip: 3.375" x 8.5" portrait with Style A background drawn server-side,
  digit-column amounts, GnuMICR routing/account line rotated 90 deg, rotated
  deposit total and check count in left margin
- Deposit report: plain Courier ledger with depositor/bank info, check grid, totals
- deposits and deposit_items tables in schema; ON DELETE CASCADE for items
- Routes: GET/POST/PUT/DELETE /api/deposits, POST /api/deposit-pdf
- Generating a slip marks deposit as printed; date range and status filters
- README updated to describe deposit slip feature
2026-03-13 08:43:34 -06:00
steve a89db179cd Add account settings edit UI and GnuMICR.otf font
- Add gear button in header to open account settings modal
- Modal covers Organization, Bank, Account, Logo upload, and Printer Offset fields
- PUT /api/account/:id backend endpoint with full field validation
- Logo file reader with inline preview; only updates logo if a new file is chosen
- CSS for btn-header-icon, settings-section-label, logo-preview, form-row-4
- Add GnuMICR.otf (tracked alongside existing GnuMICR.ttf)
2026-03-12 22:58:56 -06:00
steve c689ddc72a Fix startup crash on existing DB: move account_id indexes after migration 2026-03-12 22:24:15 -06:00
steve e81a4386d2 Add multi-account support
- Schema: account_id FK on checks and layout_fields; UNIQUE per-account on check_no and field_name
- DB: runtime migration recreates both tables to add account_id (assigns existing rows to account 1)
- Routes: GET /api/accounts lists all; GET /api/account/:id replaces hardcoded id=1; POST /api/account/setup always creates a new account and returns accountId
- checks.js: all queries scoped by account_id; POST requires account_id in body
- pdf.js: resolves account from check's account_id instead of id=1; layout fields fetched per-account
- import-mdb.js: always INSERTs a new account (never deletes existing); all records tagged with new accountId
- Frontend: account switcher in header; activeAccountId persisted to localStorage; all API calls pass account_id; switching accounts reloads checks; wizard and import auto-switch to newly created account
2026-03-12 22:13:52 -06:00
steve 5f9cc16ea5 Support multi-page PDFs for more than 3 selected checks
- pdfService: batch checks into groups of 3, add doc.addPage() between groups
- pdf route: remove 1-3 limit, accept any number of check IDs
- Frontend: remove 3-check selection cap; any number of checks can be selected
2026-03-12 21:16:37 -06:00
steve 667ec146cc Replace printed/unprinted tabs with unified list and inline filters
- All checks shown in one table by default (load all, no server-side printed filter)
- Add payee search, date-from/to, and status filter controls to toolbar
- Filtering is client-side; no extra API calls on filter changes
- All checks get Edit/Delete buttons regardless of printed status
- All checks get checkboxes for PDF selection
- Remove separate Reprint button and reprintCheck function
- Remove printed guard from PUT and DELETE endpoints
2026-03-12 21:12:08 -06:00
steve f827de9b1a Shift company name to logo x origin when no logo is present 2026-03-12 17:37:53 -06:00
steve 91958b5331 Remove MICR font diagnostic logging 2026-03-12 17:35:21 -06:00
steve e1a22bdd1c Add MICR font diagnostic logging to trace load failure 2026-03-12 16:29:44 -06:00
steve 27d580e967 Switch MICR font to TTF; fix multi-line rendering for bank info and address fields
- Use GnuMICR.ttf instead of .otf (OTF converted from PS Type 1 may not embed in PDFKit)
- Add renderLines() helper that splits on \n and places each line at explicit Y offset,
  so lineBreak:false is honored per-line and PDFKit cursor doesn't drift
- Include bank_name as first line of Bank Information field
2026-03-12 16:03:22 -06:00
steve c7ce87afd5 Fix MICR font path, date import, PDF button bug; clean up config; add TODO markers
- Hardcode GnuMICR.otf path in pdfService.js; remove MICR_FONT_PATH env var
- Fix normalizeDate to handle MM/DD/YY (2-digit year) and return null on no match
- Fix generatePdf button DOM bug: update span directly instead of overwriting textContent
- Remove .env.example and NTFY_URL from docker-compose (app has no required config)
- Remove redundant fonts volume mount from docker-compose (fonts bundled in image)
- Mark MVP TODO items complete; add // TODO comments in source for post-MVP features
- Update README: correct slot height, remove stale env var docs
2026-03-12 15:49:56 -06:00
steve f5b1292aff Fix check slot height to 3.5", remove separator lines between checks 2026-03-12 14:49:22 -06:00
steve 14e1e15226 Add first-run setup wizard with 3-step account configuration 2026-03-12 14:18:15 -06:00
steve 68300d0375 Add .mdb import button and modal with server-side migration runner 2026-03-12 14:15:13 -06:00
steve e252ddb952 Add full project structure: backend, frontend, Docker, and CI workflows
- Organize backend into src/ (routes/, services/, db/) per package.json entrypoint
- Add migrations/import-mdb.js for one-time .mdb → SQLite migration
- Add public/ frontend: check ledger table, slide-in new/edit panel, PDF generation
- Add docker/Dockerfile and docker-compose.yml for self-hosted deployment
- Add .github/workflows: Docker Hub build+push on main/tags, TODO→Issues scanner
- Add GnuMICR font files (GPL-2.0) for MICR E-13B line rendering
2026-03-12 10:29:36 -06:00