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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,22 @@ This backend consists of the API Server and the Orchestrator.
The API Server receives API requests and accesses the database to fulfill them.
The API documentation can be accessed at [http://localhost:8000/docs](http://localhost:8000/docs).

## CORS Middleware

### Purpose

The CORS (Cross-Origin Resource Sharing) middleware allows web browsers to make requests to the Tangle API from different origins (domains). This is essential for:
- Local development (frontend on `localhost:3000`, backend on `localhost:8000`)
- Production deployments where the frontend and backend are on different domains
- Multi-tenant deployments with multiple frontend URLs

### How It Works

1. **Environment Variable Configuration**: Origins are specified in the `TANGLE_CORS_ALLOWED_ORIGINS` environment variable as a comma-separated list
2. **Request Validation**: For each incoming request, the middleware checks the `Origin` header from the browser
3. **Dynamic Response**: If the origin is in the allowed list, the server responds with `Access-Control-Allow-Origin` set to that specific origin
4. **Security**: Only pre-approved origins receive CORS headers, preventing unauthorized cross-origin access

### Orchestrator

The Orchestrator works independently from the API Server.
Expand Down
4 changes: 4 additions & 0 deletions cloud_pipelines_backend/api_router.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from . import component_library_api_server as components_api
from . import database_ops
from . import errors
from . import middleware

if typing.TYPE_CHECKING:
from .launchers import interfaces as launcher_interfaces
Expand All @@ -39,6 +40,9 @@ def setup_routes(
container_launcher_for_log_streaming: "launcher_interfaces.ContainerTaskLauncher[launcher_interfaces.LaunchedContainer] | None" = None,
default_component_library_owner_username: str = "admin",
):
# Setup global middleware (CORS, etc.) - must be called before routes are added
middleware.setup_cors_middleware(app)

def get_session():
with orm.Session(autocommit=False, autoflush=False, bind=db_engine) as session:
yield session
Expand Down
94 changes: 94 additions & 0 deletions cloud_pipelines_backend/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
Global middleware configuration for Tangle API servers.

This module provides reusable middleware setup functions that should be used
by all entry points to ensure consistent behavior across deployments.
"""

import logging
import os
from urllib.parse import urlparse

import fastapi
from fastapi.middleware.cors import CORSMiddleware


logger = logging.getLogger(__name__)


def _is_valid_origin(origin: str) -> bool:
"""
Validate that an origin string is a valid URL format.

Args:
origin: The origin URL to validate

Returns:
True if valid, False otherwise
"""
try:
parsed = urlparse(origin)
# Must have a scheme (http/https) and a netloc (domain/host)
if not parsed.scheme or not parsed.netloc:
return False
# Scheme must be http or https
if parsed.scheme not in ("http", "https"):
return False
# Should not have a path beyond '/'
if parsed.path and parsed.path != "/":
return False
return True
except Exception:
return False


def setup_cors_middleware(app: fastapi.FastAPI) -> None:
"""
Configure CORS middleware for the FastAPI application.

Environment Variables:
TANGLE_CORS_ALLOWED_ORIGINS: Comma-separated list of allowed origins.
Default: "http://localhost:3000,http://127.0.0.1:3000" if not set.
"""
cors_origins_str = os.environ.get(
"TANGLE_CORS_ALLOWED_ORIGINS", "http://localhost:3000,http://127.0.0.1:3000"
)

# Parse the comma-separated list and strip whitespace from each origin
raw_origins = [
origin.strip() for origin in cors_origins_str.split(",") if origin.strip()
]

# Validate each origin
allowed_origins = []
invalid_origins = []

for origin in raw_origins:
if _is_valid_origin(origin):
allowed_origins.append(origin)
else:
invalid_origins.append(origin)

# Log warnings for invalid origins
if invalid_origins:
logger.warning(
f"Invalid CORS origins found and ignored: {', '.join(invalid_origins)}. "
f"Origins must be valid URLs with http:// or https:// scheme."
)

if not allowed_origins:
logger.warning("No valid CORS origins found. CORS middleware not configured.")
return

app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

logger.info(
f"CORS middleware configured for {len(allowed_origins)} origin(s): "
f"{', '.join(allowed_origins)}"
)
Loading