diff --git a/api/app.py b/api/app.py index f60d0ec..b2b2811 100644 --- a/api/app.py +++ b/api/app.py @@ -2,7 +2,12 @@ import sentry_sdk -from api.constants import DEPLOYMENT_MODE, ENABLE_TELEMETRY, SENTRY_DSN +from api.constants import ( + CORS_ALLOWED_ORIGINS, + DEPLOYMENT_MODE, + ENABLE_TELEMETRY, + SENTRY_DSN, +) from api.logging_config import ENVIRONMENT, setup_logging # Set up logging and get the listener for cleanup @@ -83,13 +88,33 @@ app = FastAPI( ) -# Configure CORS +# Configure CORS. +# OSS is typically deployed with UI and API behind a single reverse proxy +# (same-origin, so CORS does not apply). Keep it permissive without +# credentials — wildcard + credentials is rejected by browsers and unsafe. +# SaaS deployments must set CORS_ALLOWED_ORIGINS to an explicit allowlist. +if DEPLOYMENT_MODE == "oss": + cors_origins: list[str] = ["*"] + cors_allow_credentials = False +else: + if not CORS_ALLOWED_ORIGINS: + raise RuntimeError( + "CORS_ALLOWED_ORIGINS must be set to an explicit origin allowlist " + "when DEPLOYMENT_MODE != 'oss'" + ) + if "*" in CORS_ALLOWED_ORIGINS: + raise RuntimeError( + "CORS_ALLOWED_ORIGINS cannot contain '*' with credentialed requests" + ) + cors_origins = CORS_ALLOWED_ORIGINS + cors_allow_credentials = True + app.add_middleware( CORSMiddleware, - allow_origins=["*"], # Allows all origins - allow_credentials=True, - allow_methods=["*"], # Allows all methods - allow_headers=["*"], # Allows all headers + allow_origins=cors_origins, + allow_credentials=cors_allow_credentials, + allow_methods=["*"], + allow_headers=["*"], ) api_router = APIRouter() diff --git a/api/constants.py b/api/constants.py index ef949eb..e5d9d7f 100644 --- a/api/constants.py +++ b/api/constants.py @@ -26,6 +26,9 @@ DATABASE_URL = os.environ["DATABASE_URL"] REDIS_URL = os.environ["REDIS_URL"] DEPLOYMENT_MODE = os.getenv("DEPLOYMENT_MODE", "oss") +CORS_ALLOWED_ORIGINS = [ + o.strip() for o in os.getenv("CORS_ALLOWED_ORIGINS", "").split(",") if o.strip() +] AUTH_PROVIDER = os.getenv("AUTH_PROVIDER", "local") DOGRAH_MPS_SECRET_KEY = os.getenv("DOGRAH_MPS_SECRET_KEY", None) MPS_API_URL = os.getenv("MPS_API_URL", "https://services.dograh.com") diff --git a/api/tests/test_user_configured_service_url_security.py b/api/tests/test_user_configured_service_url_security.py index ecfc1c3..1585652 100644 --- a/api/tests/test_user_configured_service_url_security.py +++ b/api/tests/test_user_configured_service_url_security.py @@ -215,9 +215,7 @@ def test_runtime_blocks_elevenlabs_local_tts_base_url_in_saas(monkeypatch): def test_embedding_service_blocks_private_base_url_in_saas(monkeypatch): - monkeypatch.setattr( - "api.utils.url_security.DEPLOYMENT_MODE", "saas" - ) + monkeypatch.setattr("api.utils.url_security.DEPLOYMENT_MODE", "saas") with pytest.raises(ValueError, match="public IP"): OpenAIEmbeddingService( diff --git a/docs/developer/environment-variables.mdx b/docs/developer/environment-variables.mdx index 05e95c6..d7f480b 100644 --- a/docs/developer/environment-variables.mdx +++ b/docs/developer/environment-variables.mdx @@ -56,6 +56,7 @@ Never use the placeholder `OSS_JWT_SECRET` in a production deployment. Generate | `UI_APP_URL` | `http://localhost:3010` | URL of the frontend application | | `MPS_API_URL` | `https://services.dograh.com` | Dograh Managed Platform Services URL | | `DOGRAH_MPS_SECRET_KEY` | `null` | **Required for non-OSS deployments.** Secret key for authenticating with MPS | +| `CORS_ALLOWED_ORIGINS` | `null` | **Required for non-OSS deployments.** Comma-separated list of origins allowed to make credentialed cross-origin requests (e.g. `https://app.example.com,https://admin.example.com`). Ignored in OSS mode, which serves a permissive same-origin policy without credentials | --- diff --git a/sdk/python/src/dograh_sdk/_generated_models.py b/sdk/python/src/dograh_sdk/_generated_models.py index 09fe76f..301511a 100644 --- a/sdk/python/src/dograh_sdk/_generated_models.py +++ b/sdk/python/src/dograh_sdk/_generated_models.py @@ -1,6 +1,6 @@ # generated by datamodel-codegen: -# filename: dograh-openapi-NApdQI.json -# timestamp: 2026-05-26T09:41:07+00:00 +# filename: dograh-openapi-aBVTJk.json +# timestamp: 2026-05-27T10:06:12+00:00 from __future__ import annotations