Tighten auth helper origin checks

This commit is contained in:
Jage9
2026-03-08 21:58:19 -04:00
parent 0fc6018ca8
commit ba34ce4e9b
5 changed files with 50 additions and 8 deletions

View File

@@ -288,6 +288,20 @@ class SignalingServer:
secure = "; Secure" if self._session_cookie_secure(request) else ""
return f"{AUTH_SESSION_COOKIE_NAME}=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0{secure}"
def _origin_allowed(self, request: HttpRequest) -> bool:
"""Return whether one auth helper HTTP request comes from the configured app origin."""
if not self.host_origin:
return False
raw_origin = str(request.headers.get("Origin", "")).strip()
if not raw_origin:
return False
try:
origin = normalize_origin(raw_origin)
except ValueError:
return False
return origin == self.host_origin
@staticmethod
def _cookie_value(cookie_header: str, name: str) -> str:
"""Extract one cookie value by name from a Cookie header."""
@@ -311,6 +325,8 @@ class SignalingServer:
client_header = str(request.headers.get(AUTH_SESSION_COOKIE_CLIENT_HEADER, "")).strip()
if client_header != "1":
return HttpResponse(400, "Bad Request", headers, b"missing client header")
if not self._origin_allowed(request):
return HttpResponse(403, "Forbidden", headers, b"origin not allowed")
if path == AUTH_SESSION_COOKIE_CHECK_PATH:
cookie_header = str(request.headers.get("Cookie", "")).strip()

View File

@@ -26,7 +26,7 @@ def _request(path: str, headers: dict[str, str] | None = None) -> Request:
@pytest.mark.asyncio
async def test_session_cookie_set_endpoint_sets_httponly_cookie() -> None:
server = SignalingServer("127.0.0.1", 8765, None, None)
server = SignalingServer("127.0.0.1", 8765, None, None, host_origin="https://example.com")
username = f"user_{uuid.uuid4().hex[:8]}"
session = server.auth_service.register(username, "password99")
request = _request(
@@ -34,6 +34,7 @@ async def test_session_cookie_set_endpoint_sets_httponly_cookie() -> None:
headers={
AUTH_SESSION_COOKIE_CLIENT_HEADER: "1",
"Authorization": f"Bearer {session.token}",
"Origin": "https://example.com",
},
)
@@ -49,8 +50,11 @@ async def test_session_cookie_set_endpoint_sets_httponly_cookie() -> None:
@pytest.mark.asyncio
async def test_session_cookie_clear_endpoint_expires_cookie() -> None:
server = SignalingServer("127.0.0.1", 8765, None, None)
request = _request(AUTH_SESSION_COOKIE_CLEAR_PATH, headers={AUTH_SESSION_COOKIE_CLIENT_HEADER: "1"})
server = SignalingServer("127.0.0.1", 8765, None, None, host_origin="https://example.com")
request = _request(
AUTH_SESSION_COOKIE_CLEAR_PATH,
headers={AUTH_SESSION_COOKIE_CLIENT_HEADER: "1", "Origin": "https://example.com"},
)
response = await server._process_http_request(SimpleNamespace(), request)
@@ -64,7 +68,7 @@ async def test_session_cookie_clear_endpoint_expires_cookie() -> None:
@pytest.mark.asyncio
async def test_session_cookie_check_endpoint_accepts_valid_cookie() -> None:
server = SignalingServer("127.0.0.1", 8765, None, None)
server = SignalingServer("127.0.0.1", 8765, None, None, host_origin="https://example.com")
username = f"user_{uuid.uuid4().hex[:8]}"
session = server.auth_service.register(username, "password99")
request = _request(
@@ -72,6 +76,7 @@ async def test_session_cookie_check_endpoint_accepts_valid_cookie() -> None:
headers={
AUTH_SESSION_COOKIE_CLIENT_HEADER: "1",
"Cookie": f"{AUTH_SESSION_COOKIE_NAME}={session.token}",
"Origin": "https://example.com",
},
)
@@ -83,8 +88,11 @@ async def test_session_cookie_check_endpoint_accepts_valid_cookie() -> None:
@pytest.mark.asyncio
async def test_session_cookie_check_endpoint_rejects_missing_cookie() -> None:
server = SignalingServer("127.0.0.1", 8765, None, None)
request = _request(AUTH_SESSION_COOKIE_CHECK_PATH, headers={AUTH_SESSION_COOKIE_CLIENT_HEADER: "1"})
server = SignalingServer("127.0.0.1", 8765, None, None, host_origin="https://example.com")
request = _request(
AUTH_SESSION_COOKIE_CHECK_PATH,
headers={AUTH_SESSION_COOKIE_CLIENT_HEADER: "1", "Origin": "https://example.com"},
)
response = await server._process_http_request(SimpleNamespace(), request)
@@ -92,6 +100,20 @@ async def test_session_cookie_check_endpoint_rejects_missing_cookie() -> None:
assert response.status_code == 401
@pytest.mark.asyncio
async def test_session_cookie_helpers_reject_wrong_origin() -> None:
server = SignalingServer("127.0.0.1", 8765, None, None, host_origin="https://example.com")
request = _request(
AUTH_SESSION_COOKIE_CLEAR_PATH,
headers={AUTH_SESSION_COOKIE_CLIENT_HEADER: "1", "Origin": "https://evil.example.com"},
)
response = await server._process_http_request(SimpleNamespace(), request)
assert response is not None
assert response.status_code == 403
def test_session_token_from_websocket_cookie_reads_named_cookie() -> None:
server = SignalingServer("127.0.0.1", 8765, None, None)
websocket = SimpleNamespace(