Align Sinbad2 HTTPS deployment with orcid2sword reverse-proxy pattern.
This adds nginx dual-path routing, forwarded proxy headers, Uvicorn proxy-headers, production security settings, and deployment docs for https://sinbad2.ujaen.es/generadorexamenesllm/.
This commit is contained in:
@@ -7,6 +7,9 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
class Settings(BaseSettings):
|
||||
app_name: str = "GenExamenes IA"
|
||||
environment: str = "local"
|
||||
public_base_url: str = "https://sinbad2.ujaen.es/generadorexamenesllm"
|
||||
trusted_hosts: str = "sinbad2.ujaen.es,localhost,127.0.0.1"
|
||||
security_hsts_seconds: int = Field(default=31_536_000, ge=0)
|
||||
api_prefix: str = ""
|
||||
api_key: str = Field(min_length=16)
|
||||
database_url: str = "postgresql+psycopg://genexamenes:genexamenes@localhost:5432/genexamenes"
|
||||
@@ -41,10 +44,18 @@ class Settings(BaseSettings):
|
||||
extra="ignore",
|
||||
)
|
||||
|
||||
@property
|
||||
def is_production(self) -> bool:
|
||||
return self.environment.lower() in {"production", "prod"}
|
||||
|
||||
@property
|
||||
def cors_origins(self) -> list[str]:
|
||||
return [origin.strip() for origin in self.allowed_origins.split(",") if origin.strip()]
|
||||
|
||||
@property
|
||||
def trusted_hosts_list(self) -> list[str]:
|
||||
return [host.strip() for host in self.trusted_hosts.split(",") if host.strip()]
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
||||
|
||||
from app.core.config import Settings
|
||||
|
||||
|
||||
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
||||
"""Cabeceras de seguridad; HSTS en producción o cuando el proxy indica HTTPS."""
|
||||
|
||||
def __init__(self, app: object, settings: Settings) -> None:
|
||||
super().__init__(app)
|
||||
self._settings = settings
|
||||
|
||||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
|
||||
response = await call_next(request)
|
||||
|
||||
response.headers.setdefault("X-Content-Type-Options", "nosniff")
|
||||
response.headers.setdefault("X-Frame-Options", "SAMEORIGIN")
|
||||
response.headers.setdefault("Referrer-Policy", "strict-origin-when-cross-origin")
|
||||
|
||||
forwarded_proto = request.headers.get("x-forwarded-proto", request.url.scheme)
|
||||
is_https = forwarded_proto == "https" or request.url.scheme == "https"
|
||||
|
||||
if is_https or self._settings.is_production:
|
||||
hsts = f"max-age={self._settings.security_hsts_seconds}"
|
||||
if self._settings.is_production:
|
||||
hsts += "; includeSubDomains"
|
||||
response.headers.setdefault("Strict-Transport-Security", hsts)
|
||||
|
||||
return response
|
||||
+6
-1
@@ -1,13 +1,15 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
from fastapi import Depends, FastAPI
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
||||
|
||||
from app.api.routes import auth, exports, generation, health, history, images, materials, questions, templates
|
||||
from app.core.config import get_settings
|
||||
from app.core.errors import register_exception_handlers
|
||||
from app.core.middleware import RateLimitMiddleware, RequestSizeLimitMiddleware
|
||||
from app.core.security_headers import SecurityHeadersMiddleware
|
||||
from app.db.init_db import init_db
|
||||
|
||||
|
||||
@@ -25,6 +27,9 @@ def create_app() -> FastAPI:
|
||||
# las peticiones OPTIONS (preflight) respondan antes que rate limit, etc.
|
||||
app.add_middleware(RequestSizeLimitMiddleware, settings=settings)
|
||||
app.add_middleware(RateLimitMiddleware, settings=settings)
|
||||
app.add_middleware(SecurityHeadersMiddleware, settings=settings)
|
||||
if settings.trusted_hosts_list:
|
||||
app.add_middleware(TrustedHostMiddleware, allowed_hosts=settings.trusted_hosts_list)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=settings.cors_origins,
|
||||
|
||||
Reference in New Issue
Block a user