From abeaec39786623b006aaccb949711deaee4b44d0 Mon Sep 17 00:00:00 2001 From: Michael Yankelev Date: Tue, 11 Aug 2026 23:16:36 +0200 Subject: [PATCH] docs: state device approval as an API rendezvous in the blueprint Three files described MFA and device approval as chrome-side Core Kit UX. That framing concealed an API slice: the Core Kit has no native cross-device share transfer, so approval is a server-mediated rendezvous, and v1 built an API module with its own table to provide one. Records the rendezvous beside the other API surfaces, the client's part in it, the recovery phrase as the guaranteed path on every host, and the fact that the Core Kit staging-dispatch exemption does not extend to a flow over our own API. 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. Records FSM1/cipher-box-next ADR 0009. Entire-Checkpoint: c08842ecedbf --- blueprint/api.md | 9 ++++++++- blueprint/desktop.md | 10 ++++++++-- blueprint/testing.md | 8 ++++++-- blueprint/web-client.md | 26 ++++++++++++++++---------- 4 files changed, 38 insertions(+), 15 deletions(-) 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