Commit Graph

10 Commits

Author SHA1 Message Date
null 2a07e762a2 fix: four review-confirmed regressions in auth + OIDC flows
All found by the multi-angle /code-review over the branch and verified
against the installed libraries:

- 401 discriminator: the session-expiry redirect keyed off a path prefix
  (!/auth/*) and falsely logged users out on /profile/change-password
  with a wrong current password. requireAuth's no-session 401 now carries
  the distinct AUTH_REQUIRED code — the only signal api.ts dispatches
  auth:expired on; wrong-credential 401s keep AUTH_ERROR on any path.
  Test updated to pin the code-based contract on both directions.
- OIDC RFC 9207: exchangeAndVerifyTokens rebuilt the callback URL with
  only code+state, dropping the iss parameter openid-client v6 validates
  when the provider advertises support (authentik does) — every OIDC
  login would fail post-upgrade. The full callback query is now forwarded.
- OIDC http issuers: v6 enforces HTTPS on discovery/endpoints (v5
  didn't) — breaking internal http:// providers on Docker networks.
  OIDC_ALLOW_INSECURE_HTTP=true opts back in (documented in .env.example).
- 2FA downgrade: authService's webauthn fallback caught ANY challenge
  error and silently downgraded to password-only login; now only the
  stale-flag 'No registered WebAuthn credentials' case falls through,
  everything else rethrows.
- Burned-challenge retry: the server consumes the single-use 2FA
  challenge before verifying (anti-replay), so after a server rejection
  a retry can only ever return 'Challenge expired'. LoginPage now resets
  to sign-in on server failure (WebAuthn AND the pre-existing TOTP trap);
  a browser-prompt cancel keeps retry (token still live). Also lazy-loads
  @simplewebauthn/browser out of the entry bundle, matching ProfilePage.

Server 252/252, client 52/52, OIDC smoke 44/44, probe 33/33, e2e 27.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:57:07 -05:00
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 abb9f7d443 fix(migration): repair server.js/.cts launch + require refs missed in the JS→TS migration
Full-deployment QA against a copy of the real DB surfaced launch/require
references the server .cts migration didn't update. All are non-source
tooling/edge paths, which is why typecheck/check/tests stayed green:

- e2e/setup/prepare-db.js: require('../../db/database') and
  require('../../scripts/seedDemoData') → .cts. This broke `npm run
  test:e2e*` entirely (harness couldn't load). [material]
- playwright.config.js + scripts/prod-smoke.sh: webServer/boot command
  `node server.js` → `node server.cts`. Also blocked the e2e + prod-smoke
  suites. [material]
- setup/firstRun.js → .cts, require('../services/auditService') → .cts,
  and server.cts call site → require('./setup/firstRun.cts'). Latent:
  server.cts only reaches firstRun when userCount===0, but database.cts
  auto-seeds a default admin during getDb() first, so the path isn't hit
  in normal boot — fixed defensively so it can't crash if ever reached.
- .env.example: doc reference `node server.js` → server.cts.

Verified: typecheck:server + check:server clean; server test suite
226/226; e2e probe 17/17 (was 0 — suite couldn't boot before); the
production Docker image builds and boots on a copy of the real
production DB (44 bills, 1159 payments) with /api/health 200 and all 64
page endpoints returning 2xx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:05:40 -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
null 67ce59db50 v0.35.0 2026-05-31 15:52:50 -05:00
null b03264ceb1 feat: zero-config encryption + SimpleFIN Bridge links
- encryptionService.js: getKey() tries TOKEN_ENCRYPTION_KEY env first, then
  auto-generates a random 48-byte key on first startup, persists to settings
  as _auto_encryption_key. assertEncryptionReady() is now a no-op.
- bankSyncConfigService.js: removed encryption_key_set response and
  encryptionKeyReady() helper. No env config required.
- .env.example: TOKEN_ENCRYPTION_KEY removed. Comment says enable from Admin
  panel, no env config required.
- BankSyncSection.jsx: added SimpleFIN Bridge links — 'Open SimpleFIN Bridge'
  for first-time setup, 'Get a SimpleFIN token' for existing connections
2026-05-29 00:04:28 -05:00
null 22df64e5e7 feat: auto-sync worker for SimpleFIN bank sync
New:
  services/bankSyncWorker.js — interval-based worker running every 4h (configurable via SIMPLEFIN_SYNC_INTERVAL_HOURS)
    - Checks bank sync enabled, fetches oldest-synced sources, skips <1h old
    - Staggers syncs 3s apart, writes last_error on failure, timer.unref() for clean shutdown

Modified:
  server.js — starts worker inside app.listen callback
  routes/admin.js — GET bank-sync-config includes worker status (running, interval, last/next run)
  client/components/admin/BankSyncAdminCard.jsx — shows auto-sync worker status panel when enabled
  .env.example — SIMPLEFIN_SYNC_INTERVAL_HOURS
2026-05-28 22:32:33 -05:00
null 42abb12497 feat: SimpleFin bank sync with encrypted token storage
New services:
  services/encryptionService.js — AES-256-GCM with SHA-256 derived key
  services/simplefinService.js — protocol layer: claim token, fetch accounts/transactions, normalize to DB shapes
  services/bankSyncService.js — orchestration: connect, sync, disconnect with encrypted access URL storage

Modified:
  routes/dataSources.js — status, connect, sync, disconnect endpoints (gate on BANK_SYNC_ENABLED=true)
  client/api.js — simplefinStatus, connectSimplefin, syncDataSource, deleteDataSource, dataSources
  client/pages/SettingsPage.jsx — BankSyncSection with connected account info, sync/disconnect actions, setup token input
  .env.example — BANK_SYNC_ENABLED, TOKEN_ENCRYPTION_KEY, SIMPLEFIN_APP_NAME
2026-05-28 21:30:20 -05:00
kaspa 4d1709aea3 push 2026-05-09 13:03:36 -05:00
_null b9d1366d46 initial commit 2026-05-03 19:51:57 -05:00