move app to gatehouse-app
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
"""Middleware package."""
|
||||
from gatehouse_app.middleware.request_id import RequestIDMiddleware
|
||||
from gatehouse_app.middleware.security_headers import SecurityHeadersMiddleware
|
||||
from gatehouse_app.middleware.cors import setup_cors
|
||||
|
||||
__all__ = ["RequestIDMiddleware", "SecurityHeadersMiddleware", "setup_cors"]
|
||||
@@ -0,0 +1,64 @@
|
||||
"""CORS middleware configuration."""
|
||||
from flask import request, make_response
|
||||
|
||||
|
||||
def setup_cors(app):
|
||||
"""
|
||||
Configure CORS for the application.
|
||||
|
||||
Args:
|
||||
app: Flask application instance
|
||||
"""
|
||||
|
||||
@app.before_request
|
||||
def handle_preflight():
|
||||
"""Handle CORS preflight OPTIONS requests."""
|
||||
if request.method == "OPTIONS":
|
||||
origin = request.headers.get("Origin")
|
||||
cors_origins = app.config.get("CORS_ORIGINS", [])
|
||||
|
||||
# Allow all origins if CORS_ORIGINS is "*" (string) or ["*"] (list with wildcard)
|
||||
allow_all = cors_origins == "*" or (isinstance(cors_origins, list) and "*" in cors_origins)
|
||||
|
||||
if allow_all:
|
||||
response = make_response("", 204)
|
||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Request-ID, Cache-Control, Pragma"
|
||||
response.headers["Access-Control-Max-Age"] = "3600"
|
||||
response.headers["Cache-Control"] = "no-cache, no-store"
|
||||
return response
|
||||
elif origin and origin in cors_origins:
|
||||
response = make_response("", 204)
|
||||
response.headers["Access-Control-Allow-Origin"] = origin
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Request-ID, Cache-Control, Pragma"
|
||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||
response.headers["Access-Control-Max-Age"] = "3600"
|
||||
response.headers["Cache-Control"] = "no-cache, no-store"
|
||||
return response
|
||||
|
||||
@app.after_request
|
||||
def after_request_cors(response):
|
||||
"""Add additional CORS headers if needed."""
|
||||
origin = request.headers.get("Origin")
|
||||
cors_origins = app.config.get("CORS_ORIGINS", [])
|
||||
|
||||
# Allow all origins if CORS_ORIGINS is "*" (string) or ["*"] (list with wildcard)
|
||||
allow_all = cors_origins == "*" or (isinstance(cors_origins, list) and "*" in cors_origins)
|
||||
|
||||
if allow_all:
|
||||
# When allowing all origins, set header to "*"
|
||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Request-ID, Cache-Control, Pragma"
|
||||
response.headers["Access-Control-Max-Age"] = "3600"
|
||||
elif origin and origin in cors_origins:
|
||||
# When allowing specific origins, echo the request origin
|
||||
response.headers["Access-Control-Allow-Origin"] = origin
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, PATCH, DELETE, OPTIONS"
|
||||
response.headers["Access-Control-Allow-Headers"] = "Content-Type, Authorization, X-Request-ID, Cache-Control, Pragma"
|
||||
response.headers["Access-Control-Allow-Credentials"] = "true"
|
||||
response.headers["Access-Control-Max-Age"] = "3600"
|
||||
|
||||
return response
|
||||
@@ -0,0 +1,38 @@
|
||||
"""Request ID middleware for request tracing."""
|
||||
import uuid
|
||||
from flask import g, request
|
||||
|
||||
|
||||
class RequestIDMiddleware:
|
||||
"""Middleware to add unique request ID to each request."""
|
||||
|
||||
def __init__(self, app=None):
|
||||
"""Initialize middleware."""
|
||||
self.app = app
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
def init_app(self, app):
|
||||
"""Initialize with Flask app."""
|
||||
app.before_request(self.before_request)
|
||||
app.after_request(self.after_request)
|
||||
|
||||
@staticmethod
|
||||
def before_request():
|
||||
"""Generate or extract request ID before request processing."""
|
||||
# Check if request already has an ID from client
|
||||
request_id = request.headers.get("X-Request-ID")
|
||||
|
||||
# Generate new ID if not provided
|
||||
if not request_id:
|
||||
request_id = str(uuid.uuid4())
|
||||
|
||||
# Store in Flask g object for access throughout request
|
||||
g.request_id = request_id
|
||||
|
||||
@staticmethod
|
||||
def after_request(response):
|
||||
"""Add request ID to response headers."""
|
||||
if hasattr(g, "request_id"):
|
||||
response.headers["X-Request-ID"] = g.request_id
|
||||
return response
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Security headers middleware."""
|
||||
from flask import request
|
||||
|
||||
|
||||
class SecurityHeadersMiddleware:
|
||||
"""Middleware to add security headers to responses."""
|
||||
|
||||
def __init__(self, app=None):
|
||||
"""Initialize middleware."""
|
||||
self.app = app
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
|
||||
def init_app(self, app):
|
||||
"""Initialize with Flask app."""
|
||||
app.after_request(self.add_security_headers)
|
||||
|
||||
@staticmethod
|
||||
def add_security_headers(response):
|
||||
"""Add security headers to response."""
|
||||
# Prevent MIME type sniffing
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
|
||||
# Enable XSS protection
|
||||
response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||
|
||||
# Prevent clickjacking
|
||||
response.headers["X-Frame-Options"] = "DENY"
|
||||
|
||||
# Strict Transport Security (HSTS)
|
||||
if request.is_secure:
|
||||
response.headers["Strict-Transport-Security"] = (
|
||||
"max-age=31536000; includeSubDomains"
|
||||
)
|
||||
|
||||
# Content Security Policy
|
||||
response.headers["Content-Security-Policy"] = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self' 'unsafe-inline'; "
|
||||
"style-src 'self' 'unsafe-inline'; "
|
||||
"img-src 'self' data: https:; "
|
||||
"font-src 'self' data:; "
|
||||
"connect-src 'self'"
|
||||
)
|
||||
|
||||
# Referrer Policy
|
||||
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
||||
|
||||
# Permissions Policy
|
||||
response.headers["Permissions-Policy"] = (
|
||||
"geolocation=(), microphone=(), camera=()"
|
||||
)
|
||||
|
||||
# Cache-Control: Allow OIDC endpoints to set their own Cache-Control
|
||||
# Only set no-cache for API responses that haven't set their own cache headers
|
||||
if "Cache-Control" not in response.headers:
|
||||
# Check if this is a JSON API response (shouldn't be cached)
|
||||
content_type = response.headers.get("Content-Type", "")
|
||||
if "application/json" in content_type:
|
||||
response.headers["Cache-Control"] = "no-cache, no-store"
|
||||
elif "text/html" not in content_type:
|
||||
# For non-HTML responses, add Pragma for HTTP/1.0 compatibility
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user