diff --git a/backend/app/main.py b/backend/app/main.py index ae5788af..5dd3f06a 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -132,7 +132,7 @@ async def lifespan(_: FastAPI) -> AsyncIterator[None]: # actually need cookie-based auth. allow_credentials=False, # Explicit allowlist (avoid "*") so CORS behavior is reviewable. - allow_methods=["GET", "POST", "OPTIONS"], + allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=CORS_ALLOW_HEADERS, ) diff --git a/backend/tests/test_cors_methods.py b/backend/tests/test_cors_methods.py new file mode 100644 index 00000000..645e4232 --- /dev/null +++ b/backend/tests/test_cors_methods.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from fastapi.testclient import TestClient + +from app.main import app + + +API_CORS_METHODS = ("GET", "POST", "PUT", "DELETE", "OPTIONS") +UNSUPPORTED_API_CORS_METHOD = "PATCH" + + +def test_cors_preflight_allows_every_supported_api_method() -> None: + """Production CORS preflight must accept every HTTP method exposed by the API.""" + + client = TestClient(app) + + for method in API_CORS_METHODS: + response = client.options( + "/api/projects", + headers={ + "Origin": "http://localhost:5173", + "Access-Control-Request-Method": method, + "Access-Control-Request-Headers": "Content-Type", + }, + ) + + assert response.status_code in (200, 204) + allowed_methods = { + item.strip() + for item in response.headers["Access-Control-Allow-Methods"].split(",") + } + assert method in allowed_methods + + +def test_cors_preflight_rejects_unexposed_api_method() -> None: + """Production CORS must reject methods that have no registered API route.""" + + client = TestClient(app) + response = client.options( + "/api/projects", + headers={ + "Origin": "http://localhost:5173", + "Access-Control-Request-Method": UNSUPPORTED_API_CORS_METHOD, + "Access-Control-Request-Headers": "Content-Type", + }, + ) + + assert response.status_code == 400 + allowed_methods = { + item.strip() + for item in response.headers["Access-Control-Allow-Methods"].split(",") + } + assert UNSUPPORTED_API_CORS_METHOD not in allowed_methods