Commit graph

3 commits

Author SHA1 Message Date
ef02b3f585 feat(ops): emergency password-reset CLI + deployment docs
New CLI: bun run reset-password <email>

Two modes selected interactively:

- Recovery mode: if you still have the user's recovery code, unwrap
  the existing DEK with it and re-wrap with the new password. No data
  loss; the recovery code stays valid (mirrors /auth/recovery-complete).
- Nuke mode: if both password AND recovery code are gone, generate a
  fresh DEK + new recovery code (printed once), and DELETE the user's
  private activities — their ciphertext is permanently unrecoverable.
  Public/semi/friends rows and engagement (hearts/bookmarks/done) are
  preserved.

Both modes invalidate the user's sessions.

Password length matches the signup/recovery rule (12 chars min).
Wrong-recovery-code path aborts before any DB writes. Hand-rolled
line reader sidesteps a Bun quirk where node:readline only delivers
the first answer when stdin is piped.

Also expand README's "Deployment" section: container snippet stays,
plus new subsections for env vars, TLS termination (with a Caddyfile
example), backup/restore via sqlite3 .backup, the /api/health
healthcheck, upgrade flow, and a walkthrough of the reset CLI.
2026-05-25 20:04:57 +02:00
59e2f95767 Drag-reorder works on touch via svelte-dnd-action
HTML5 native drag-and-drop doesn't fire on touchscreens — mobile
users couldn't reorder the list at all. Swapped the manual DnD
wiring (dragstart/dragover/drop) for svelte-dnd-action, which uses
Pointer Events and handles mouse, touch, AND keyboard reorder
uniformly. Linear-quality reorder UX for ~11 KB gzipped.

Replacement details:
  - bun add svelte-dnd-action (0.9.69)
  - Home.svelte: ~70 lines of manual handler code deleted, replaced
    with ~30 lines wiring up `use:dndzone` + `onconsider` +
    `onfinalize`. The midpoint-position math for sort_position is
    unchanged (finalize gives us the new neighbour list directly).
  - ActivityRow.svelte: the drag handle's `onpointerdown` flips a
    parent-owned dragDisabled flag to false — the library then
    takes over. Standard "handle-only drag" recipe; clicks on the
    title/buttons inside the card don't initiate drag because
    dragDisabled stays true everywhere else.
  - dndItems is a buffer copy of `filtered` that the library
    mutates during a drag. An $effect re-syncs it from `filtered`
    between drags (so new activities still float to the top, etc).
  - Shadow item (the library's placeholder while dragging) is
    rendered at 30% opacity so the drop target is visible without
    flashing.

Accessibility wins for free:
  - Keyboard reorder: focus an item, press Space to "pick up",
    arrow keys to move, Space to drop. Screen readers get
    polite-live announcements of each move from the library.
  - Touch reorder works on iOS Safari and Android Chrome.

96 tests still pass; typecheck clean; build ok.
Bundle: 122 KB → 154 KB (gzipped 42 → 53 KB, ~+11 KB).
2026-05-25 16:59:43 +02:00
47963c9225 Scaffold Vinterliste — end-to-end encrypted winter activity list
Foundation for an E2E-encrypted activity list per
winter-list-claude-code-prompt.md.

Server (Bun + Hono):
- bun:sqlite with WAL and the spec's schema (idempotent migration)
- opaque server-stored sessions, httpOnly cookie
- signup / challenge / login / logout / me / password / recovery-challenge /
  recovery-complete
- activity CRUD with strict visibility rules: private uses ciphertext+nonce,
  semi never serializes owner_id, public attributes the owner
- tag store with normalisation + autocomplete (semi/public only)

Frontend (Svelte 5 + Vite):
- libsodium-wrappers-sumo for client crypto (Argon2id + XChaCha20-Poly1305).
  SUMO is required because the standard build omits crypto_pwhash.
- IndexedDB-backed private tag index (never leaves the browser)
- in-memory DEK (no localStorage); page reload re-prompts for password
- signup shows the recovery code once; tag input merges server + private
  sources with clear labelling
- Bokmål UI

Crypto module (shared/crypto.ts):
- pure, runs in both Bun and the browser via a runtime-conditional loader
  that papers over libsodium-wrappers-sumo's broken ESM entry (createRequire
  on server, Vite alias in the browser)
- DEK wrap/unwrap, AEAD payload encryption, recovery code generation with
  a visually-unambiguous alphabet

Verification:
- 22 crypto round-trip tests (wrap/unwrap, AEAD tamper rejection, password
  change preserves ciphertexts, recovery still works after rotation)
- typecheck passes for server and frontend
- Vite production build succeeds; libsodium SUMO chunk is ~315 KB gzipped

Single-image Containerfile for podman: builds frontend in a builder stage,
runs Bun in a slim runtime; one volume for the SQLite file; BUILD_DATE /
GIT_REVISION baked into OCI labels and /etc/build-info.

Known limitation deferred for this commit: the recovery endpoint has no
server-side proof of the recovery code (anyone who knows an email can lock
out the legitimate user, though they can't read any data). Closed in the
next commit.
2026-05-25 12:27:14 +02:00