"""Where a websocket's credential comes from, and how it stays out of the URL. A client that can set headers sends `Authorization: Bearer` on the handshake: the worker and the SDK. A browser cannot, so it trades its token for a ticket over an authenticated REST call and brings the ticket instead. A ticket is good once, for thirty seconds, so the URL every proxy logs is worth nothing after. The engine is one process, so the tickets live in its memory: the tunnel connector, which serves the live socket inline, redeems them from the same place. """ from __future__ import annotations import secrets import threading from collections.abc import Mapping from time import monotonic #: How long a ticket waits to be used: the length of one connect. TICKET_TTL_S = 30.0 _tickets: dict[str, tuple[str, float]] = {} _lock = threading.Lock() def issue_ticket(token: str) -> str: """A one-time ticket standing in for *token*.""" ticket = secrets.token_urlsafe(32) now = monotonic() with _lock: # Swept here rather than on a timer: issuing is what fills the table. for stale in [key for key, (_, due) in _tickets.items() if due <= now]: del _tickets[stale] _tickets[ticket] = (token, now + TICKET_TTL_S) return ticket def redeem_ticket(ticket: str) -> str: """The token a ticket stands for, once; empty if it is unknown or stale.""" with _lock: entry = _tickets.pop(ticket, None) if entry is None or entry[1] <= monotonic(): return "" return entry[0] def socket_token(headers: Mapping[str, str], ticket: str = "", token: str = "") -> str: """The credential a handshake carries: a header, then a ticket, then a token. `token` is the query parameter clients sent before the other two existed. It is read for one more release so an engine can be upgraded before its workers. New clients should use the Authorization header. """ header = headers.get("authorization") or "" if header.lower().startswith("bearer "): return header[7:] if ticket: return redeem_ticket(ticket) return token