diff --git a/blueprint/api.md b/blueprint/api.md index 3a0e7c59a..6e85d43a4 100644 --- a/blueprint/api.md +++ b/blueprint/api.md @@ -52,8 +52,15 @@ What left the API relative to v1 — with the design that removed it: client ID. The two are not interchangeable. - Short-lived access JWT + rotating refresh token (HTTP-only cookie on web, OS keychain on desktop). Staging-gated test-login endpoint for e2e. +- **Device-approval rendezvous** ([ADR 0009](https://github.com/FSM1/cipher-box-next/blob/main/decisions/0009-device-approval-is-a-bound-rendezvous.md)): request, poll, respond, cancel, and a + pending list, under a **scoped, non-refreshable** pre-reconstruction token — a + device that cannot yet reconstruct its key reaches these routes and nothing else. + The API is a bulletin board: it relays ciphertext sealed to the requester's + ephemeral key and never holds plaintext key material. Both halves of the exchange + carry a device-key signature, and a row's life ends at collection or expiry, + whichever comes first. - Tables: `users` (keyed by `publicKey`; carries quota-limit override and BYO flag), - `auth_methods`, `refresh_tokens`. + `auth_methods`, `refresh_tokens`, `device_approvals`. ## Pin/name registry diff --git a/blueprint/desktop.md b/blueprint/desktop.md index de36b8139..9c4b6c025 100644 --- a/blueprint/desktop.md +++ b/blueprint/desktop.md @@ -243,8 +243,8 @@ navigation (FSM1/cipher-box-next#33 D2): ## Tauri shell -- **Login**: Web3Auth Core Kit in the webview (its DOM/redirect flows, MFA, - device approval — all chrome-side); the exported secret crosses to +- **Login**: Web3Auth Core Kit in the webview (its DOM/redirect flows and MFA + enrollment — chrome-side); the exported secret crosses to `start(secret)` once and is zeroized. Challenge-signature login, token refresh, and session restore are engine-native (engine.md API client); the shell's only credential duty is hosting the keychain seam. Dev-key mode @@ -262,6 +262,12 @@ navigation (FSM1/cipher-box-next#33 D2): is genuinely native (ADR 0008). - **No wallet method here.** The webview reaches no wallet, so the method is absent rather than offered and unable to complete (ADR 0008 D2). +- **Recovery phrase always works here** ([ADR 0009](https://github.com/FSM1/cipher-box-next/blob/main/decisions/0009-device-approval-is-a-bound-rendezvous.md) D2): a member with a factor + policy signs in on this host with the phrase alone, no second device and no + rendezvous. Whether the shell also participates in device approval is a scope + decision that must be made rather than left open — v1 shipped a requester UI + that could never work beside a settings string saying MFA was web-only, and + the affordance and the truth have to agree. - **Tray** renders the event stream: the staleness ladder maps to `Synced / Reconciling / Stale / Offline`, dead-letters to the parked-writes state (edge-triggered notifications, v1's anti-spam watermark kept), trust diff --git a/blueprint/testing.md b/blueprint/testing.md index d9ba4b75c..489c1b47c 100644 --- a/blueprint/testing.md +++ b/blueprint/testing.md @@ -315,8 +315,12 @@ percentage never did. - **Measured constants** — kernel TTLs, migration window, sweep cadence, chunk size/DAG shape: process fixed above, values land during build. - **Web3Auth Core Kit interactive login** — wallet-mock covers SIWE in CI; - real Core Kit login (MFA, device approval) stays a staging-dispatch job - with test credentials, never a PR gate — an honest, inherited limitation. + real Core Kit login and MFA enrollment stay a staging-dispatch job with test + credentials, never a PR gate — an honest, inherited limitation. + **Device approval is not covered by that exemption** ([ADR 0009](https://github.com/FSM1/cipher-box-next/blob/main/decisions/0009-device-approval-is-a-bound-rendezvous.md)): it is a + rendezvous over our own API, and it needs a harness driving two sessions. v1 + skipped every cross-device case for want of a second device, which is how a + desktop path that could never succeed reached a verified status. - **Runner provisioning, staging deploy gates, release-tag e2e gating, nightly scheduling** → [deployment blueprint (FSM1/cipher-box-next#48)](https://github.com/FSM1/cipher-box-next/issues/48). diff --git a/blueprint/web-client.md b/blueprint/web-client.md index 6450aae32..05f559824 100644 --- a/blueprint/web-client.md +++ b/blueprint/web-client.md @@ -189,8 +189,8 @@ all living in `packages/client` and running inside the engine worker realm: ## Login and identity - **Web3Auth Core Kit runs on the UI thread** (it owns its DOM/redirect - flows, MFA enrollment, and device approval — all UI-side, outside the - engine's sight). Login or session restore exports the login secret; the + flows and MFA enrollment — UI-side, outside the engine's sight). Login or + session restore exports the login secret; the client passes it through the facade to the engine **once**, as a transferred buffer, and zeroes its own copy. Everything derives in-engine via the KDF catalog: identity key, encryption subkey, pointer chain, vault @@ -209,6 +209,12 @@ all living in `packages/client` and running inside the engine worker realm: - **Wallet is a first login here** (ADR 0008 D2): wagmi collects the signature on the UI thread, the API verifies it and mints the identity token, and the Core Kit login proceeds as for any other method. Web only. +- **Device approval is not Core Kit UX** ([ADR 0009](https://github.com/FSM1/cipher-box-next/blob/main/decisions/0009-device-approval-is-a-bound-rendezvous.md)). The Core Kit has no native + cross-device share transfer, so approval is a server-mediated rendezvous with + its own API surface (api.md) — the client's part is minting the ephemeral key, + displaying the comparison value both devices must match, signing both halves + with the device identity key, and sealing a **fresh** factor to the requester. + The recovery phrase is the guaranteed path and needs none of this. - **Cold start**: facade `start(secret)` → vault-pointer resolve, floor cold-seed, root adoption (the engine's non-circular cold-start sequence) → first snapshot event. The UI shows exactly two cold-start states: an @@ -251,14 +257,14 @@ all living in `packages/client` and running inside the engine worker realm: ## Composition (apps/web) -| Route | View | -| ----------------- | ---------------------------------------------------------------------------------------------------------------- | -| `/` | Login (Core Kit methods + SIWE), recovery/approval UI | -| `/files/:nodeId?` | Vault browser (absent id = current root) | -| `/shared` | Received shares; browsing shared scopes is the same browser over the same snapshot | -| `/bin` | Recycle bin (kept per FSM1/cipher-box-next#5), restore/purge ops via facade | -| `/settings` | Auth methods, MFA/devices (Core Kit UX), BYO pinning (sealed `ByoIpfsConfig` via facade), vault settings, export | -| `/invite/:…` | Invite claim — fragment secret handed to the facade unread | +| Route | View | +| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `/` | Login (Core Kit methods + SIWE), recovery/approval UI | +| `/files/:nodeId?` | Vault browser (absent id = current root) | +| `/shared` | Received shares; browsing shared scopes is the same browser over the same snapshot | +| `/bin` | Recycle bin (kept per FSM1/cipher-box-next#5), restore/purge ops via facade | +| `/settings` | Auth methods, MFA enrollment and recovery phrase (Core Kit UX), authorized devices and approval (ADR 0009), BYO pinning (sealed `ByoIpfsConfig` via facade), vault settings, export | +| `/invite/:…` | Invite claim — fragment secret handed to the facade unread | Cross-cutting chrome renders event-stream state only: sync/staleness indicator, quota (advisory-aware for BYO), dead-letter and escalation