from __future__ import annotations import logging import secrets import string import time as _time from yuxi.channel.extensions.rocketchat.security import normalize_allow_entry logger = logging.getLogger(__name__) PAIRING_CODE_LENGTH = 8 PAIRING_CODE_EXPIRY_SECONDS = 600 class RocketChatPairingAdapter: def __init__(self): self._pending: dict[str, tuple[str, float]] = {} def generate_code(self, peer_id: str) -> str: code = _generate_pairing_code() self._pending[peer_id] = (code, _time.time()) return code def verify_code(self, peer_id: str, code: str) -> bool: if peer_id not in self._pending: return False stored_code, timestamp = self._pending[peer_id] now = _time.time() if now - timestamp > PAIRING_CODE_EXPIRY_SECONDS: self._pending.pop(peer_id, None) return False if stored_code != code.strip(): return False self._pending.pop(peer_id, None) return True def get_pending_code(self, peer_id: str) -> str | None: if peer_id not in self._pending: return None code, timestamp = self._pending[peer_id] if _time.time() - timestamp > PAIRING_CODE_EXPIRY_SECONDS: self._pending.pop(peer_id, None) return None return code def clear_expired(self) -> None: now = _time.time() expired = [pid for pid, (_, ts) in self._pending.items() if now - ts > PAIRING_CODE_EXPIRY_SECONDS] for pid in expired: self._pending.pop(pid, None) def is_paired(self, peer_id: str, allow_list: list[str]) -> bool: normalized = [normalize_allow_entry(e) for e in allow_list] return peer_id in normalized def _generate_pairing_code() -> str: chars = string.ascii_uppercase + string.digits raw = "".join(secrets.choice(chars) for _ in range(PAIRING_CODE_LENGTH)) return f"{raw[:4]}-{raw[4:]}"