aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_daemon.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-13 03:56:30 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-13 03:56:30 +0200
commitf0248975908ad670fa8a820f865bf22ea8d0172d (patch)
treef4af64d36cacaccb4f6d13436e001aeb57e861e3 /packages/meshbay-node/tests/test_daemon.py
parent35130e5528a52161630fd1c93572e1b2b7cd911b (diff)
downloadmeshbay-f0248975908ad670fa8a820f865bf22ea8d0172d.tar.gz
feat: Phase 12 — P2P crypto material, password split, node Ed25519 auth
Baseline commit capturing in-progress Phase 12 work that was already present in the working tree (uncommitted) before the Phase 11.5 security remediation begins. Committed as-is, without review or modification, so that remediation changes arrive as a separable diff. Contents: BundleStore (P2P GEK + keypair bundles), password split (auth_key / bundle_key), node Ed25519 auth (POST /v1/nodes/auth, node-scoped JWT), GEK-HMAC handshake proof with DTLS channel binding, Ed25519 admin challenge-response, node local admin UI rewrite, browser key persistence. Not authored in this session — captured to establish a baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/tests/test_daemon.py')
-rw-r--r--packages/meshbay-node/tests/test_daemon.py44
1 files changed, 30 insertions, 14 deletions
diff --git a/packages/meshbay-node/tests/test_daemon.py b/packages/meshbay-node/tests/test_daemon.py
index faf12e3..1c5a07e 100644
--- a/packages/meshbay-node/tests/test_daemon.py
+++ b/packages/meshbay-node/tests/test_daemon.py
@@ -7,11 +7,13 @@ Hub interaction is mocked.
"""
import asyncio
+import base64
import os
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
+from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from unittest.mock import AsyncMock, MagicMock, patch
from meshbay_common.crypto import generate_gek
@@ -20,6 +22,20 @@ from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
+def _mock_keystore_keys(sk_ed):
+ """Create a mock keystore with real Ed25519 + X25519 key material."""
+ sk_x = X25519PrivateKey.generate()
+ pk_x_raw = sk_x.public_key().public_bytes(
+ serialization.Encoding.Raw, serialization.PublicFormat.Raw)
+
+ mock_keys = MagicMock()
+ mock_keys.sk_ed25519 = sk_ed
+ mock_keys.pk_ed25519_b64 = "test"
+ mock_keys.sk_x25519 = sk_x
+ mock_keys.pk_x25519_b64 = base64.b64encode(pk_x_raw).decode()
+ return mock_keys
+
+
@pytest.fixture
def sk_hub():
return Ed25519PrivateKey.generate()
@@ -48,7 +64,7 @@ def shared_dir(tmp_path):
@pytest.fixture
def node_config(tmp_path, shared_dir):
return Config(
- hub=HubConfig(url="http://localhost:9999", username="testuser", password="testpass"),
+ hub=HubConfig(url="http://localhost:9999", username="testuser"),
node=NodeConfig(port=29000, quic_port=29010, http_port=29001, ui_port=28000),
groups=[GroupConfig(
id="g" * 32,
@@ -70,10 +86,7 @@ async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem)
daemon = NodeDaemon(node_config)
sk_node = Ed25519PrivateKey.generate()
- mock_keys = MagicMock()
- mock_keys.sk_ed25519 = sk_node
- mock_keys.pk_ed25519_b64 = "test"
- mock_keys.pk_x25519_b64 = "test"
+ mock_keys = _mock_keystore_keys(sk_node)
mock_session = MagicMock()
mock_session.node_id = "node123"
@@ -85,7 +98,6 @@ async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem)
hub_instance = AsyncMock()
hub_instance.startup = AsyncMock(return_value=mock_session)
- hub_instance.fetch_gek = AsyncMock(return_value=gek)
hub_instance.maintain_ws = AsyncMock()
hub_instance.send_ws = AsyncMock()
hub_instance._ws = None
@@ -139,7 +151,7 @@ async def test_daemon_creates_chat_store(tmp_path, node_config, gek, hub_pk_pem)
async def test_daemon_no_groups_exits(tmp_path):
"""Daemon with no valid groups exits cleanly."""
config = Config(
- hub=HubConfig(url="http://localhost:9999", username="testuser", password="testpass"),
+ hub=HubConfig(url="http://localhost:9999", username="testuser"),
node=NodeConfig(),
groups=[GroupConfig(id="", name="empty", shared_dir="")],
keystore=KeystoreConfig(path=tmp_path / "keystore.enc"),
@@ -148,17 +160,22 @@ async def test_daemon_no_groups_exits(tmp_path):
daemon = NodeDaemon(config)
sk_node = Ed25519PrivateKey.generate()
- mock_keys = MagicMock()
- mock_keys.sk_ed25519 = sk_node
- mock_keys.pk_ed25519_b64 = "test"
+ mock_keys = _mock_keystore_keys(sk_node)
mock_session = MagicMock()
mock_session.node_id = "node123"
mock_session.user_id = "user123"
mock_session.hub_pk_pem = b"pem"
+ mock_server = AsyncMock()
+ mock_server.serve = AsyncMock()
+
with patch("meshbay_node.daemon.load_or_create_keystore", return_value=mock_keys), \
- patch("meshbay_node.daemon.HubClient") as MockHub:
+ patch("meshbay_node.daemon.HubClient") as MockHub, \
+ patch("meshbay_node.daemon.uvicorn") as mock_uvicorn:
+
+ mock_uvicorn.Config = MagicMock()
+ mock_uvicorn.Server = MagicMock(return_value=mock_server)
hub_instance = AsyncMock()
hub_instance.startup = AsyncMock(return_value=mock_session)
@@ -169,7 +186,6 @@ async def test_daemon_no_groups_exits(tmp_path):
await daemon.run()
- assert daemon._state["status"] == "starting"
assert len(daemon._chat_stores) == 0
@@ -177,7 +193,7 @@ async def test_daemon_no_groups_exits(tmp_path):
async def test_daemon_index_change_pushes_to_peers(tmp_path, shared_dir, gek, hub_pk_pem):
"""Index change callback pushes updated index to WebRTC peers."""
config = Config(
- hub=HubConfig(url="http://localhost:9999", username="testuser", password="testpass"),
+ hub=HubConfig(url="http://localhost:9999", username="testuser"),
node=NodeConfig(port=29000, quic_port=29010, http_port=29001, ui_port=28000),
groups=[GroupConfig(
id="a" * 32,
@@ -228,7 +244,7 @@ async def test_daemon_index_change_skips_other_group_peers(
):
"""Index change only pushes to peers in the same group."""
config = Config(
- hub=HubConfig(url="http://localhost:9999", username="testuser", password="testpass"),
+ hub=HubConfig(url="http://localhost:9999", username="testuser"),
node=NodeConfig(),
groups=[],
keystore=KeystoreConfig(path=tmp_path / "keystore.enc"),