diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-11 23:11:36 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-11 23:11:36 +0200 |
| commit | 35130e5528a52161630fd1c93572e1b2b7cd911b (patch) | |
| tree | 50954e2da56e186e2651ad838093eb6c09c259a8 /packages/meshbay-node/tests/test_audit.py | |
| parent | c66ee41d8476461939c5f4e7fdc71c5d7fb4a85c (diff) | |
| download | meshbay-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.py | 76 |
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 |