forked from claw/flaskpaste
refactor: code consistency and best practices
- add type hints to error handlers in app/__init__.py - add docstrings to nested callback functions - remove deprecated X-XSS-Protection header (superseded by CSP) - fix typo in cleanup log message (entr(ies) -> entries) - standardize loop variable naming in fpaste CLI - update test for intentional header removal
This commit is contained in:
@@ -6,6 +6,7 @@ import sys
|
||||
import uuid
|
||||
|
||||
from flask import Flask, Response, g, request
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from app.config import VERSION, config
|
||||
|
||||
@@ -39,12 +40,14 @@ def setup_security_headers(app: Flask) -> None:
|
||||
|
||||
@app.after_request
|
||||
def add_security_headers(response: Response) -> Response:
|
||||
"""Apply security headers to response.
|
||||
|
||||
Headers follow OWASP recommendations for API security.
|
||||
"""
|
||||
# Prevent MIME type sniffing
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
# Prevent clickjacking
|
||||
response.headers["X-Frame-Options"] = "DENY"
|
||||
# XSS protection (legacy but still useful)
|
||||
response.headers["X-XSS-Protection"] = "1; mode=block"
|
||||
# Referrer policy
|
||||
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
|
||||
# Content Security Policy (restrictive for API)
|
||||
@@ -64,8 +67,8 @@ def setup_request_id(app: Flask) -> None:
|
||||
"""Add request ID tracking for log correlation and tracing."""
|
||||
|
||||
@app.before_request
|
||||
def assign_request_id():
|
||||
# Use incoming X-Request-ID from proxy, or generate a new one
|
||||
def assign_request_id() -> None:
|
||||
"""Assign unique request ID from header or generate new UUID."""
|
||||
request_id = request.headers.get("X-Request-ID", "").strip()
|
||||
if not request_id:
|
||||
request_id = str(uuid.uuid4())
|
||||
@@ -73,7 +76,7 @@ def setup_request_id(app: Flask) -> None:
|
||||
|
||||
@app.after_request
|
||||
def add_request_id_header(response: Response) -> Response:
|
||||
# Echo request ID back to client for tracing
|
||||
"""Echo request ID in response header and log access."""
|
||||
request_id = getattr(g, "request_id", None)
|
||||
if request_id:
|
||||
response.headers["X-Request-ID"] = request_id
|
||||
@@ -90,11 +93,12 @@ def setup_request_id(app: Flask) -> None:
|
||||
|
||||
|
||||
def setup_error_handlers(app: Flask) -> None:
|
||||
"""Register global error handlers."""
|
||||
"""Register global error handlers with JSON responses."""
|
||||
import json
|
||||
|
||||
@app.errorhandler(400)
|
||||
def bad_request(error):
|
||||
def bad_request(error: HTTPException) -> Response:
|
||||
"""Handle 400 Bad Request errors."""
|
||||
app.logger.warning("Bad request: %s [rid=%s]", request.path, getattr(g, "request_id", "-"))
|
||||
return Response(
|
||||
json.dumps({"error": "Bad request"}),
|
||||
@@ -103,7 +107,8 @@ def setup_error_handlers(app: Flask) -> None:
|
||||
)
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(error):
|
||||
def not_found(error: HTTPException) -> Response:
|
||||
"""Handle 404 Not Found errors."""
|
||||
return Response(
|
||||
json.dumps({"error": "Not found"}),
|
||||
status=404,
|
||||
@@ -111,7 +116,8 @@ def setup_error_handlers(app: Flask) -> None:
|
||||
)
|
||||
|
||||
@app.errorhandler(429)
|
||||
def rate_limit_exceeded(error):
|
||||
def rate_limit_exceeded(error: HTTPException) -> Response:
|
||||
"""Handle 429 Too Many Requests errors."""
|
||||
app.logger.warning(
|
||||
"Rate limit exceeded: %s from %s [rid=%s]",
|
||||
request.path,
|
||||
@@ -125,7 +131,8 @@ def setup_error_handlers(app: Flask) -> None:
|
||||
)
|
||||
|
||||
@app.errorhandler(500)
|
||||
def internal_error(error):
|
||||
def internal_error(error: HTTPException) -> Response:
|
||||
"""Handle 500 Internal Server errors."""
|
||||
app.logger.error(
|
||||
"Internal error: %s - %s [rid=%s]",
|
||||
request.path,
|
||||
@@ -139,7 +146,8 @@ def setup_error_handlers(app: Flask) -> None:
|
||||
)
|
||||
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(error):
|
||||
def handle_exception(error: Exception) -> Response:
|
||||
"""Handle unhandled exceptions with generic 500 response."""
|
||||
app.logger.exception(
|
||||
"Unhandled exception: %s [rid=%s]", str(error), getattr(g, "request_id", "-")
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ def run_scheduled_cleanup():
|
||||
|
||||
count = cleanup_rate_limits()
|
||||
if count > 0:
|
||||
current_app.logger.info(f"Cleaned up {count} rate limit entr(ies)")
|
||||
current_app.logger.info(f"Cleaned up {count} rate limit entries")
|
||||
|
||||
|
||||
from app.api import routes # noqa: E402, F401
|
||||
|
||||
Reference in New Issue
Block a user