- Multi-stage Dockerfile with gunicorn, non-root user, healthcheck - Structured JSON logging with request ID propagation - Redis-based rate limiting middleware (sliding window) - Security headers middleware (X-Frame-Options, CSP, XSS protection) - Global exception handler hiding stack traces in production - Disable /docs /redoc in production mode - CORS hardened to explicit methods/headers - TrustedHostMiddleware support - Health endpoints: /health returns 503 on degraded, /health/live for liveness - Frontend static export (output: 'export') for Apache serving - docker-compose.prod.yml with resource limits, pinned images, celery-worker - deploy.sh: full pipeline (git pull → build → up → frontend → copy to /var/www) - Cloud Run worker: Dockerfile.worker, cloudbuild.yaml, deploy script (optical-414516) - Celery hardened: task time limits, healthcheck task, configurable concurrency - Admin panel improvements, system prompts, AD group sync Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
30 lines
1.1 KiB
Python
30 lines
1.1 KiB
Python
"""
|
|
Security headers and Request ID middleware.
|
|
"""
|
|
import logging
|
|
from starlette.middleware.base import BaseHTTPMiddleware
|
|
from starlette.requests import Request
|
|
|
|
from app.core.logging_config import request_id_var, generate_request_id
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
|
async def dispatch(self, request: Request, call_next):
|
|
response = await call_next(request)
|
|
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"] = "camera=(), microphone=(), geolocation=()"
|
|
return response
|
|
|
|
|
|
class RequestIdMiddleware(BaseHTTPMiddleware):
|
|
async def dispatch(self, request: Request, call_next):
|
|
rid = request.headers.get("x-request-id") or generate_request_id()
|
|
request_id_var.set(rid)
|
|
response = await call_next(request)
|
|
response.headers["X-Request-ID"] = rid
|
|
return response
|