Skip to content

Claude/repo potential review 25d776 - #1

Merged
Coriana merged 3 commits into
mainfrom
claude/repo-potential-review-25d776
Jul 9, 2026
Merged

Claude/repo potential review 25d776#1
Coriana merged 3 commits into
mainfrom
claude/repo-potential-review-25d776

Conversation

@Coriana

@Coriana Coriana commented Jul 9, 2026

Copy link
Copy Markdown
Owner

No description provided.

Coriana and others added 3 commits July 9, 2026 18:34
Security fixes:
- storage: contain all file paths within the bucket dir and drop the
  double-decode, closing arbitrary read/write/delete via path traversal
- crud/external-api: validate every dynamic column name against the real
  table columns (pragma table_info), closing SQL injection through JSON
  body keys and query filter/select/order fields
- auth: require team:write for user creation and settings:write for API
  key creation; ignore a forged user_id unless the caller has team:write
- auth: replace passwordless first-login (account takeover) with a
  single-use, 72h invite-token flow (/auth/accept-invite)
- middleware: re-check is_active on every request so disabling a user
  takes effect immediately instead of at JWT expiry
- index: refuse to boot in production with a placeholder JWT secret,
  warn on wildcard CORS, and rate-limit login/accept-invite
- Dockerfile: remove insecure baked-in JWT_SECRET and wildcard CORS

Bug fix:
- auth: datetime("now") used double quotes; better-sqlite3 runs with
  SQLITE_DQS=0 so the statement threw and execute() swallowed it, meaning
  change-password and accept-invite silently never persisted. Now
  datetime('now').

Frontend:
- rework SignUp into invite acceptance (reads ?token=, calls accept-invite)
- make ForgotPassword honest (no fake "email sent"); relabel Login link
- add auth.acceptInvite to the API client

Tests + CI:
- Vitest + supertest integration tests over a temp SQLite (17 tests)
  covering each security invariant with positive controls
- GitHub Actions CI: gates on server typecheck, tests, frontend build,
  and docker build; lint/frontend-typecheck run non-blocking
- fix tsconfig.server.json CommonJS/ESM mismatch and Express param typing
  so the server typechecks cleanly

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Invoice/job numbers were generated three inconsistent ways: the external
API used COUNT(*)+1 (which reuses a number after a delete and uses a
different format), while the frontend and recurring generator did a
read-modify-write of the company_settings counter across separate steps
(racy: two concurrent creates get the same number and the second fails
the UNIQUE constraint).

Server:
- add server/utils/numbering.ts: allocateInvoiceNumber/allocateJobNumber
  do an atomic read-increment-write of the settings counters inside a
  transaction (better-sqlite3 is synchronous, so this is race-free)
- external-api: allocate numbers via the shared helper instead of COUNT(*)
- functions.ts: recurring generation uses the allocator; add
  POST /api/functions/allocate-invoice-number and /allocate-job-number
  for the frontend to reserve a number atomically
- schema: add job_prefix/job_next_number to company_settings; migration
  adds them to existing DBs and creates UNIQUE indexes on invoice_number
  and job_number only when no duplicates exist (skips with a warning
  otherwise so boot never breaks)
- also fixes a latent double-dash format bug (prefix 'INV-' + '-' number)

Frontend:
- InvoiceDetail/JobDetail reserve the number via the atomic endpoint at
  save time and stop writing the counter back client-side; a user-typed
  custom invoice number is still respected on the create path
- preview format now matches the server allocator output

Tests: add numbering.test.ts (6 tests) — sequential allocation, no reuse
after delete, distinct numbers via the endpoint and external API, and the
UNIQUE backstop. Full suite 23 passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
App.tsx statically imported all ~35 pages, so the login screen downloaded
the entire app (a single 1.4 MB / 370 kB gzip JS chunk). Convert every
page to React.lazy behind a Suspense boundary (Login stays eager as the
common entry point). Initial entry chunk drops to ~388 kB / 123 kB gzip,
with each route loaded on demand; the >500 kB build warning is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Coriana
Coriana merged commit de6fbfa into main Jul 9, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant