aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_audit.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-11 23:11:36 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-11 23:11:36 +0200
commit35130e5528a52161630fd1c93572e1b2b7cd911b (patch)
tree50954e2da56e186e2651ad838093eb6c09c259a8 /packages/meshbay-node/tests/test_audit.py
parentc66ee41d8476461939c5f4e7fdc71c5d7fb4a85c (diff)
downloadmeshbay-35130e5528a52161630fd1c93572e1b2b7cd911b.tar.gz
feat(node): audit logging + local admin UI rewrite
Add SQLite audit store for legal compliance (LCEN/DSA): logs user IP, actions (handshake, file download/upload/delete, stream, chat), and timestamps. Retention: 1 year, with cleanup method. WebRTC transport now logs all user actions to the audit store with remote IP extraction from the ICE transport. Local web UI rewritten as a proper admin dashboard: - Stats cards (groups, files, peers) - Connected peers table with IP, username, group, state - Group cards with file listings and shared directory info - Audit log page with event/user filtering - Dark theme, responsive, auto-refresh - JSON API: /api/status, /api/groups, /api/peers, /api/audit, /api/config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/tests/test_audit.py')
-rw-r--r--packages/meshbay-node/tests/test_audit.py76
1 files changed, 76 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_audit.py b/packages/meshbay-node/tests/test_audit.py
new file mode 100644
index 0000000..b2ed15e
--- /dev/null
+++ b/packages/meshbay-node/tests/test_audit.py
@@ -0,0 +1,76 @@
+"""Tests for the node audit store (legal compliance IP/action logging)."""
+
+import time
+
+import pytest
+
+from meshbay_node.audit import AuditStore
+
+
+@pytest.fixture
+async def audit(tmp_path):
+ store = AuditStore(db_path=tmp_path / "audit.db")
+ await store.open()
+ yield store
+ await store.close()
+
+
+@pytest.mark.asyncio
+async def test_log_and_retrieve(audit):
+ await audit.log_event(
+ user_id="u1", event="handshake", ip="1.2.3.4",
+ username="alice", group_id="g1", detail="test")
+ await audit.log_event(
+ user_id="u2", event="file_download", ip="5.6.7.8",
+ username="bob", group_id="g1", detail="video.mp4")
+
+ entries = await audit.get_entries()
+ assert len(entries) == 2
+ assert entries[0].event == "file_download"
+ assert entries[0].ip == "5.6.7.8"
+ assert entries[1].event == "handshake"
+
+
+@pytest.mark.asyncio
+async def test_filter_by_event(audit):
+ await audit.log_event(user_id="u1", event="handshake", ip="1.1.1.1")
+ await audit.log_event(user_id="u1", event="file_download", ip="1.1.1.1")
+ await audit.log_event(user_id="u1", event="handshake", ip="1.1.1.1")
+
+ entries = await audit.get_entries(event="handshake")
+ assert len(entries) == 2
+ assert all(e.event == "handshake" for e in entries)
+
+
+@pytest.mark.asyncio
+async def test_filter_by_user(audit):
+ await audit.log_event(user_id="u1", event="handshake")
+ await audit.log_event(user_id="u2", event="handshake")
+
+ entries = await audit.get_entries(user_id="u1")
+ assert len(entries) == 1
+ assert entries[0].user_id == "u1"
+
+
+@pytest.mark.asyncio
+async def test_entry_count(audit):
+ assert await audit.entry_count() == 0
+ await audit.log_event(user_id="u1", event="connect")
+ await audit.log_event(user_id="u2", event="connect")
+ assert await audit.entry_count() == 2
+
+
+@pytest.mark.asyncio
+async def test_cleanup_old_entries(audit):
+ await audit.log_event(user_id="u1", event="old")
+ # manually backdate
+ await audit._db.execute(
+ "UPDATE audit_log SET timestamp = ? WHERE event = 'old'",
+ (time.time() - 400 * 86400,))
+ await audit._db.commit()
+
+ await audit.log_event(user_id="u2", event="recent")
+
+ deleted = await audit.cleanup(retention_days=365)
+ assert deleted == 1
+ assert await audit.entry_count() == 1