اصلاح مشکل CORS
Verify / test (push) Failing after 17s

This commit is contained in:
2026-11-05 18:49:21 +03:30
والد 56c5ee23d3
کامیت 407b213222
3فایلهای تغییر یافته به همراه72 افزوده شده و 1 حذف شده
+9
مشاهده پرونده
@@ -10,6 +10,7 @@ from pathlib import Path
from typing import Annotated, Literal
from fastapi import FastAPI, File, Form, Header, HTTPException, Request, UploadFile
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse, JSONResponse, Response
from sqlalchemy import func, select, text
from sqlalchemy.exc import SQLAlchemyError
@@ -80,6 +81,14 @@ async def security(request, call_next):
})
return response
# Register after security so CORS also decorates its early authentication errors.
app.add_middleware(
CORSMiddleware,
allow_origins=['https://git.sadentel.net'],
allow_methods=['GET', 'POST', 'OPTIONS'],
allow_headers=['X-API-Key', 'X-Viewer-Token', 'Content-Type'],
)
@app.exception_handler(HTTPException)
async def http_error(request, exc):
detail = exc.detail if isinstance(exc.detail, dict) else {'code': 'http_error', 'message': str(exc.detail)}
@@ -163,3 +163,65 @@ def test_parser_wall_timeout_is_reported(client,ascii_schematic,monkeypatch):
process(job)
status=client.get(f"/api/v1/schematics/{body['id']}/status",headers={'X-API-Key':KEY}).json()
assert status['status']=='failed' and status['error']['code']=='parse_timeout'
CORS_ORIGIN = 'https://git.sadentel.net'
@pytest.mark.parametrize('requested_headers', ['x-api-key', 'X-API-Key, Content-Type'])
def test_gitea_preflight_without_api_key(client, requested_headers):
response = client.options('/api/v1/schematics/render', headers={
'Origin': CORS_ORIGIN,
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': requested_headers,
})
assert response.status_code == 200, response.text
assert response.headers['access-control-allow-origin'] == CORS_ORIGIN
assert {'POST', 'OPTIONS'} <= set(response.headers['access-control-allow-methods'].split(', '))
allowed = {h.strip().lower() for h in response.headers['access-control-allow-headers'].split(',')}
assert {'x-api-key', 'content-type'} <= allowed
assert 'Origin' in response.headers['vary']
assert 'access-control-allow-credentials' not in response.headers
@pytest.mark.parametrize('origin', ['https://evil.example', 'https://git.sadentel.net.evil.example', 'http://git.sadentel.net'])
def test_cors_rejects_other_origins(client, origin):
response = client.options('/api/v1/schematics/render', headers={
'Origin': origin,
'Access-Control-Request-Method': 'POST',
'Access-Control-Request-Headers': 'x-api-key',
})
assert response.status_code == 400
assert 'access-control-allow-origin' not in response.headers
@pytest.mark.parametrize('key', [None, 'invalid-key', KEY])
def test_gitea_post_retains_authentication_and_cors(client, ascii_schematic, key):
headers = {'Origin': CORS_ORIGIN}
if key is not None: headers['X-API-Key'] = key
response = client.post('/api/v1/schematics/render', headers=headers,
files={'file': ('design.SchDoc', ascii_schematic)}, data={'access': 'private'})
assert response.status_code == (202 if key == KEY else 401)
assert response.headers['access-control-allow-origin'] == CORS_ORIGIN
def test_gitea_private_status_read(client, ascii_schematic):
created = upload(client, ascii_schematic).json()
endpoint = f"/api/v1/schematics/{created['id']}/status"
preflight = client.options(endpoint, headers={
'Origin': CORS_ORIGIN, 'Access-Control-Request-Method': 'GET',
'Access-Control-Request-Headers': 'x-viewer-token',
})
assert preflight.status_code == 200
response = client.get(endpoint, headers={'Origin': CORS_ORIGIN, 'X-Viewer-Token': created['viewer_token']})
assert response.status_code == 200
assert response.headers['access-control-allow-origin'] == CORS_ORIGIN
assert client.get(endpoint, headers={'Origin': CORS_ORIGIN}).status_code == 404
def test_cors_rejects_unneeded_methods_and_headers(client):
for method, header in [('DELETE', 'x-api-key'), ('POST', 'x-unapproved-header')]:
response = client.options('/api/v1/schematics/render', headers={
'Origin': CORS_ORIGIN, 'Access-Control-Request-Method': method,
'Access-Control-Request-Headers': header,
})
assert response.status_code == 400
+1 -1
مشاهده پرونده
@@ -1,6 +1,6 @@
# REST API contract
All routes are same-origin by default. Authentication for upload is `X-API-Key`. Private reads accept the same service key or `X-Viewer-Token`, returned only at creation. The service uses one integration key, not multi-tenant user authentication. Use a gateway for organization-specific account roles, key rotation, quotas and rate limits.
Cross-origin browser access is allowed only from `https://git.sadentel.net`, with GET, POST and OPTIONS and the request headers `X-API-Key`, `X-Viewer-Token` and `Content-Type` (plus the framework's standard safelisted headers). Preflight OPTIONS requires no API key; POST authentication and private-read permissions remain enforced. Cookie credentials are not enabled. Authentication for upload is `X-API-Key`. Private reads accept the same service key or `X-Viewer-Token`, returned only at creation. The service uses one integration key, not multi-tenant user authentication. Use a gateway for organization-specific account roles, key rotation, quotas and rate limits.
`POST /api/v1/schematics/render` accepts exactly one multipart file field `file`, with `.SchDoc` suffix (case insensitive). The client-provided content type is not trusted. The request body is capped before multipart spooling, and extracted file bytes are separately capped. Initial format identification accepts OLE signature or an ASCII SchDoc header; complete structural/version validation runs asynchronously in an isolated worker.