feat: production launch — email audit, team invites, security headers, router fixes
- Fix loyalty & monitoring router bugs (_get_router → named routers) - Implement team invitation email with send_template + seed templates (en/fr/de) - Add SecurityHeadersMiddleware (nosniff, HSTS, referrer-policy, permissions-policy) - Build email audit admin page: service, schemas, API, page route, menu, i18n, HTML, JS - Clean stale TODO in platform-menu-config.js - Add 67 tests (unit + integration) covering all new functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
41
middleware/security_headers.py
Normal file
41
middleware/security_headers.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# middleware/security_headers.py
|
||||
"""
|
||||
Security headers middleware.
|
||||
|
||||
Adds standard security headers to all responses:
|
||||
- X-Content-Type-Options: nosniff
|
||||
- X-Frame-Options: SAMEORIGIN
|
||||
- Strict-Transport-Security (HTTPS only)
|
||||
- Referrer-Policy: strict-origin-when-cross-origin
|
||||
- Permissions-Policy: camera=(), microphone=(), geolocation=()
|
||||
"""
|
||||
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
|
||||
from fastapi import Request, Response
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
|
||||
"""Middleware that adds security headers to all responses."""
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||||
response = await call_next(request)
|
||||
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
response.headers["X-Frame-Options"] = "SAMEORIGIN"
|
||||
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
||||
response.headers["Permissions-Policy"] = (
|
||||
"camera=(), microphone=(), geolocation=()"
|
||||
)
|
||||
|
||||
# Only add HSTS when the request came over HTTPS
|
||||
if request.url.scheme == "https":
|
||||
response.headers["Strict-Transport-Security"] = (
|
||||
"max-age=63072000; includeSubDomains"
|
||||
)
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user