mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
Add capability RBAC, Alembic bootstrap, optional worker schedulers, and close public docs by default so lab installs stay usable without shared signing keys. Co-authored-by: Cursor <cursoragent@cursor.com>
49 lines
1.2 KiB
Python
49 lines
1.2 KiB
Python
"""Short-lived WebCRT WebSocket tickets (avoid putting JWT in query strings)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import secrets
|
|
import threading
|
|
import time
|
|
from dataclasses import dataclass
|
|
|
|
_TTL_SEC = 90
|
|
_lock = threading.Lock()
|
|
_tickets: dict[str, tuple[float, str, frozenset[str]]] = {}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TicketInfo:
|
|
user_id: str
|
|
scopes: frozenset[str]
|
|
|
|
|
|
def issue_ws_ticket(*, user_id: str, scopes: frozenset[str], ttl_sec: int = _TTL_SEC) -> tuple[str, int]:
|
|
tid = secrets.token_urlsafe(24)
|
|
exp = time.time() + max(15, int(ttl_sec))
|
|
with _lock:
|
|
_purge_locked()
|
|
_tickets[tid] = (exp, str(user_id), frozenset(scopes))
|
|
return tid, max(15, int(ttl_sec))
|
|
|
|
|
|
def consume_ws_ticket(ticket: str) -> TicketInfo | None:
|
|
raw = str(ticket or "").strip()
|
|
if not raw:
|
|
return None
|
|
with _lock:
|
|
_purge_locked()
|
|
row = _tickets.pop(raw, None)
|
|
if row is None:
|
|
return None
|
|
exp, user_id, scopes = row
|
|
if exp < time.time():
|
|
return None
|
|
return TicketInfo(user_id=user_id, scopes=scopes)
|
|
|
|
|
|
def _purge_locked() -> None:
|
|
now = time.time()
|
|
dead = [k for k, (exp, _, _) in _tickets.items() if exp < now]
|
|
for k in dead:
|
|
_tickets.pop(k, None)
|