File size: 2,572 Bytes
92fd1a7
 
 
 
 
 
 
dbe78dd
92fd1a7
 
 
dbe78dd
 
 
 
 
 
 
 
 
 
 
 
 
92fd1a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
dbe78dd
 
 
 
 
92fd1a7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
"""Global error handling middleware."""
from fastapi import Request, HTTPException
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from starlette.exceptions import HTTPException as StarletteHTTPException
import logging
import traceback
import re

logger = logging.getLogger(__name__)

def redact_sensitive_data(data: str) -> str:
    """Redact sensitive information from logs."""
    # Redact Authorization headers
    data = re.sub(r'(Authorization["\']?:\s*["\']?Bearer\s+)[^\s"\']+', r'\1***', data, flags=re.IGNORECASE)
    
    # Redact bearer tokens in any form
    data = re.sub(r'(bearer["\']?:\s*["\']?)[^\s"\']+', r'\1***', data, flags=re.IGNORECASE)
    
    # Redact JWT tokens (3 base64 parts separated by dots)
    data = re.sub(r'\b[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b', '***JWT***', data)
    
    return data

async def http_exception_handler(request: Request, exc: HTTPException):
    """Handle HTTP exceptions."""
    return JSONResponse(
        status_code=exc.status_code,
        content={
            "error": exc.detail,
            "code": f"HTTP_{exc.status_code}"
        }
    )

async def validation_exception_handler(request: Request, exc: RequestValidationError):
    """Handle validation errors."""
    errors = []
    for error in exc.errors():
        field = ".".join(str(x) for x in error["loc"])
        errors.append(f"{field}: {error['msg']}")
    
    return JSONResponse(
        status_code=422,
        content={
            "error": "Validation failed",
            "details": "; ".join(errors),
            "code": "VALIDATION_ERROR"
        }
    )

async def general_exception_handler(request: Request, exc: Exception):
    """Handle unexpected exceptions."""
    # Redact sensitive data from error messages and traceback
    error_msg = redact_sensitive_data(str(exc))
    sanitized_traceback = redact_sensitive_data(traceback.format_exc())
    
    logger.error(f"Unexpected error: {error_msg}\n{sanitized_traceback}")
    
    return JSONResponse(
        status_code=500,
        content={
            "error": "Internal server error",
            "details": "An unexpected error occurred",
            "code": "INTERNAL_ERROR"
        }
    )

def register_exception_handlers(app):
    """Register all exception handlers with the app."""
    app.add_exception_handler(HTTPException, http_exception_handler)
    app.add_exception_handler(RequestValidationError, validation_exception_handler)
    app.add_exception_handler(Exception, general_exception_handler)