import os from app.api.router import api_router, health_router from app.config import settings from app.middleware.exceptions import ( APIException, api_exception_handler, general_exception_handler, http_exception_handler, validation_exception_handler, ) from app.middleware.logging import RateLimitMiddleware, RequestLoggingMiddleware from app.middleware.prometheus_metrics import PrometheusMetricsMiddleware, metrics_endpoint from fastapi import FastAPI from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware from starlette.exceptions import HTTPException as StarletteHTTPException from starlette.staticfiles import StaticFiles app = FastAPI( title="小虾 SaaS API", description="自动化剪辑 SaaS 平台 API", version=settings.APP_VERSION, docs_url=None if settings.ENVIRONMENT == "production" else "/docs", redoc_url=None if settings.ENVIRONMENT == "production" else "/redoc", openapi_url=None if settings.ENVIRONMENT == "production" else "/openapi.json", redirect_slashes=False, ) app.add_exception_handler(APIException, api_exception_handler) app.add_exception_handler(StarletteHTTPException, http_exception_handler) app.add_exception_handler(RequestValidationError, validation_exception_handler) app.add_exception_handler(Exception, general_exception_handler) # P1-1: Fix CORS configuration security issue # - allow_credentials=True is incompatible with allow_origins=["*"] # - In production, only allow configured domains, not "*" if settings.DEBUG: allow_origins = settings.CORS_ORIGINS # Allow localhost in debug mode else: # In production, filter out any wildcard "*" origins allow_origins = list({origin for origin in settings.CORS_ORIGINS if origin != "*"}) # Always ensure production domains are included for domain in ("https://xiaoxiajianji.com", "https://saas.xiaoxiajianji.com"): if domain not in allow_origins: allow_origins.append(domain) app.add_middleware( CORSMiddleware, allow_origins=allow_origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], allow_headers=["Authorization", "Content-Type"], ) app.add_middleware(GZipMiddleware, minimum_size=1000) # P0-1: 登录接口限流 - 每 IP 每分钟最多 10 次登录尝试 app.add_middleware(RateLimitMiddleware, max_requests=10, window_seconds=60, paths=["/api/v1/auth/login"]) app.add_middleware(RequestLoggingMiddleware) app.add_middleware(PrometheusMetricsMiddleware) app.include_router(health_router) app.add_route("/metrics", metrics_endpoint, methods=["GET"]) app.include_router(api_router) _generated_files_dir = os.getenv("GENERATED_FILES_DIR", "/app/generated") os.makedirs(_generated_files_dir, exist_ok=True) app.mount( os.getenv("GENERATED_FILES_URL_PREFIX", "/generated-files"), StaticFiles(directory=_generated_files_dir), name="generated-files", ) def create_app() -> FastAPI: return app @app.get("/") async def root(): return { "service": settings.APP_NAME, "status": "running", "version": settings.APP_VERSION, } if __name__ == "__main__": import uvicorn uvicorn.run(app, host=settings.API_HOST, port=settings.API_PORT)