Refactor: migrate inline middleware to dedicated classes (#123)

This commit is contained in:
Alexander Whitestone
2026-03-04 07:58:39 -05:00
committed by GitHub
parent eb2b34876b
commit 15c7ee5d1e
3 changed files with 52 additions and 21 deletions

View File

@@ -29,7 +29,7 @@ addopts =
--tb=short
--strict-markers
--disable-warnings
-n auto
# -n auto
# Coverage configuration
[coverage:run]

View File

@@ -4,6 +4,7 @@ Key improvements:
1. Background tasks use asyncio.create_task() to avoid blocking startup
2. Chat integrations start in background
3. All startup operations complete quickly
4. Security and logging handled by dedicated middleware
"""
import asyncio
@@ -40,6 +41,11 @@ from dashboard.routes.thinking import router as thinking_router
from dashboard.routes.calm import router as calm_router
from infrastructure.router.api import router as cascade_router
# Import dedicated middleware
from dashboard.middleware.csrf import CSRFMiddleware
from dashboard.middleware.request_logging import RequestLoggingMiddleware
from dashboard.middleware.security_headers import SecurityHeadersMiddleware
def _configure_logging() -> None:
"""Configure logging with console and optional rotating file handler."""
@@ -241,29 +247,20 @@ def _get_cors_origins() -> list[str]:
return origins
async def add_security_headers(request: Request, call_next):
"""Add security headers to all responses."""
response = await call_next(request)
response.headers["X-Frame-Options"] = "SAMEORIGIN"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' cdn.jsdelivr.net; "
"style-src 'self' 'unsafe-inline' fonts.googleapis.com cdn.jsdelivr.net; "
"font-src 'self' fonts.gstatic.com; "
"img-src 'self' data: https:; "
"connect-src 'self' ws: wss:; "
"frame-ancestors 'self'; "
"base-uri 'self'; "
"form-action 'self'"
)
return response
# Add dedicated middleware in correct order
# 1. Logging (outermost to capture everything)
app.add_middleware(RequestLoggingMiddleware, skip_paths=["/health"])
# 2. Security Headers
app.add_middleware(
SecurityHeadersMiddleware,
production=not settings.debug
)
app.middleware("http")(add_security_headers)
# 3. CSRF Protection
app.add_middleware(CSRFMiddleware)
# 4. Standard FastAPI middleware
app.add_middleware(
TrustedHostMiddleware,
allowed_hosts=["localhost", "127.0.0.1", "*.local", "testserver"],

View File

@@ -0,0 +1,34 @@
import pytest
from fastapi.testclient import TestClient
from dashboard.app import app
@pytest.fixture
def client():
return TestClient(app)
def test_security_headers_middleware_is_used(client):
"""Verify that SecurityHeadersMiddleware is used instead of the inline function."""
response = client.get("/")
# SecurityHeadersMiddleware sets X-Frame-Options to 'DENY' by default
# The inline function in app.py sets it to 'SAMEORIGIN'
assert response.headers["X-Frame-Options"] == "DENY"
# SecurityHeadersMiddleware also sets Permissions-Policy
assert "Permissions-Policy" in response.headers
def test_request_logging_middleware_is_used(client):
"""Verify that RequestLoggingMiddleware is used."""
response = client.get("/")
# RequestLoggingMiddleware adds X-Correlation-ID to the response
assert "X-Correlation-ID" in response.headers
def test_csrf_middleware_is_used(client):
"""Verify that CSRFMiddleware is used."""
# GET request should set a csrf_token cookie if not present
response = client.get("/")
assert "csrf_token" in response.cookies
# POST request without token should be blocked (403)
# Use a path that isn't likely to be exempt
response = client.post("/agents/create")
assert response.status_code == 403
assert response.json()["code"] == "CSRF_INVALID"