cleanup
This commit is contained in:
@@ -33,17 +33,46 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
||||
- For POST requests to ``/api/auth/login`` and ``/api/auth/setup``
|
||||
a simple per-IP rate limiter is applied to mitigate brute-force
|
||||
attempts.
|
||||
- Rate limit records are periodically cleaned to prevent memory leaks.
|
||||
"""
|
||||
|
||||
def __init__(self, app: ASGIApp, *, rate_limit_per_minute: int = 5) -> None:
|
||||
def __init__(
|
||||
self, app: ASGIApp, *, rate_limit_per_minute: int = 5
|
||||
) -> None:
|
||||
super().__init__(app)
|
||||
# in-memory rate limiter: ip -> {count, window_start}
|
||||
self._rate: Dict[str, Dict[str, float]] = {}
|
||||
self.rate_limit_per_minute = rate_limit_per_minute
|
||||
self.window_seconds = 60
|
||||
# Track last cleanup time to prevent memory leaks
|
||||
self._last_cleanup = time.time()
|
||||
self._cleanup_interval = 300 # Clean every 5 minutes
|
||||
|
||||
def _cleanup_old_entries(self) -> None:
|
||||
"""Remove rate limit entries older than cleanup interval.
|
||||
|
||||
This prevents memory leaks from accumulating old IP addresses.
|
||||
"""
|
||||
now = time.time()
|
||||
if now - self._last_cleanup < self._cleanup_interval:
|
||||
return
|
||||
|
||||
# Remove entries older than 2x window to be safe
|
||||
cutoff = now - (self.window_seconds * 2)
|
||||
old_ips = [
|
||||
ip for ip, record in self._rate.items()
|
||||
if record["window_start"] < cutoff
|
||||
]
|
||||
for ip in old_ips:
|
||||
del self._rate[ip]
|
||||
|
||||
self._last_cleanup = now
|
||||
|
||||
async def dispatch(self, request: Request, call_next: Callable):
|
||||
path = request.url.path or ""
|
||||
|
||||
# Periodically clean up old rate limit entries
|
||||
self._cleanup_old_entries()
|
||||
|
||||
# Apply rate limiting to auth endpoints that accept credentials
|
||||
if (
|
||||
@@ -75,7 +104,8 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
||||
},
|
||||
)
|
||||
|
||||
# If Authorization header present try to decode token and attach session
|
||||
# If Authorization header present try to decode token
|
||||
# and attach session
|
||||
auth_header = request.headers.get("authorization")
|
||||
if auth_header and auth_header.lower().startswith("bearer "):
|
||||
token = auth_header.split(" ", 1)[1].strip()
|
||||
@@ -87,7 +117,9 @@ class AuthMiddleware(BaseHTTPMiddleware):
|
||||
# Invalid token: if this is a protected API path, reject.
|
||||
# For public/auth endpoints let the dependency system handle
|
||||
# optional auth and return None.
|
||||
if path.startswith("/api/") and not path.startswith("/api/auth"):
|
||||
is_api = path.startswith("/api/")
|
||||
is_auth = path.startswith("/api/auth")
|
||||
if is_api and not is_auth:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
content={"detail": "Invalid token"}
|
||||
|
||||
Reference in New Issue
Block a user