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