Skip to content
Merged
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
2 changes: 1 addition & 1 deletion Procfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
web: python -m uvicorn rosetta.api.app:app --host 0.0.0.0 --port $PORT
web: python -m uvicorn rosetta.api.app:app --host 0.0.0.0 --port $PORT --timeout-keep-alive 75 --timeout-graceful-shutdown 30 --limit-concurrency 1000 --backlog 2048
4 changes: 4 additions & 0 deletions run_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@
host="0.0.0.0",
port=8000,
reload=True,
timeout_keep_alive=75, # Standard HTTP keep-alive timeout for corporate proxies
timeout_graceful_shutdown=30, # Graceful shutdown timeout
limit_concurrency=1000, # Maximum concurrent connections
backlog=2048, # Maximum number of pending connections
)
51 changes: 48 additions & 3 deletions src/rosetta/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@

import requests
from dotenv import load_dotenv
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi import FastAPI, File, Form, HTTPException, Request, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from starlette.middleware.base import BaseHTTPMiddleware
from openpyxl import load_workbook

from rosetta.services.translation_service import count_cells, translate_file
from rosetta.api.mcp import router as mcp_router
from .mcp import router as mcp_router

# Load environment variables from .env file
load_dotenv()
Expand All @@ -24,13 +25,19 @@

# CORS configuration - allow frontend origins
FRONTEND_URL = os.getenv("FRONTEND_URL", "")
CORS_ALLOW_ALL = os.getenv("CORS_ALLOW_ALL", "false").lower() == "true"

ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
if FRONTEND_URL:
ALLOWED_ORIGINS.append(FRONTEND_URL)

# For corporate firewalls, allow all origins if configured
if CORS_ALLOW_ALL:
ALLOWED_ORIGINS = ["*"]

# Limits
# Keep in sync with frontend validation/copy (50MB).
MAX_FILE_SIZE = 50 * 1024 * 1024 # 50MB
Expand All @@ -42,13 +49,41 @@
version="0.1.0",
)


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Middleware to add security headers for corporate firewall compatibility."""

async def dispatch(self, request: Request, call_next):
response = await call_next(request)

# Security headers that corporate firewalls expect
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"

# HSTS header (only add if HTTPS)
if request.url.scheme == "https":
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"

# Connection keep-alive for corporate proxies
response.headers["Connection"] = "keep-alive"

return response


# Add security headers middleware first (before CORS)
app.add_middleware(SecurityHeadersMiddleware)

# CORS middleware for frontend
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_credentials=not CORS_ALLOW_ALL, # Disable credentials if allowing all origins
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"],
)

# Register MCP router
Expand All @@ -61,6 +96,16 @@ async def root() -> dict:
return {"status": "ok", "service": "rosetta"}


@app.get("/health")
async def health() -> dict:
"""Lightweight health check endpoint for firewalls and monitoring.

Returns minimal JSON response quickly to help corporate firewalls
validate the connection without heavy processing.
"""
return {"status": "healthy"}


@app.post("/sheets")
async def get_sheets(
file: UploadFile = File(..., description="Excel file to get sheet names from"),
Expand Down
Loading