forked from andika/membership-be
Security Hardening
This commit is contained in:
66
server.py
66
server.py
@@ -60,11 +60,29 @@ async def lifespan(app: FastAPI):
|
|||||||
# Shutdown
|
# Shutdown
|
||||||
logger.info("Application shutdown")
|
logger.info("Application shutdown")
|
||||||
|
|
||||||
|
# Environment detection
|
||||||
|
ENVIRONMENT = os.environ.get('ENVIRONMENT', 'development')
|
||||||
|
IS_PRODUCTION = ENVIRONMENT == 'production'
|
||||||
|
|
||||||
|
# Security: Disable API documentation in production
|
||||||
|
if IS_PRODUCTION:
|
||||||
|
print("🔒 Production mode: API documentation disabled")
|
||||||
|
app_config = {
|
||||||
|
"lifespan": lifespan,
|
||||||
|
"root_path": "/membership",
|
||||||
|
"docs_url": None, # Disable /docs
|
||||||
|
"redoc_url": None, # Disable /redoc
|
||||||
|
"openapi_url": None # Disable /openapi.json
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print("🔓 Development mode: API documentation enabled at /docs and /redoc")
|
||||||
|
app_config = {
|
||||||
|
"lifespan": lifespan,
|
||||||
|
"root_path": "/membership"
|
||||||
|
}
|
||||||
|
|
||||||
# Create the main app
|
# Create the main app
|
||||||
app = FastAPI(
|
app = FastAPI(**app_config)
|
||||||
lifespan=lifespan,
|
|
||||||
root_path="/membership" # Configure for serving under /membership path
|
|
||||||
)
|
|
||||||
|
|
||||||
# Create a router with the /api prefix
|
# Create a router with the /api prefix
|
||||||
api_router = APIRouter(prefix="/api")
|
api_router = APIRouter(prefix="/api")
|
||||||
@@ -6262,4 +6280,42 @@ app.add_middleware(
|
|||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
expose_headers=["*"],
|
expose_headers=["*"],
|
||||||
max_age=600, # Cache preflight requests for 10 minutes
|
max_age=600, # Cache preflight requests for 10 minutes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Security Headers Middleware
|
||||||
|
@app.middleware("http")
|
||||||
|
async def add_security_headers(request: Request, call_next):
|
||||||
|
response = await call_next(request)
|
||||||
|
|
||||||
|
# Security headers to protect against common vulnerabilities
|
||||||
|
security_headers = {
|
||||||
|
# Prevent clickjacking attacks
|
||||||
|
"X-Frame-Options": "DENY",
|
||||||
|
|
||||||
|
# Prevent MIME type sniffing
|
||||||
|
"X-Content-Type-Options": "nosniff",
|
||||||
|
|
||||||
|
# Enable XSS protection in older browsers
|
||||||
|
"X-XSS-Protection": "1; mode=block",
|
||||||
|
|
||||||
|
# Control referrer information
|
||||||
|
"Referrer-Policy": "strict-origin-when-cross-origin",
|
||||||
|
|
||||||
|
# Permissions policy (formerly Feature-Policy)
|
||||||
|
"Permissions-Policy": "geolocation=(), microphone=(), camera=()",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add HSTS header in production (force HTTPS)
|
||||||
|
if IS_PRODUCTION:
|
||||||
|
security_headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
|
||||||
|
|
||||||
|
# Apply all security headers
|
||||||
|
for header, value in security_headers.items():
|
||||||
|
response.headers[header] = value
|
||||||
|
|
||||||
|
# Remove server identification headers
|
||||||
|
response.headers.pop("Server", None)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
print(f"✓ Security headers configured (Production: {IS_PRODUCTION})")
|
||||||
Reference in New Issue
Block a user