Repository navigation
feat: per-user access, stored bank sync, fail-closed production - #1
Merged
Merged
Conversation
…cess auth
Replace the Hono API with NestJS, organised as business feature modules:
access, bank-connections, merchant-rules, settings, sheet-export, health,
plus shared (database, crypto, http). Behaviour and response shapes of the
existing endpoints are unchanged (POSTs still return 200, errors still {error}).
Bank sync (Enable Banking, with a built-in test bank for local use):
- off by default; misconfiguration fails closed (503), test bank refused in production
- Cloudflare Access JWT verified server-side, admin allow-list for the back office
- CSRF header on mutating requests, single-use state + HttpOnly cookie on the callback
- session ids encrypted at rest (AES-256-GCM); transactions are never stored server-side
- sync throttle, audit log, log redaction, no-store responses
- reconnecting an already connected bank replaces the old connection
- each synced row carries bank, account label and a stable account reference
Tooling and housekeeping:
- oxlint + oxfmt configs; vitest + supertest tests (60)
- load api/.env on local runs (real environment variables still win)
- api/.env.example and a refreshed root example.env
- remove the AI categorization feature and the OpenAI dependency
- Dockerfile: PORT=3000, node dist/main.js
- README rewritten for the self-hosted, privacy-first setup
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…d month filters Restructure the single dashboard into routed pages under /app, with a left sidebar (drawer and bottom tab bar on phones), breadcrumbs and one global period picker. Pages and hierarchy: - Overview, Banks, Connect, a bank (accounts), Transactions, merchant, Review, Income, Analytics, Trends, Export, Settings (sheet, columns, rules, back office for admins) - banks -> accounts -> transactions -> merchant groups -> one merchant, each a link - filters live in the address (?cat=Groceries&bank=N26&view=merchants), so views can be bookmarked and survive the back button; saved views and removable chips - bank pages redirect to the overview when banking is off; clear messages when FinKit's own server is unreachable Bank sync (replaces the modal): connect flow, status and expiry bars, sync, sync all, reconnect, disconnect, back office; rows carry bank and account. Period handling: year-first picker (all time, quick picks, years with months) and year/month tabs on Analytics, Trends, Income, Review and Transactions. Export picks a year, then a month, and copies that year's rows. Mobile: transactions as cards with a sort menu, bottom tab bar, no horizontal overflow on any route. Also: review queue "up next" list and counts, category legend with amounts, income vs spending running totals, category-chart and trend colours that match their meaning, vendor chunks (react, charts, xlsx, tanstack), serve.json rewrites for /app/* routes, oxlint/oxfmt, tests for period summaries (28). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… phase machines Reorganize the SPA into features/* and shared/* with docs/web-architecture.md; split AppState into workspace hooks, modularize parser/categorizer/sheet layout, and model import, settings, banking, review, and table flows with explicit phases. Co-authored-by: Cursor <cursoragent@cursor.com>
Allowlist users with verified Cloudflare Access, production fail-closed env checks, and user-scoped rules, settings, and banking; store encrypted synced transactions server-side with delete/disconnect flows. Reorganize bank-connections, validate Sheets A1 ranges, add root package scripts, stylistic lint/format rules, module constants, and updated docs/README. Co-authored-by: Cursor <cursoragent@cursor.com>
…istory on first fetch Manual syncs now tell the bank a person is present (their IP and browser, only when the bank's required PSU headers are ones we can supply), so they don't use up the daily background-fetch limit. Scheduled syncs send none. A connection's first fetch asks for the bank's longest history and falls back to the normal window if refused. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…update privacy and terms - Stored transactions: date, amount, text, names and IBANs are one encrypted value per row; the repeat-skipping key is a keyed fingerprint. Older databases are upgraded on start. - example.env: required production settings first, optional ones grouped below; drop the GitHub registry login (images are pulled anonymously); TUNNEL_TOKEN matches docker-compose. - docker-compose.prod: mount ./secrets read-only at /run/secrets for the Enable Banking and Google key files; deploy.sh creates the folder; README documents generating the key in place. - Privacy and terms pages and docs/privacy-facts.md reflect stored transactions, multiple users, forwarding the person's IP and browser to the bank on manual syncs, retention and deletion. - serve.json: /terms and /privacy resolve to the .html pages. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Move to Node 26 in Docker and engines; upgrade web to React 19 and TypeScript 6 with TS 6 deprecations silenced; pin direct dependency versions; use Nest SWC builder with forked typecheck; harden better-sqlite3 postinstall and run it before dev/start so native binaries match the host Node. Co-authored-by: Cursor <cursoragent@cursor.com>
…nt and a stale key name - docker-build.yml: a check job (lint/format, types for web, tests) for api and web; the image builds wait for it. - install-better-sqlite3.cjs: blank lines the linter wants; api/package.json key order from the formatter. - enable-banking-setup.md: the key file is enablebanking.pem, as everywhere else. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The app is stopped and started on a schedule, so a connection can be days out of date when it comes back. Run one catch-up shortly after start instead of waiting for the first 30 minute check. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…encies The API image build took its node_modules from a published dependency image, which on a PR comes from main and can be stale (it lacked the nest CLI), and the deps job pushed images from PRs. - api/Dockerfile: the builder installs devDependencies itself; production dependencies come from the published image only when CI passes one, otherwise they are installed in the build. - docker-build.yml: PRs skip the deps job, log in nowhere and push nothing; main and tags publish as before. - api: build script calls the Nest CLI directly; better-sqlite3 12.11.1. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Also: Sheets input validation (tab names, column letters), generic export errors,
/termsand/privacyroutes, privacy and terms pages anddocs/privacy-facts.mdupdated,.env.examplerewritten (required first),./secretsmounted read-only for key files, CIcheckjob gates the image builds.Evidence
ALLOWED_ORIGINbooted in shareddev@localhostmode with open CORS; a missingOriginpassed CSRF; export errors leaked Google messages; stored transactions did not exist.After: API 106 tests, web 35 tests, lint, format and types pass locally.
Merge Danger
Door: one-way
The database upgrades itself on start: rows from before accounts existed are given to the first address in
ALLOWED_USER_EMAILS, and the earlierbank_transactionslayout is rewritten into the encrypted one. Take a backup first.Blast Radius: Deployment
ALLOWED_ORIGIN,CF_ACCESS_TEAM_DOMAINandCF_ACCESS_AUDare set. The tunnel variable isTUNNEL_TOKEN(the old example saidCLOUDFLARE_TUNNEL_TOKEN).BANKING_ENCRYPTION_KEYnow protects all stored transactions; losing it loses them. Back it up apart from the database.🤖 Generated with Claude Code