managarten/apps
Till JS 1ba5948ce5 feat(mana/web): encryption foundation — phase 1 (no-op)
Lays the groundwork for selective field-level encryption-at-rest in the
data layer. Phase 1 ships ONLY the building blocks; nothing is actually
encrypted yet (every registry entry has enabled:false), so this commit
is a no-op for app behaviour and safe to merge.

New module: src/lib/data/crypto/

aes.ts — pure Web Crypto AES-GCM-256 wrap/unwrap
  - wrapValue / unwrapValue with format-versioned envelope
    `enc:1:<base64-iv>.<base64-ct>` — one-scan detection, survives
    JSON.stringify on the sync wire, ~1.4× original byte length.
  - JSON-stringifies the input so any value type works (string, number,
    object, array). null/undefined pass through unchanged so optional
    fields don't need a guard at every call site.
  - Authenticated encryption: tampered ciphertext throws on decrypt.
  - generateMasterKey / importMasterKey / exportMasterKey for the
    Phase 2 server-side vault flow.
  - toBufferSource() helper works around the TS 5.7 Uint8Array generic
    parameterisation that broke the WebCrypto BufferSource overloads.

key-provider.ts — pluggable master-key source
  - KeyProvider interface (getKey, isUnlocked, onChange).
  - NullKeyProvider (default): always-locked, encryption call sites
    silently skip. Safe for the rollout window where individual tables
    are still flipping enabled:true.
  - MemoryKeyProvider: holds a CryptoKey in process memory only,
    notifies subscribers on lock/unlock transitions, sets a sentinel
    in sessionStorage so the UI can detect the unlock state on hard
    reload before the vault fetch completes.
  - setKeyProvider / getKeyProvider / getActiveKey / isVaultUnlocked
    are the boundary the rest of the data layer calls — no direct
    references to the concrete provider.

registry.ts — strict per-table allowlist
  - 30 tables registered, all enabled:false in Phase 1.
  - Field selection rule: encrypt user-typed text, transcripts, PII,
    free-form notes; leave IDs, timestamps, status flags, foreign
    keys, sort keys plaintext so the query/index/sync layer keeps
    working unchanged.
  - getEncryptedFields(table) returns null for the common (disabled)
    case so the Dexie hook hot-path stays allocation-free.
  - hasAnyEncryption() lets the boot path skip the vault fetch
    entirely while everything is still disabled.

index.ts — barrel export so consumers don't reach into sub-files.

aes.test.ts — 31 tests covering:
  - isEncrypted detection (string prefix, non-strings, wrong version)
  - wrap/unwrap roundtrip for string, empty string, unicode, object,
    array, number, boolean, 10KB blob, null, undefined, plaintext
    pass-through, null/undefined unwrap pass-through
  - IV uniqueness across repeated wraps of the same plaintext
  - Wrong-key rejection
  - Tampered-ciphertext rejection (auth tag mismatch)
  - Malformed-blob handling (missing iv/ct separator)
  - importMasterKey / exportMasterKey raw byte roundtrip
  - importMasterKey rejects non-32-byte input
  - KeyProvider lifecycle: NullKeyProvider default, MemoryKeyProvider
    set/get, listener fires only on transitions, dispose unsubscribes
  - Registry: returns null for unregistered/disabled tables, every
    entry has non-empty + duplicate-free fields list, hasAnyEncryption
    returns false in Phase 1

All tests pass against Node 20 native Web Crypto. No fake-indexeddb
needed — the foundation is pure functions over crypto.subtle.

Verified: 31/31 new tests + 291/291 full mana/web suite passing.

Phase 2: mana-auth server-side vault (encryption_vaults table, KEK
loading, GET /me/encryption-key endpoint).
Phase 3: wire MemoryKeyProvider to the vault fetch on login, flip
registry entries to enabled:true table by table, extend Dexie hooks
to call wrapValue/unwrapValue on configured fields.
Phase 4: settings UI (lock state, key rotation, recovery code opt-in).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 18:19:41 +02:00
..
api feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
calc/packages/shared chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
calendar chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
cards chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
chat chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
citycorners chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
contacts chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
context chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
docs chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
guides chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
inventar chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
mana feat(mana/web): encryption foundation — phase 1 (no-op) 2026-04-07 18:19:41 +02:00
manacore/apps/web/src/lib feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
manavoxel chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
matrix chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
memoro chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
moodlit feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
mukke feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
news chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
nutriphi chore(workspace): unify vitest to ^4.1.2 across all packages 2026-04-07 13:58:29 +02:00
photos chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
picture chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
planta chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
presi chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
questions feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
skilltree chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00
storage chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
times chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
todo chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
traces feat: rename ManaCore to Mana across entire codebase 2026-04-05 20:00:13 +02:00
uload chore: complete ManaCore → Mana rename (docs, go modules, plists, images) 2026-04-07 12:26:10 +02:00
zitare/packages/content chore: delete 25 web-archived directories, remove stale stubs, clean workspace config 2026-04-03 13:03:49 +02:00