Source code for litestar_security.websocket._bindings

"""Handshake, binding, and refresh contracts for a secured connection.

These are the value types and application-implemented ports the rest of the
package is expressed in, so they sit below configuration.
"""

from dataclasses import dataclass, field
from typing import Protocol, TypeVar, runtime_checkable

from litestar_security.context import AuthorizationSnapshot, Principal
from litestar_security.websocket._internal import strict_text

__all__ = ("AuthorizationSnapshotRefresher", "WebSocketBinding", "WebSocketHandshake", "WebSocketRevocationSource")

UserT = TypeVar("UserT")


[docs] @dataclass(frozen=True, slots=True) class WebSocketHandshake: """Describe credential transports presented by one WebSocket handshake.""" origin: str | None uses_cookie_credentials: bool uses_authorization_header: bool connect_token: str | None = field(repr=False)
[docs] @dataclass(frozen=True, slots=True) class WebSocketBinding: """Secret-free identity and route binding supplied to revocation hooks.""" connection_id: str subject_id: str credential_ids: frozenset[str] session_id: str | None route_name: str def __post_init__(self) -> None: """Normalize stable binding identifiers.""" if ( not strict_text(self.connection_id) or not strict_text(self.subject_id) or not strict_text(self.route_name) or any(not strict_text(value) for value in self.credential_ids) or (self.session_id is not None and not strict_text(self.session_id)) ): message = "WebSocket revocation binding is invalid" raise ValueError(message) object.__setattr__(self, "credential_ids", frozenset(self.credential_ids))
[docs] @runtime_checkable class WebSocketRevocationSource(Protocol): """Event-driven, secret-free application hook for one binding's revocation."""
[docs] async def wait(self, binding: WebSocketBinding) -> None: """Block without polling until the supplied connection binding is revoked. Args: binding: The secret-free identity and route binding to supervise. Returns: ``None`` only after a genuine revocation of ``binding``. Raises: Exception: When supervision fails. The connection lifetime treats this as unavailable and closes the connection. """ ... # pragma: no cover
[docs] @runtime_checkable class AuthorizationSnapshotRefresher(Protocol[UserT]): """Application hook returning one detached immutable authorization snapshot."""
[docs] async def refresh( self, *, principal: Principal[UserT], previous: AuthorizationSnapshot, route_name: str ) -> AuthorizationSnapshot: """Resolve and return a new detached authorization snapshot. Args: principal: The authenticated principal for the connection. previous: The prior immutable snapshot, which is never mutated. route_name: The bound application route name. Returns: A new detached ``AuthorizationSnapshot``; any other runtime type is treated as unavailable by connection lifetime supervision. Raises: Exception: When refresh fails. Connection lifetime supervision treats this as unavailable and closes the connection. """ ... # pragma: no cover