updates
This commit is contained in:
Binary file not shown.
@@ -28,6 +28,10 @@ class Settings(BaseSettings):
|
||||
CORS_ORIGINS: List[str] = Field(default_factory=lambda: ['http://localhost:5173', 'http://localhost:3000', 'http://127.0.0.1:5173'], description='Allowed CORS origins')
|
||||
RATE_LIMIT_ENABLED: bool = Field(default=True, description='Enable rate limiting')
|
||||
RATE_LIMIT_PER_MINUTE: int = Field(default=60, description='Requests per minute per IP')
|
||||
RATE_LIMIT_ADMIN_PER_MINUTE: int = Field(default=300, description='Requests per minute for admin users')
|
||||
RATE_LIMIT_STAFF_PER_MINUTE: int = Field(default=200, description='Requests per minute for staff users')
|
||||
RATE_LIMIT_ACCOUNTANT_PER_MINUTE: int = Field(default=200, description='Requests per minute for accountant users')
|
||||
RATE_LIMIT_CUSTOMER_PER_MINUTE: int = Field(default=100, description='Requests per minute for customer users')
|
||||
CSRF_PROTECTION_ENABLED: bool = Field(default=True, description='Enable CSRF protection')
|
||||
HSTS_PRELOAD_ENABLED: bool = Field(default=False, description='Enable HSTS preload directive (requires domain submission to hstspreload.org)')
|
||||
LOG_LEVEL: str = Field(default='INFO', description='Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL')
|
||||
|
||||
Binary file not shown.
51
Backend/src/shared/middleware/api_versioning.py
Normal file
51
Backend/src/shared/middleware/api_versioning.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""
|
||||
API versioning middleware for backward compatibility.
|
||||
"""
|
||||
from fastapi import Request, HTTPException, status
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import Response
|
||||
from typing import Callable
|
||||
import re
|
||||
from ...shared.config.logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
class APIVersioningMiddleware(BaseHTTPMiddleware):
|
||||
"""Middleware to handle API versioning."""
|
||||
|
||||
def __init__(self, app, default_version: str = "v1"):
|
||||
super().__init__(app)
|
||||
self.default_version = default_version
|
||||
self.version_pattern = re.compile(r'/api/v(\d+)/')
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable) -> Response:
|
||||
"""Process request and handle versioning."""
|
||||
path = request.url.path
|
||||
|
||||
# Check if path starts with /api
|
||||
if path.startswith('/api'):
|
||||
# Extract version from path
|
||||
version_match = self.version_pattern.search(path)
|
||||
|
||||
if version_match:
|
||||
version = version_match.group(1)
|
||||
# Store version in request state
|
||||
request.state.api_version = f"v{version}"
|
||||
# Remove version from path for routing
|
||||
new_path = self.version_pattern.sub('/api/', path)
|
||||
request.scope['path'] = new_path
|
||||
elif path.startswith('/api/') and not path.startswith('/api/v'):
|
||||
# No version specified, use default
|
||||
request.state.api_version = self.default_version
|
||||
else:
|
||||
# Health check or other non-versioned endpoints
|
||||
request.state.api_version = None
|
||||
|
||||
response = await call_next(request)
|
||||
|
||||
# Add version header to response
|
||||
if hasattr(request.state, 'api_version') and request.state.api_version:
|
||||
response.headers['X-API-Version'] = request.state.api_version
|
||||
|
||||
return response
|
||||
|
||||
111
Backend/src/shared/utils/audit_decorator.py
Normal file
111
Backend/src/shared/utils/audit_decorator.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""
|
||||
Decorator for automatic audit logging of financial operations.
|
||||
"""
|
||||
from functools import wraps
|
||||
from typing import Callable, Any
|
||||
from fastapi import Request
|
||||
from sqlalchemy.orm import Session
|
||||
from ...analytics.services.audit_service import audit_service
|
||||
from ...shared.config.logging_config import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
def audit_financial_operation(action: str, resource_type: str):
|
||||
"""
|
||||
Decorator to automatically log financial operations to audit trail.
|
||||
|
||||
Usage:
|
||||
@audit_financial_operation('payment_refunded', 'payment')
|
||||
async def refund_payment(...):
|
||||
...
|
||||
"""
|
||||
def decorator(func: Callable) -> Callable:
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
# Extract request and db from function arguments
|
||||
request: Request = None
|
||||
db: Session = None
|
||||
current_user = None
|
||||
|
||||
# Find request and db in kwargs or args
|
||||
for arg in list(args) + list(kwargs.values()):
|
||||
if isinstance(arg, Request):
|
||||
request = arg
|
||||
elif isinstance(arg, Session):
|
||||
db = arg
|
||||
elif hasattr(arg, 'id') and hasattr(arg, 'email'): # Likely a User object
|
||||
current_user = arg
|
||||
|
||||
# Get request from kwargs if not found
|
||||
if not request and 'request' in kwargs:
|
||||
request = kwargs['request']
|
||||
if not db and 'db' in kwargs:
|
||||
db = kwargs['db']
|
||||
if not current_user and 'current_user' in kwargs:
|
||||
current_user = kwargs['current_user']
|
||||
|
||||
# Extract resource_id from function arguments if available
|
||||
resource_id = None
|
||||
if 'id' in kwargs:
|
||||
resource_id = kwargs['id']
|
||||
elif len(args) > 0 and isinstance(args[0], int):
|
||||
resource_id = args[0]
|
||||
|
||||
# Get client info
|
||||
client_ip = None
|
||||
user_agent = None
|
||||
request_id = None
|
||||
if request:
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
# Execute the function
|
||||
result = await func(*args, **kwargs)
|
||||
|
||||
# Log successful operation
|
||||
if db and current_user:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
user_id=current_user.id if current_user else None,
|
||||
resource_id=resource_id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'function': func.__name__,
|
||||
'result': 'success'
|
||||
},
|
||||
status='success'
|
||||
)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
# Log failed operation
|
||||
if db and current_user:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action=action,
|
||||
resource_type=resource_type,
|
||||
user_id=current_user.id if current_user else None,
|
||||
resource_id=resource_id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'function': func.__name__,
|
||||
'result': 'failed',
|
||||
'error': str(e)
|
||||
},
|
||||
status='failed',
|
||||
error_message=str(e)
|
||||
)
|
||||
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
Reference in New Issue
Block a user