|
4 | 4 | # export no_proxy="localhost, 127.0.0.1, ::1" |
5 | 5 | # Start with: |
6 | 6 | # - uv run app/main.py |
7 | | -# or use docker and access UI and backend at geti-tune.localhost |
| 7 | +# or use docker |
8 | 8 | # - docker compose up |
9 | 9 |
|
10 | 10 | import importlib |
11 | 11 | import logging |
12 | 12 | import pkgutil |
| 13 | +from collections.abc import Awaitable, Callable |
| 14 | +from os import getenv |
13 | 15 | from pathlib import Path |
| 16 | +from typing import cast |
14 | 17 |
|
15 | 18 | import uvicorn |
16 | | -from fastapi import FastAPI |
| 19 | +from fastapi import FastAPI, Request, Response |
17 | 20 | from fastapi.middleware.cors import CORSMiddleware |
18 | 21 | from fastapi.responses import FileResponse |
| 22 | +from fastapi.staticfiles import StaticFiles |
19 | 23 | from loguru import logger |
20 | 24 |
|
21 | 25 | from app.api import routers |
|
39 | 43 |
|
40 | 44 | app.add_middleware( # TODO restrict settings in production |
41 | 45 | CORSMiddleware, |
42 | | - allow_origins=["*"], |
| 46 | + allow_origins=settings.cors_allowed_origins, |
43 | 47 | allow_credentials=True, |
44 | 48 | allow_methods=["*"], |
45 | 49 | allow_headers=["*"], |
@@ -73,6 +77,32 @@ async def health_check() -> dict[str, str]: |
73 | 77 | return {"status": "ok"} |
74 | 78 |
|
75 | 79 |
|
| 80 | +@app.middleware("http") |
| 81 | +async def security_headers_middleware( |
| 82 | + request: Request, |
| 83 | + call_next: Callable[[Request], Awaitable[Response]], |
| 84 | +) -> Response: |
| 85 | + """Add COEP and COOP security headers to all HTTP responses.""" |
| 86 | + response = await call_next(request) |
| 87 | + response.headers.setdefault("Cross-Origin-Embedder-Policy", "require-corp") |
| 88 | + response.headers.setdefault("Cross-Origin-Opener-Policy", "same-origin") |
| 89 | + return response |
| 90 | + |
| 91 | + |
| 92 | +static_dir = settings.static_files_dir |
| 93 | +if static_dir is not None and static_dir.is_dir() and any(static_dir.iterdir()): |
| 94 | + asset_prefix = getenv("ASSET_PREFIX", "/html") |
| 95 | + logger.info("Serving static files from {} by context {}", static_dir, asset_prefix) |
| 96 | + |
| 97 | + app.mount(asset_prefix, StaticFiles(directory=static_dir), name="static") |
| 98 | + |
| 99 | + @app.get("/", include_in_schema=False) |
| 100 | + @app.get("/{full_path:path}", include_in_schema=False) |
| 101 | + async def serve_spa() -> FileResponse: |
| 102 | + """Serve the Single Page Application (SPA) index.html file for any path.""" |
| 103 | + return FileResponse(cast(Path, static_dir) / "index.html") |
| 104 | + |
| 105 | + |
76 | 106 | def main() -> None: |
77 | 107 | """Main application entry point""" |
78 | 108 | logger.info(f"Starting {settings.app_name} in {settings.environment} mode") |
|
0 commit comments