P0.5: Database Row-Level Security (RLS) - CRITICAL - Created Alembic migration for RLS policies on all client-scoped tables - Policies for: presentations, master_decks, brand_configs, slides, templates - Updated get_async_session to set PostgreSQL session variables - Multi-tenant isolation now enforced at database level (defense-in-depth) - Session variables: app.current_user_id, app.user_role P0.6: Safe Error Messages - Created safe_exception_handler to prevent info disclosure - Logs full errors internally with context (user_id, path, method) - Returns generic "internal error" message to clients - Preserves HTTPException details (intentional error messages) P0.7: Security Headers - Created SecurityHeadersMiddleware with comprehensive headers - Headers: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection - CSP, Referrer-Policy, Permissions-Policy, HSTS - Updated nginx.conf with matching security headers P0.8: Database Connection Pool Optimization - Increased pool_size from 5 to 20 connections - Added max_overflow of 40 for burst traffic - Enabled pool_pre_ping for connection health checks - Pool recycle after 1 hour to prevent stale connections - Configurable via DB_POOL_SIZE, DB_MAX_OVERFLOW, DB_POOL_RECYCLE All critical pre-launch security tasks complete. System now has: ✅ CORS protection ✅ Rate limiting ✅ Request size limits ✅ Database-level tenant isolation (RLS) ✅ Safe error handling ✅ Security headers ✅ Optimized connection pooling Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
53 lines
1.6 KiB
Python
53 lines
1.6 KiB
Python
"""
|
|
Safe error handler for FastAPI.
|
|
|
|
Logs full error details internally but returns generic messages to clients
|
|
to prevent information disclosure.
|
|
"""
|
|
|
|
import logging
|
|
from fastapi import Request, HTTPException
|
|
from fastapi.responses import JSONResponse
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
async def safe_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""
|
|
Handle unhandled exceptions safely.
|
|
|
|
- Logs full error details with context for debugging
|
|
- Returns generic error message to client (prevents info disclosure)
|
|
- Preserves HTTPException details (those are intended for clients)
|
|
"""
|
|
|
|
# Extract context from request state
|
|
user_id = getattr(request.state, "user", None)
|
|
user_id_str = str(user_id.id) if user_id and hasattr(user_id, "id") else "anonymous"
|
|
|
|
# Log full error details internally
|
|
logger.error(
|
|
f"Unhandled exception on {request.method} {request.url.path}",
|
|
exc_info=True,
|
|
extra={
|
|
"user_id": user_id_str,
|
|
"method": request.method,
|
|
"path": request.url.path,
|
|
"client_host": request.client.host if request.client else None,
|
|
},
|
|
)
|
|
|
|
# If it's an HTTPException, return it as-is (these are intentional)
|
|
if isinstance(exc, HTTPException):
|
|
return JSONResponse(
|
|
status_code=exc.status_code,
|
|
content={"detail": exc.detail},
|
|
)
|
|
|
|
# For all other exceptions, return generic message
|
|
return JSONResponse(
|
|
status_code=500,
|
|
content={
|
|
"detail": "An internal error occurred. Please contact support if the problem persists."
|
|
},
|
|
)
|