From 6f68bb6c1970e26de1f66c073ea5d7fecd21840d Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 15 Oct 2026 00:56:04 +0200 Subject: feat(cli): invitations, join, decline, leave, devices, browser access join accepts an invitation or redeems a link pasted as sent, then has the node admit a key pair made here, with the invitation's code (a link's code goes only to the node it names; one for a node that is away is kept for the next connection). decline, leave and devices revoke ask first, and refuse without --yes where there is no terminal. devices approve countersigns the one waiting request a code matches, none when several do. browser-access is the application's setting, kept per machine and never enabled from the hub's mirror: on leaves a copy sealed under M on each node, off withdraws it, unset touches nothing. A key pair made for a first contact is now kept. Co-Authored-By: Claude Opus 5.5 --- .../meshbay-hub/tests/test_bundle_kdf_parity.py | 24 +++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'packages/meshbay-hub/tests') diff --git a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py index 358d6ce..bad698e 100644 --- a/packages/meshbay-hub/tests/test_bundle_kdf_parity.py +++ b/packages/meshbay-hub/tests/test_bundle_kdf_parity.py @@ -157,8 +157,11 @@ const fp = async (key) => Buffer.from(await webcrypto.subtle.encrypt( const bundle = await K.encryptBundle( Buffer.from('ed-private'), Buffer.from('x-private'), kNode, { userId: v.user_id, nodePk: v.node_pk, pepperVersion: 1 }); + // A bundle the command-line client sealed, opened the way the page opens one. + const cliOpened = v.cli_bundle ? (await K.decryptBundle( + v.cli_bundle, kNode, { userId: v.user_id, nodePk: v.node_pk })).skEd : null; out.push({ node: await fp(kNode), playlists: await fp(await derivePlaylistKey(sk.v3)), - bundle, auth: await K.deriveAuthKey(v.password, v.username) }); + bundle, auth: await K.deriveAuthKey(v.password, v.username), cliOpened }); } process.stdout.write(JSON.stringify(out)); })().catch((e) => { console.error(e); process.exit(1); }); @@ -189,6 +192,16 @@ def chain(tmp_path_factory): d = tmp_path_factory.mktemp("chain") vectors = [{**v, "pepper": base64.b64encode(bytes([i + 1]) * 32).decode()} for i, v in enumerate(CHAIN)] + try: + from meshbay_cli.client import keys as cli_keys + except ImportError: + cli_keys = None + for v in vectors if cli_keys else []: + m = cli_keys.bundle_master_key(cli_keys.passphrase_half(v["username"], v["password"]), + base64.b64decode(v["pepper"]), v["user_id"]) + v["cli_bundle"] = cli_keys.seal_bundle( + {"skEd": base64.b64encode(b"cli-ed").decode(), "skX": "eA=="}, master=m, + pepper_version=1, user_id=v["user_id"], node_pk=v["node_pk"]) (d / "harness.cjs").write_text(_CHAIN_HARNESS, encoding="utf-8") (d / "vectors.json").write_text(json.dumps(vectors), encoding="utf-8") proc = subprocess.run( @@ -265,3 +278,12 @@ def test_the_cli_opens_a_bundle_the_page_sealed(chain): with pytest.raises(cli_keys.BundleError): cli_keys.open_bundle(got["bundle"], master=m, legacy=None, user_id="someone-else", node_pk=v["node_pk"]) + + +def test_the_page_opens_a_bundle_the_cli_sealed(chain): + """`browser-access on` in the CLI leaves bundles a browser has to open.""" + pytest.importorskip("meshbay_cli.client.keys") + import base64 + _, js = chain + for got in js: + assert base64.b64decode(got["cliOpened"]) == b"cli-ed" -- cgit v1.2.3