move app to gatehouse-app

This commit is contained in:
2026-01-15 03:40:29 +10:30
parent 5e4cffcf73
commit 2c0aaf484b
69 changed files with 1569 additions and 294 deletions
+6
View File
@@ -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"]
+64
View File
@@ -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
+38
View File
@@ -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