Commit Graph

15 Commits

Author SHA1 Message Date
null 88a32a983f feat(auth): passkey / security-key UI — WebAuthn ships end to end
The backend existed since 99abca9 with no way to reach it (QA-B1-01).
Now wired:

- LoginPage: requires_webauthn second step mirrors TOTP — the key prompt
  fires automatically after the password; cancel/retry + back-to-sign-in
- ProfilePage: PasskeySection (mirrors TotpSection) — enroll with a name,
  list keys, password-gated per-key remove + remove-all
- webauthnService: WEBAUTHN_ORIGIN stays authoritative; otherwise accept
  the browser's origin when its hostname equals the RP ID — fixes real
  dev/e2e enrollment where the Vite UI port differs from the API port
  (the browser already enforces RP ID ⊆ origin, so no widened trust)
- Deploy safety: WEBAUTHN_RP_ID documented in .env.example + a boot-time
  prod warning in utils/env.cts (localhost-bound keys silently fail
  behind a real domain)
- e2e/webauthn.probe.spec.js: CDP virtual authenticator drives the full
  lifecycle (enroll -> key sign-in -> password-gated removal), retiring
  Cycle 1's 'needs a human with a key' assumption. Probe suite 18/18.

Server 252/252, client 52/52, typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:30:14 -05:00
null b4634a8080 feat(logging): redacted, level-gated logger; migrate all server console.* (Pillar D)
utils/logger.cts — console-compatible log.{debug,info,warn,error} with:
- level gating (LOG_LEVEL; debug in dev / info in prod), and
- REDACTION of secrets/PII (sensitive keys + bearer/JWT) so tokens,
  passwords, and encrypted values never reach the logs (financial app).
Backend is abstracted (console now, swappable to pino later).

Swept all 317 server-runtime console.* across 35 files → log.* + inserted
the import at correct scope. tests/logger.test.js pins the redaction
contract (4 tests). CODE_STANDARDS updated; raw console.* is now banned in
server code.

Verified: check:server + typecheck:server + lint clean; 240/240 server
tests; client 50/50; build; e2e probe 17/17; boot smoke logs flow through
the logger and graceful shutdown intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 16:05:18 -05:00
null 26d976e272 feat(config): boot-time env validation — fail-fast + prod key warning (Pillar D)
utils/env.cts `validateEnv()` runs first in main(): exits 1 on clearly
invalid config (non-integer PORT / SESSION_CLEANUP_INTERVAL_MS, too-short
TOKEN_ENCRYPTION_KEY), and warns loudly in production when
TOKEN_ENCRYPTION_KEY is unset (secrets fall back to a DB-stored key).
Resolves the standing prod-hardening TODO. Verified: bad PORT → exit 1;
prod without key → warns but boots (health 200).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:51:04 -05:00
null 7390c458ed refactor(routes): categories → uniform Express-5 throw pattern (Pillar B template)
Establishes the canonical route error pattern (see docs/CODE_STANDARDS.md):
handlers `throw ValidationError/NotFoundError/ConflictError(...)` and let
Express 5 forward to the single terminal handler — no per-handler
try/catch swallows, no ad-hoc `res.status().json({error})`. Only the
UNIQUE-constraint → 409 mapping keeps a tight try/catch (`asConflict`).

- apiError factories NotFoundError/ConflictError now take an optional
  `field` (parity with ValidationError) so form-field info is preserved.
- routes/categories.cts fully converted; identical status/messages, now
  uniform `{ error, message, code, field }` bodies.
- Test harnesses (categoryGroups, categoryReorder) updated to simulate the
  terminal handler (thrown ApiError → formatted response).

Verified: 236/236 server tests; e2e probe 17/17; live smoke confirms
400 VALIDATION_ERROR / 404 NOT_FOUND with correct field + status.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:46:32 -05:00
null 5ad3c4e18e refactor(errors): one canonical error handler + process resilience (Pillar B)
- formatError() now emits `code` (the field the client reads) and hides
  internals on any 5xx (missing status treated as 500). Single body shape
  everywhere: { error, message, code, field? }.
- The terminal error handler now delegates to formatError, so a thrown
  ApiError produces its real status + message + code (previously every
  error collapsed to a generic "Internal server error"). Only 5xx are
  logged/recorded; 4xx client errors no longer spam the error tracker.
  This unblocks the Express-5 `throw ApiError` route pattern.
