First product

This commit is contained in:
Justin Visser 2026-02-17 14:51:25 +01:00
parent 778da9e2fc
commit 4433d7d2c4
157 changed files with 23975 additions and 0 deletions

62
app/auth/rate_limit.py Normal file
View file

@ -0,0 +1,62 @@
from __future__ import annotations
from collections import defaultdict, deque
from collections.abc import Callable
from dataclasses import dataclass
from math import ceil
from threading import Lock
from time import monotonic
@dataclass(frozen=True)
class RateLimitDecision:
allowed: bool
retry_after_seconds: int = 0
class SlidingWindowRateLimiter:
def __init__(
self,
*,
max_attempts: int,
window_seconds: int,
now: Callable[[], float] = monotonic,
) -> None:
self._max_attempts = max_attempts
self._window_seconds = window_seconds
self._now = now
self._attempts: dict[str, deque[float]] = defaultdict(deque)
self._lock = Lock()
def check(self, key: str) -> RateLimitDecision:
with self._lock:
now_value = self._now()
attempts = self._attempts[key]
self._trim_expired(attempts, now_value)
if len(attempts) >= self._max_attempts:
retry_after = self._window_seconds - (now_value - attempts[0])
return RateLimitDecision(
allowed=False,
retry_after_seconds=max(1, ceil(retry_after)),
)
return RateLimitDecision(allowed=True)
def record_failure(self, key: str) -> None:
with self._lock:
now_value = self._now()
attempts = self._attempts[key]
self._trim_expired(attempts, now_value)
attempts.append(now_value)
def reset(self, key: str) -> None:
with self._lock:
self._attempts.pop(key, None)
def clear_all(self) -> None:
with self._lock:
self._attempts.clear()
def _trim_expired(self, attempts: deque[float], now_value: float) -> None:
while attempts and now_value - attempts[0] >= self._window_seconds:
attempts.popleft()