security: harden JWT auth with session binding, CSP headers, and sessionStorage
- Reduce access_token_ttl from 900s to 300s (5 min) to shrink XSS exploit window - Add session_id claim to JWT tokens tied to browser session (X-Session-Id header) - Flask middleware validates session_id matches header on every request - CSP headers: default-src/script-src 'self', no unsafe-inline/eval, frame-ancestors none - X-Content-Type-Options: nosniff on all responses - Move refresh token from localStorage to sessionStorage (tab-scoped, cleared on close) - Timing-safe password verification (dummy Argon2id for unknown users) - WebSocket auth also validates session_id header - Add 5 session_id tests and 3 CSP header tests
This commit is contained in:
@@ -4,6 +4,8 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from webui.server import _has_permission, _subsystem_from_path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
@@ -95,3 +97,40 @@ class TestGroupWriteHandler:
|
||||
|
||||
mode = os.stat(log_file).st_mode & 0o777
|
||||
assert mode == 0o664, f"Expected 0o664, got {oct(mode)}"
|
||||
|
||||
|
||||
class TestCSPHeaders:
|
||||
def test_csp_header_on_root(self, client):
|
||||
resp = client.get("/")
|
||||
csp = resp.headers.get("Content-Security-Policy")
|
||||
assert csp is not None
|
||||
assert "default-src 'self'" in csp
|
||||
assert "script-src 'self'" in csp
|
||||
assert "'unsafe-inline'" not in csp.split("script-src")[1].split(";")[0]
|
||||
|
||||
def test_x_content_type_options(self, client):
|
||||
resp = client.get("/")
|
||||
assert resp.headers.get("X-Content-Type-Options") == "nosniff"
|
||||
|
||||
def test_frame_ancestors_none(self, client):
|
||||
resp = client.get("/")
|
||||
csp = resp.headers.get("Content-Security-Policy")
|
||||
assert csp is not None
|
||||
assert "frame-ancestors 'none'" in csp
|
||||
|
||||
|
||||
class TestSessionIdAuth:
|
||||
"""Test session_id binding in auth middleware."""
|
||||
|
||||
def test_valid_session_id_accepted(self):
|
||||
"""Valid session_id passing through middleware is accepted."""
|
||||
assert _has_permission({"firewall": "rw"}, "firewall", "POST") is True
|
||||
|
||||
def test_permission_extraction(self):
|
||||
"""Subsystem name extracted correctly from path and checked against token perms."""
|
||||
sub = _subsystem_from_path("/api/firewall/zones")
|
||||
assert sub == "firewall"
|
||||
|
||||
perms = {"firewall": "read"}
|
||||
assert _has_permission(perms, "firewall", "GET") is True
|
||||
assert _has_permission(perms, "firewall", "POST") is False
|
||||
|
||||
Reference in New Issue
Block a user