Architecture
Overview
Section titled “Overview”HandoverKey is a Turbo monorepo with two deployable apps and three shared packages.
apps/ api/ Express 5 REST API web/ React 19 SPA docs/ Starlight docs site (this site)packages/ crypto/ AES-256-GCM, PBKDF2, Shamir's Secret Sharing database/ Kysely client, repository layer, schema types shared/ Cross-package types, constants, validation utilitiesRuntime topology
Section titled “Runtime topology”graph TD Browser["React SPA<br/>(apps/web)"] -->|"HTTPS + httpOnly cookies"| API["Express API<br/>(apps/api)"] Browser -->|WSS /ws| WS["Realtime service"] API --> WS API -->|SQL via Kysely| DB[("PostgreSQL")] API -->|"Queues / sessions / lockout"| Redis[("Redis")] API -->|Transactional email| SMTP["SMTP provider"] API --> Crypto["packages/crypto"] API --> Database["packages/database"] API --> Shared["packages/shared"] Browser --> SharedPackage dependency direction
Section titled “Package dependency direction”graph LR api["apps/api"] --> database["packages/database"] api --> crypto["packages/crypto"] api --> shared["packages/shared"] web["apps/web"] --> shared shared --> cryptoCircular dependencies are intentionally avoided.
Core data flows
Section titled “Core data flows”Encrypted vault
Section titled “Encrypted vault”- The browser derives a master key from the user’s password via PBKDF2 (100,000 iterations, SHA-256).
- Each vault entry is encrypted with AES-256-GCM entirely in the browser before being sent.
- The API stores encrypted blobs, IVs, salts, and metadata — never plaintext.
- On read, the browser decrypts locally with the in-memory master key.
Authentication
Section titled “Authentication”- Browser derives an auth key from password + email via PBKDF2 — the raw password never leaves the device.
- Server stores bcrypt hash of the auth key (rounds = 12).
- Login sets
accessToken(1h) andrefreshToken(7d) as httpOnly cookies. - Every protected route validates both the JWT and the backing server-side session record.
- Optional TOTP 2FA adds a second factor.
Inactivity and handover state machine
Section titled “Inactivity and handover state machine”flowchart TD Active["Active\n(user is checking in)"] Reminders["Reminders\n(75% / 85% / 95%)"] Grace["Grace period\n(48 hours)"] Awaiting["Awaiting successors"] Completed["Completed"] Cancelled["Cancelled"]
Active -->|"inactivity reaches 75%"| Reminders Reminders -->|"user checks in"| Active Reminders -->|"100% reached"| Grace Grace -->|"user checks in or logs in"| Cancelled Grace -->|"48 hours elapse"| Awaiting Awaiting -->|"all successors respond"| Completed Cancelled -->|"timer resets"| ActiveShamir’s Secret Sharing
Section titled “Shamir’s Secret Sharing”The master encryption key is split into N shares with threshold K. Any K shares reconstruct the key; fewer than K reveals nothing.
requireMajority |
Threshold K |
|---|---|
| off | min(2, N) |
| on | floor(N / 2) + 1 |
Reconstruction happens entirely in the successor’s browser — the server never handles plaintext.
Persistence
Section titled “Persistence”PostgreSQL
Section titled “PostgreSQL”Stores: users, sessions, vault entries, successors, inactivity settings, activity logs, notification deliveries, handover processes.
Used for: BullMQ job queues, login throttle counters, low-latency operational state.
Observability
Section titled “Observability”| Endpoint | Output |
|---|---|
GET /health |
JSON: DB, Redis, queues, realtime status |
GET /metrics |
Prometheus-compatible metrics |
Structured logging via Pino throughout the API. Activity records are HMAC-signed for integrity.