import random import time CODE_LENGTH = 6 CODE_TTL_SECONDS = 600 PENDING_MAX = 3 RATE_LIMIT_SECONDS = 3600 CODE_CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789" class WebhookPairing: def __init__(self): self._pending: dict[str, dict] = {} self._sender_last_code_at: dict[str, float] = {} def generate_code(self, sender_id: str) -> str | None: now = time.time() now_mono = time.monotonic() if sender_id in self._sender_last_code_at: if now - self._sender_last_code_at[sender_id] < RATE_LIMIT_SECONDS: return None self._evict_if_full(now_mono) code = "".join(random.choices(CODE_CHARS, k=CODE_LENGTH)) self._pending[sender_id] = {"code": code, "created_at": now_mono} self._sender_last_code_at[sender_id] = now return code def verify(self, sender_id: str, code: str) -> bool: entry = self._pending.get(sender_id) if not entry: return False if time.monotonic() - entry["created_at"] > CODE_TTL_SECONDS: del self._pending[sender_id] return False if entry["code"] != code.upper().strip(): return False del self._pending[sender_id] return True def _evict_if_full(self, now: float): if len(self._pending) < PENDING_MAX: return oldest = min(self._pending.values(), key=lambda p: p["created_at"]) if now - oldest["created_at"] < CODE_TTL_SECONDS: return expired_key = next(k for k, v in self._pending.items() if v == oldest) del self._pending[expired_key]