Skip to content
Closed
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
7 changes: 6 additions & 1 deletion gateway/platforms/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ def __len__(self) -> int:

_CORS_HEADERS = {
"Access-Control-Allow-Methods": "GET, POST, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Authorization, Content-Type",
# Include Idempotency-Key so browser clients can use request deduplication safely.
"Access-Control-Allow-Headers": "Authorization, Content-Type, Idempotency-Key",
}


Expand All @@ -185,10 +186,14 @@ async def cors_middleware(request, handler):
if request.method == "OPTIONS":
if cors_headers is None:
return web.Response(status=403)
# Ensure downstream caches do not serve one origin's CORS response to another.
cors_headers.setdefault("Vary", "Origin")
return web.Response(status=200, headers=cors_headers)

response = await handler(request)
if cors_headers is not None:
# Ensure downstream caches do not serve one origin's CORS response to another.
cors_headers.setdefault("Vary", "Origin")
response.headers.update(cors_headers)
return response
else:
Expand Down
28 changes: 28 additions & 0 deletions tests/gateway/test_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,34 @@ async def test_cors_headers_present_for_allowed_origin(self):
assert "DELETE" in resp.headers.get("Access-Control-Allow-Methods", "")

@pytest.mark.asyncio


@pytest.mark.asyncio
async def test_cors_allows_idempotency_key_header(self):
adapter = _make_adapter(cors_origins=["http://localhost:3000"])
app = _create_app(adapter)
async with TestClient(TestServer(app)) as cli:
resp = await cli.options(
"/v1/chat/completions",
headers={
"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "Idempotency-Key",
},
)
assert resp.status == 200
assert "Idempotency-Key" in resp.headers.get("Access-Control-Allow-Headers", "")

@pytest.mark.asyncio
async def test_cors_sets_vary_origin_header(self):
adapter = _make_adapter(cors_origins=["http://localhost:3000"])
app = _create_app(adapter)
async with TestClient(TestServer(app)) as cli:
resp = await cli.get("/health", headers={"Origin": "http://localhost:3000"})
assert resp.status == 200
assert resp.headers.get("Vary") == "Origin"

@pytest.mark.asyncio
async def test_cors_options_preflight_allowed_for_configured_origin(self):
"""Configured origins can complete browser preflight."""
adapter = _make_adapter(cors_origins=["http://localhost:3000"])
Expand Down
Loading