""" Security middleware — rate limiting and session fingerprint validation. """ from django.http import JsonResponse from django.utils.deprecation import MiddlewareMixin from apps.security.services.rate_limit_service import ( build_login_key, check_rate_limit, ) from core.exceptions import RateLimitError from core.utils.ip import get_client_ip, get_device_fingerprint class RateLimitMiddleware(MiddlewareMixin): """ Rate-limit login attempts by IP address. Only applies to POST requests on the login URL. """ LOGIN_PATHS = ["/accounts/login/"] def process_request(self, request): if request.method != "POST": return None if request.path not in self.LOGIN_PATHS: return None ip = get_client_ip(request) key = build_login_key(ip) try: check_rate_limit(key) except RateLimitError as e: return JsonResponse( {"error": str(e)}, status=429, ) return None class SessionFingerprintMiddleware(MiddlewareMixin): """ Bind sessions to device fingerprint. Validates that the current request's fingerprint matches the session fingerprint. Logs a security event on mismatch. """ def process_request(self, request): if not hasattr(request, "user") or not request.user.is_authenticated: return None current_fingerprint = get_device_fingerprint(request) stored_fingerprint = request.session.get("device_fingerprint") if not stored_fingerprint: # New or legacy session - bind it now request.session["device_fingerprint"] = current_fingerprint elif stored_fingerprint != current_fingerprint: # Fingerprint mismatch! from django.contrib.auth import logout from django.core.exceptions import PermissionDenied logout(request) raise PermissionDenied("Session invalid: Device fingerprint mismatch.") # Store fingerprint on request for downstream use request.device_fingerprint = current_fingerprint return None