- Resilience: added `uncaughtException` (record + exit for clean restart),
  enriched `unhandledRejection` (record, don't crash), and a SIGTERM/SIGINT
  graceful shutdown that drains connections, checkpoints the WAL, and
  closes the DB — verified: kill -TERM → "database closed cleanly" + exit.

check:server + typecheck:server clean; 232/232 tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:33:38 -05:00
null f587d09d4a style: apply Prettier across client + server (no behavior change)
One-time repo-wide `prettier --write` (isolated commit so future diffs
stay reviewable). Mechanical formatting only.

Verified unchanged: typecheck + typecheck:server + check:server clean;
server tests 232/232; client tests 50/50; Vite build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:23:53 -05:00
null a6c9b6cb1c refactor(server): migrate 8 leaf services + apiError to TypeScript (.cts)
Batch of low-blast-radius modules: utils/apiError, and services statusRuntime,
loginFingerprint, auditService, transactionMatchState, updateCheckService,
advisoryFilterService, bankSyncConfigService.

- types/http.d.ts: shared permissive Express-ish Req/Res/Next/Router types
  (@types/express isn't installed; handlers are dynamic) — for apiError's
  errorHandler now and the routes/middleware batches next.
- Every require of a migrated module updated to the explicit .cts extension.
- Confirmed + documented the Node rule: a .cts needs at least one `import`
  statement or type-stripping never activates (node --check fails on the first
  type token); import-less modules lead with an `import type`.

Behavior-preserving. Verified: typecheck:server 0, check:server 0, suite 226/226.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:47:16 -05:00
null 3f9a6cf23e feat(server-ts): utils/dates → TypeScript (.mts); utils/ dir now TS (Phase 2)
dates.mts (ESM, typed) joins money.mts — the two most-required server
leaves are now TypeScript. Added .cts to the server tsconfig for the
CJS-module migration path. typecheck:server clean; suite 225 + probe 17/17.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:48:44 -05:00
null 200c6373f5 feat(server-ts): migrate the money boundary to TypeScript on Node's native runtime (Phase 2)
Establishes the server-TypeScript foundation and migrates utils/money →
utils/money.mts with branded Cents/Dollars (mirroring the client), so the
cents↔dollars boundary — the origin of the reconciliation bug class — is a
compile error for any typed server caller.

Approach (no build step): Node 25 runs .ts natively (type-stripping).
Migrated modules use the explicit-ESM .mts extension (the project is
"type":"commonjs", which disables .ts ESM auto-detection) and are required
from the CJS callers via Node's require(esm) — verified working. Infra:
tsconfig.server.json (allowJs + checkJs:false → incremental like the
client), npm run typecheck:server, check:server extended to .mts, wired
into ci. 28 require sites repointed to money.mts.

typecheck:server clean; full server suite 225 + e2e probe 17/17 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:44:04 -05:00
null 72d06aa2d8 fix(qa): cent-exact toCents rounding + money.js test coverage
- utils/money: toCents rounds off the shortest decimal string instead of
  Math.round(n*100), so 1.005 -> 101 (not 100). Output is identical for all
  integer / <=2-decimal / "$1,234.56" inputs, so no downstream change (QA-B7-01)
- add tests/money.test.js (9 tests; the money core previously had none)
- docs: archive QA-B7-01 to HISTORY v0.41.0; QA cycle 1 now 0 open findings

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:11:12 -05:00
null 029c227685 fix(qa): seed demo data amounts, bill amount validation, negative USD format, a11y aria-labels, Playwright E2E setup (batch 0.41.0 QA) 2026-07-02 20:36:09 -05:00
null 19d0e653a3 feat(utils): add money.js with integer-cents arithmetic helpers (batch 0.38.2) 2026-06-10 20:09:25 -05:00
null 947fa3bdf8 feat(auth): add per-username rate limiter, migrate dates to local-time utils (batch 0.38.1) 2026-06-10 19:42:51 -05:00
null 38c8bbd472 feat(server): add trust proxy, CSRF HTTPS detection, error formatting, dates util (batch 0.38.0) 2026-06-10 19:37:19 -05:00
kaspa 4d1709aea3 push 2026-05-09 13:03:36 -05:00