feat(backup): client-driven v2 snapshot export, drop server-side backup

Replaces the mana-sync event-stream export (GET /backup/export) with a
fully client-driven `.mana` v2 archive: webapp reads Dexie, decrypts
per-field, packages JSONL + manifest, optionally PBKDF2+AES-GCM seals
with a passphrase.

- New: backup/v2/{format,passphrase,export,import}.ts + format.test.ts
  (10 tests: round-trip, sealed path, 3 failure modes incl. wrong-
  passphrase vs. tamper distinction).
- UI: ExportImportPanel with module multi-select, optional passphrase,
  progress + sealed-file detection — replaces the old backup flow in
  Settings → MyData.
- Removes services/mana-sync/internal/backup/ and the corresponding
  client helpers + v1 tests. No parallel paths, no legacy shim.
- Why client-driven: zero-knowledge users hold their vault key only
  client-side, so a server exporter cannot produce plaintext archives;
  GDPR Art. 20 portability is better served by plaintext-by-default.
- Cross-account restore works via re-encryption under the target
  vault key (no MK transfer needed).

DATA_LAYER_AUDIT.md §8 rewritten to reflect the new architecture.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-22 18:46:29 +02:00
parent 3a7bc7f1c3
commit fd1ea47075
18 changed files with 2145 additions and 1530 deletions

View file

@ -13,7 +13,6 @@ import (
"time"
"github.com/mana/mana-sync/internal/auth"
"github.com/mana/mana-sync/internal/backup"
"github.com/mana/mana-sync/internal/billing"
"github.com/mana/mana-sync/internal/config"
"github.com/mana/mana-sync/internal/memberships"
@ -73,11 +72,12 @@ func main() {
mux.Handle("GET /sync/{appId}/pull", billingMiddleware(http.HandlerFunc(handler.HandlePull)))
mux.Handle("GET /sync/{appId}/stream", billingMiddleware(http.HandlerFunc(handler.HandleStream)))
// Backup/export — GDPR-grade, auth-only (no billing gate so users can
// always retrieve their data). M1 thin slice: streams raw sync_changes
// as JSONL. Manifest + zip container land in M3.
backupHandler := backup.NewHandler(db, validator)
mux.Handle("GET /backup/export", http.HandlerFunc(backupHandler.HandleExport))
// Backup/export — removed 2026-04-22 (data-export-v2 rollout).
// Data export is now fully client-driven (apps/mana/apps/web/src/lib/
// data/backup/v2/): client reads local Dexie, decrypts per-field,
// optionally passphrase-seals, downloads. Server would need the user's
// vault key to produce plaintext exports — which is a key it
// deliberately never sees.
// WebSocket endpoints
// Unified: one connection per user, receives all app notifications with appId in payload