""" Einfacher In-Memory Rate-Limiter fuer den Login-Endpunkt (pro Quell-IP). Fuer einen Single-Process-ASGI-Deployment (siehe Konzept: kleine/mittlere Umgebung) ausreichend. Bei horizontaler Skalierung auf mehrere Prozesse/Hosts muss dies durch einen geteilten Store (z.B. Redis) ersetzt werden -- als Erweiterungspunkt bewusst hinter einer kleinen Klasse gekapselt. """ from __future__ import annotations import time from collections import defaultdict, deque from app.config import settings class SlidingWindowRateLimiter: def __init__(self, max_events: int, window_seconds: int) -> None: self.max_events = max_events self.window_seconds = window_seconds self._events: dict[str, deque[float]] = defaultdict(deque) def allow(self, key: str) -> bool: now = time.time() window = self._events[key] while window and now - window[0] > self.window_seconds: window.popleft() if len(window) >= self.max_events: return False window.append(now) return True # E8 (Umsetzungsauftrag Teil E): Werte konfigurierbar (settings.login_rate_ # limit_*, siehe app/config.py) statt fest verdrahtet -- ergaenzt den # Account-basierten Lockout in app/auth/routes.py. ACHTUNG: greift pro # Quell-IP. Sitzen mehrere interne Benutzer hinter derselben NAT-Adresse # (typischer Buero-Fall), teilen sie sich dieses Budget; das ist eine # bewusste Betriebsentscheidung und muss zusammen mit dem nginx-limit_req # und der fail2ban-Schwelle (beide ebenfalls konfigurierbar, siehe # ansible/roles/nginx_proxy und ansible/roles/fail2ban) im Betriebshandbuch # festgehalten werden. login_rate_limiter = SlidingWindowRateLimiter( max_events=settings.login_rate_limit_max_events, window_seconds=settings.login_rate_limit_window_s, )