73 lines
2.3 KiB
Python
73 lines
2.3 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import random
|
|
import time
|
|
|
|
from .format import mask_phone
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
PAIRING_CODE_LENGTH = 6
|
|
PAIRING_CODE_TTL = 600
|
|
PAIRING_CLEANUP_INTERVAL = 300
|
|
|
|
|
|
class AliyunSmsPairing:
|
|
def __init__(self):
|
|
self._codes: dict[str, dict] = {}
|
|
self._authorized: dict[str, set[str]] = {}
|
|
self._cleanup_task: asyncio.Task | None = None
|
|
|
|
async def generate_code(self, account_id: str) -> str:
|
|
code = str(random.randint(100000, 999999))
|
|
self._codes[code] = {
|
|
"account_id": account_id,
|
|
"expires_at": time.time() + PAIRING_CODE_TTL,
|
|
}
|
|
logger.info("阿里云短信配对码已生成: account=%s code=%s", account_id, code)
|
|
self._ensure_cleanup()
|
|
return code
|
|
|
|
async def verify_code(self, account_id: str, peer_id: str, code: str) -> bool:
|
|
entry = self._codes.get(code)
|
|
if not entry:
|
|
return False
|
|
if entry["account_id"] != account_id:
|
|
return False
|
|
if time.time() > entry["expires_at"]:
|
|
del self._codes[code]
|
|
return False
|
|
|
|
if account_id not in self._authorized:
|
|
self._authorized[account_id] = set()
|
|
self._authorized[account_id].add(peer_id)
|
|
|
|
del self._codes[code]
|
|
logger.info("阿里云短信配对验证成功: account=%s phone=%s", account_id, mask_phone(peer_id))
|
|
return True
|
|
|
|
def is_authorized(self, account_id: str, phone_number: str) -> bool:
|
|
return phone_number in self._authorized.get(account_id, set())
|
|
|
|
def revoke(self, account_id: str, phone_number: str):
|
|
if account_id in self._authorized:
|
|
self._authorized[account_id].discard(phone_number)
|
|
|
|
def _ensure_cleanup(self):
|
|
try:
|
|
loop = asyncio.get_running_loop()
|
|
if self._cleanup_task is None or self._cleanup_task.done():
|
|
self._cleanup_task = loop.create_task(self._cleanup_loop())
|
|
except RuntimeError:
|
|
pass
|
|
|
|
async def _cleanup_loop(self):
|
|
while True:
|
|
await asyncio.sleep(PAIRING_CLEANUP_INTERVAL)
|
|
now = time.time()
|
|
expired = [c for c, e in self._codes.items() if now > e["expires_at"]]
|
|
for code in expired:
|
|
del self._codes[code]
|