aboutsummaryrefslogtreecommitdiffstats
path: root/docs/desktop-client-v1.md
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-18 11:00:32 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-18 11:00:32 +0200
commit7c46b4e7dc2893974a37d6a701123a95803fcb95 (patch)
treea919618ce1c105e36c1fc85f7e492fbcb3265779 /docs/desktop-client-v1.md
parent68bfe56a19aeb4c16f8e185fc85d8eee61aef78f (diff)
downloadmeshbay-7c46b4e7dc2893974a37d6a701123a95803fcb95.tar.gz
feat(client): hybrid sign-in — passphrase once, then this device's key
D4. The passphrase stays the account's credential and its only recovery path; what changes is that it is not asked for on every launch. **The renderer never holds the device key.** It is generated, stored and used entirely in the main process, which signs `meshbay:user_auth:<username>:<ts>` on request. Same rule as the save dialog, for the same reason: the renderer is the part of this application that parses decrypted content from nodes, which is attacker-controlled input. And this key is *not* a per-node identity key — those are generated per node and never leave that relationship, so nothing here correlates a person across operators. First run asks which hub, with no default. A client that picks its own hub is a client that can be pointed at one, and the address is the whole of what this application trusts a hub for — the interface comes from the package. Verified against a hub running this code, not against the deployed one: register 201 → passphrase login 200 → device register 201 → **device sign-in 200 with a real session** → `/v1/users/me` 200 → a stranger's key 401. The signature was also checked directly against the hub's own Python verifier before any of that. Inside the running application, over the debugging protocol: the bridge reaches the main process, the renderer calls the hub **through it** (200 — the CORS fix working end to end), and a call to a host that is not the configured hub is refused. **Not verified:** safeStorage persisting the key. This session has no secret service, and standing one up in xvfb did not succeed. The application behaves correctly there — it *refuses* rather than storing unprotected, and now says so in Settings, which is a real case rather than a hypothetical one since it is exactly what a headless or minimal desktop looks like. Worth remembering for next time: meshbay.org runs whatever was last deployed. It answered 405 on the Stage-C endpoints and reported MNP 0.2 while the tree had 0.3, so a local `uvicorn meshbay_hub.app:create_app --factory` on SQLite is what tests hub changes. Nothing was deployed to production for this. 799 tests pass; e2e.py passes end to end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'docs/desktop-client-v1.md')
-rw-r--r--docs/desktop-client-v1.md2
1 files changed, 1 insertions, 1 deletions
diff --git a/docs/desktop-client-v1.md b/docs/desktop-client-v1.md
index c4b5380..fedfc8d 100644
--- a/docs/desktop-client-v1.md
+++ b/docs/desktop-client-v1.md
@@ -1058,7 +1058,7 @@ build existing.
| D1 | ✅ **DONE 2026-08-18** — `static/platform.js`; `HUB` is `platform.hubBase()` and the transport is built with the same base. Browser behaviour identical, which was the acceptance criterion | 1 |
| D2 | ✅ **RUNS** (2026-08-18, Electron 42 / Chromium 148 under xvfb). The packaged interface mounts over `app://`, secure context, `crypto.subtle` present, Argon2 WASM loaded, zero console errors. Three things were learned by running it — see §3.1 |
| D3 | ◐ **PARTIAL** — the bridge (`secrets.get/set/clear/backend`) and the honest report of what the OS is actually doing: `unprotected_fallback` when safeStorage finds no keyring, surfaced in Settings rather than swallowed. The native key *lifecycle* belongs with D4 and needs a running application to mean anything | 1 |
-| D4 | Hybrid registration and first sign-in (§5) | 1 |
+| D4 | ✅ **DONE 2026-08-18, verified against a hub running this code** — first-run hub prompt (no default, on purpose), passphrase sign-in registers this device, later launches sign in with the device key and no passphrase. **The renderer never holds that key**: it is generated, stored and used entirely in the main process, which signs on request — the same rule as the save dialog, because the renderer is the part that parses hostile input. Measured: register 201 → passphrase login 200 → device register 201 → **device sign-in 200 with a real session** → a stranger's key 401. **Not verified:** safeStorage actually persisting the key, which needs a desktop with a keyring — this session has none, and the application correctly *refuses* rather than storing unprotected | 1 |
| D5 | Node management panel over the Stage-B ops, root selection included | 2 |
| D6 | First-run wizard — detect, enable the unit, link, group, `gek-init`, pair (§7.4) | 2 |
| D7 | Native save dialog, streamed write from the main process | 2 |