Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions builders/server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from core.runtime.registry import load_all_configs
from core.runtime.venv_management import setup_builder_venvs
from fastapi import Depends, FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from log_config import setup_logging as _setup_logging

logger = structlog.get_logger()
Expand Down Expand Up @@ -45,3 +46,19 @@ async def request_context_middleware(request: Request, call_next) -> Response:
structlog.contextvars.clear_contextvars()
structlog.contextvars.bind_contextvars(request_id=str(uuid.uuid4()))
return await call_next(request)


# added last so CORS wraps outermost and preflight requests short-circuit
# before auth and request-context logging
_DEFAULT_CORS_ORIGINS = "https://wat-street.github.io,http://localhost:5173"
cors_origins = [
origin.strip()
for origin in os.environ.get("CORS_ALLOW_ORIGINS", _DEFAULT_CORS_ORIGINS).split(",")
if origin.strip()
]
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_methods=["*"],
allow_headers=["Authorization", "Content-Type"],
)
41 changes: 41 additions & 0 deletions builders/server/tests/core/api/test_cors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from fastapi.testclient import TestClient
from main import app

client: TestClient = TestClient(app)

ALLOWED_ORIGIN = "https://wat-street.github.io"


def _preflight(origin: str) -> dict[str, str]:
"""build preflight headers for a GET with an Authorization header."""
return {
"Origin": origin,
"Access-Control-Request-Method": "GET",
"Access-Control-Request-Headers": "authorization",
}


def test_preflight_allowed_origin() -> None:
"""an allowed origin gets a 200 preflight with cors headers, no auth needed."""
res = client.options("/api/v1/datasets", headers=_preflight(ALLOWED_ORIGIN))
assert res.status_code == 200
assert res.headers["access-control-allow-origin"] == ALLOWED_ORIGIN
assert "Authorization" in res.headers["access-control-allow-headers"]


def test_preflight_dev_origin() -> None:
"""the local vite dev server origin is allowed by default."""
res = client.options(
"/api/v1/datasets", headers=_preflight("http://localhost:5173")
)
assert res.status_code == 200
assert res.headers["access-control-allow-origin"] == "http://localhost:5173"


def test_preflight_unknown_origin_rejected() -> None:
"""an unknown origin gets no cors headers."""
res = client.options(
"/api/v1/datasets", headers=_preflight("https://evil.example.com")
)
assert res.status_code == 400
assert "access-control-allow-origin" not in res.headers
4 changes: 4 additions & 0 deletions infra/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ DOMAIN=localhost
# cd builders/server && uv run python -m core.auth generate <label>
# only the hash goes here; hand the raw key to the client.
API_KEYS=default:<sha256hex>

# cors: comma-separated browser origins allowed to call the api. defaults to
# the github pages origin + the local vite dev server when unset.
# CORS_ALLOW_ORIGINS=https://wat-street.github.io,http://localhost:5173
Loading