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
37 changes: 2 additions & 35 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,6 @@ async def lifespan(_app: FastAPI):
max_age=100,
)

# Never log these header values verbatim (tokens/session identifiers):
_SENSITIVE_LOG_HEADERS = frozenset({"authorization", "cookie", "set-cookie"})


# Registered after CORSMiddleware, so it runs outermost and sees every
# request -- including OPTIONS preflights CORSMiddleware intercepts itself.
@app.middleware("http")
async def log_request_headers(request: Request, call_next):
safe_headers = {
k: ("<redacted>" if k.lower() in _SENSITIVE_LOG_HEADERS else v)
for k, v in request.headers.items()
}
logger.info(f"{request.method} {request.url.path} headers={safe_headers}")
return await call_next(request)


# Include routers
app.include_router(osm_router, prefix="/api/v1")
Expand Down Expand Up @@ -196,19 +181,6 @@ def get_workspace_repository(
"forwarded",
}

# osm-rails sets its own Access-Control-*/Vary headers on some API responses
# (e.g. GET /api/0.6/users) for direct browser access. Forwarding those
# verbatim alongside our own CORSMiddleware's headers produces duplicate,
# conflicting values that browsers reject as a CORS error.
STRIP_RESPONSE_HEADERS = HOP_BY_HOP_HEADERS | {
"vary",
"access-control-allow-origin",
"access-control-allow-credentials",
"access-control-allow-methods",
"access-control-allow-headers",
"access-control-expose-headers",
"access-control-max-age",
}

# Paths that do not require X-Workspace header, scoped by HTTP method. Each
# entry is a tuple of: (compiled regex, set of allowed methods).
Expand Down Expand Up @@ -258,9 +230,7 @@ async def capabilities(request: Request):
)

forwarded_headers = {
k: v
for k, v in rp_resp.headers.items()
if k.lower() not in STRIP_RESPONSE_HEADERS
k: v for k, v in rp_resp.headers.items() if k.lower() not in HOP_BY_HOP_HEADERS
}

return StreamingResponse(
Expand Down Expand Up @@ -375,7 +345,6 @@ async def catch_all(
)
try:
rp_resp = await client.send(rp_req, stream=True)
logger.info(f"Upstream request to {rp_req.url} sent successfully")
except httpx.TimeoutException:
raise HTTPException(
status_code=status.HTTP_504_GATEWAY_TIMEOUT,
Expand All @@ -396,9 +365,7 @@ async def catch_all(
logger.warning(msg)

forwarded_headers = {
k: v
for k, v in rp_resp.headers.items()
if k.lower() not in STRIP_RESPONSE_HEADERS
k: v for k, v in rp_resp.headers.items() if k.lower() not in HOP_BY_HOP_HEADERS
}

return StreamingResponse(
Expand Down
25 changes: 0 additions & 25 deletions tests/integration/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,31 +156,6 @@ async def test_hop_by_hop_response_headers_are_stripped(client, login, monkeypat
assert "keep-alive" not in response.headers


async def test_upstream_cors_headers_are_stripped(client, login, monkeypatch):
# osm-rails sets its own Access-Control-*/Vary on some responses; forwarding
# them would duplicate/conflict with CORSMiddleware's own headers.
install_osm(
monkeypatch,
lambda req: (
200,
{
"content-type": "application/xml",
"access-control-allow-origin": "*",
"vary": "Origin",
},
b"<osm/>",
),
)
login(factories.make_user_info(accessible_workspace_ids={"pg": [1]}))

response = await client.get("/api/0.6/users", headers={"X-Workspace": "1"})

assert response.status_code == 200
assert "vary" not in response.headers
# Only CORSMiddleware's Access-Control-Allow-Origin should be present.
assert response.headers.get("access-control-allow-origin") != "*"


# --- upstream status + body fidelity ---------------------------------------


Expand Down
Loading