diff --git a/README.md b/README.md index 8280f8c..afcdb01 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,12 @@ This runs: - SQLite DB path defaults to `backend/.data/edms.db` - Plant storage defaults to `backend/storage/plant` - Document upload storage defaults to `backend/storage/documents` + + +## Vercel deployment notes + +- Set `VITE_API_URL` in the frontend environment to your deployed backend URL (for example `https://.vercel.app`). +- Backend CORS allows: + - `https://gitplant-oggy.vercel.app` + - any `https://*.vercel.app` origin (for Vercel preview deployments) +- CORS preflight `OPTIONS` requests are handled by FastAPI `CORSMiddleware`. diff --git a/backend/app/main.py b/backend/app/main.py index 26c3eb6..20662c8 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -12,17 +12,21 @@ app = FastAPI(title=settings.app_name) -if settings.app_env.lower() in {"dev", "development", "local", "test"}: - app.add_middleware( - CORSMiddleware, - allow_origins=[ - "http://localhost:5173", - "http://127.0.0.1:5173", - ], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) +allowed_origins = [ + "http://localhost:5173", + "http://127.0.0.1:5173", + "https://gitplant-oggy.vercel.app", +] +allowed_origin_regex = r"https://.*\.vercel\.app" + +app.add_middleware( + CORSMiddleware, + allow_origins=allowed_origins, + allow_origin_regex=allowed_origin_regex, + allow_credentials=False, + allow_methods=["*"], + allow_headers=["*"], +) app.include_router(health.router) app.include_router(auth.router) @@ -55,6 +59,16 @@ def read_root(): } +@app.get("/cors-debug", tags=["debug"]) +def cors_debug(request: Request): + return { + "origin": request.headers.get("origin"), + "allowed_origins": allowed_origins, + "allowed_origin_regex": allowed_origin_regex, + "allow_credentials": False, + } + + frontend_dist = (Path(__file__).resolve().parents[2] / "frontend" / "dist").resolve() if frontend_dist.exists(): app.mount("/assets", StaticFiles(directory=str(frontend_dist / "assets")), name="assets") diff --git a/backend/tests/test_dev_and_cors.py b/backend/tests/test_dev_and_cors.py index 0c0bcfc..d790b1d 100644 --- a/backend/tests/test_dev_and_cors.py +++ b/backend/tests/test_dev_and_cors.py @@ -41,13 +41,35 @@ def test_dev_endpoints_disabled_message() -> None: settings.enable_demo_endpoints = original -def test_cors_preflight_allows_frontend_origin() -> None: +def test_cors_preflight_allows_vercel_production_origin() -> None: response = client.options( '/projects', headers={ - 'Origin': 'http://localhost:5173', + 'Origin': 'https://gitplant-oggy.vercel.app', 'Access-Control-Request-Method': 'POST', }, ) assert response.status_code == 200 - assert response.headers['access-control-allow-origin'] == 'http://localhost:5173' + assert response.headers['access-control-allow-origin'] == 'https://gitplant-oggy.vercel.app' + + +def test_cors_preflight_allows_vercel_preview_origin() -> None: + response = client.options( + '/projects', + headers={ + 'Origin': 'https://preview-123.vercel.app', + 'Access-Control-Request-Method': 'POST', + }, + ) + assert response.status_code == 200 + assert response.headers['access-control-allow-origin'] == 'https://preview-123.vercel.app' + + +def test_cors_debug_reports_origin_and_allowed_origins() -> None: + origin = 'https://gitplant-oggy.vercel.app' + response = client.get('/cors-debug', headers={'Origin': origin}) + assert response.status_code == 200 + body = response.json() + assert body['origin'] == origin + assert 'https://gitplant-oggy.vercel.app' in body['allowed_origins'] + assert body['allowed_origin_regex'] == r'https://.*\.vercel\.app'