orchestrata_ultimate.gateway.auth_handler
Reference › orchestrata_ultimate › gateway › auth_handler
This module is the connection authentication flow of the gateway (SPEC-014 US-006). server is its only importer: it calls these functions for every new WebSocket before the socket is registered with the session connection manager. The authenticator classes it selects among live in auth; the narrative is in gateway.
create_authenticator is the single seam that maps GatewayConfig.auth_mode to an object: jwt gives TokenAuthenticator, static gives StaticTokenAuthenticator, hmac gives HmacAuthenticator. validate_via_mode then validates an AUTH payload against the selected mode, reading token for jwt and static, or client_id, timestamp, nonce and signature for hmac, and returns a claims dict with at least sub.
authenticate waits for the first frame under AUTH_TIMEOUT_SECONDS. A JSON-RPC 2.0 frame is passed to handle_jsonrpc_auth, which serves the auth.authenticate call used by the VizInt Command Center. Any other frame must be a WSMessage of type AUTH; the module validates it with validate_via_mode, replies with an authenticated message carrying the user id, and returns that id. A timeout or AuthenticationError sends an error through send_error, records the failure with the auth metrics in metrics, and returns None. try_connect_redis and try_connect_graph are lazy-connect helpers for the ring buffer and the audit writer.
At a glance
| Source | src/orchestrata_ultimate/gateway/auth_handler.py |
| Lines of code | 366 |
| Classes | 0 |
| Functions | 7 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.gateway.auth, orchestrata_ultimate.gateway.config, orchestrata_ultimate.gateway.exceptions, orchestrata_ultimate.gateway.metrics, orchestrata_ultimate.gateway.models |
| Imported by | orchestrata_ultimate.gateway.server |
| Tests that import it | tests/gateway/test_auth_handler_behavioral.py, tests/gateway/test_auth_handler_ws.py, tests/gateway/test_auth_static_hmac.py, tests/gateway/test_gateway_metrics.py, tests/generated_gateway/test_auth_handler_generated.py, tests/generated_recovery/test_gateway_auth_handler.py |
Class diagram
No classes; see Functions.
Dependency diagram
API
Functions
| Function | Signature | Summary |
|---|---|---|
create_authenticator |
create_authenticator(config: GatewayConfig) -> Authenticator |
Construct the authenticator selected by config.auth_mode. |
validate_via_mode traces |
async validate_via_mode(config: GatewayConfig, authenticator: Authenticator, payload: dict[str, Any]) -> dict[str, Any] |
Validate AUTH-frame payload against the mode-selected authenticator. |
send_error traces |
async send_error(websocket: Any, error_msg: str, correlation_id: str) -> None |
Send an error frame on websocket. |
authenticate traces |
async authenticate(websocket: Any, conn_id: str, config: GatewayConfig, auth: Authenticator, connections: Any, ws_map: dict[str, Any], handle_jsonrpc_fn: Any) -> str | None |
Wait for an AUTH frame and validate the token. |
handle_jsonrpc_auth traces |
async handle_jsonrpc_auth(websocket: Any, conn_id: str, data: dict[str, Any], config: GatewayConfig, auth: Authenticator, connections: Any, ws_map: dict[str, Any], handle_jsonrpc_fn: Any) -> str | None |
Handle JSON-RPC 2.0 authentication message. |
try_connect_redis traces |
async try_connect_redis(ring_buffer: Any) -> None |
Attempt Redis connection, log warning on failure. |
try_connect_graph |
async try_connect_graph(audit: Any) -> None |
Attempt graph connection, log warning on failure. |
Source
"""Authentication handlers for WebSocket gateway connections.
Extracted from server.py to keep WebSocketGateway under 300 lines.
Contains module-level functions for auth frame handling and
JSON-RPC 2.0 authentication dispatch.
"""
import asyncio
import json
import logging
from typing import Any, cast
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.gateway.auth import (
HmacAuthenticator,
StaticTokenAuthenticator,
TokenAuthenticator,
)
from orchestrata_ultimate.gateway.config import GatewayConfig
from orchestrata_ultimate.gateway.exceptions import AuthenticationError
from orchestrata_ultimate.gateway.metrics import auth_failure, auth_success
from orchestrata_ultimate.gateway.models import (
MessageType,
WSMessage,
)
logger = logging.getLogger(__name__)
AUTH_TIMEOUT_SECONDS = 10
#: Any of the three shipped authenticators (SPEC-014 FR-005/006/007) --
#: which one a given ``authenticate()``/``handle_jsonrpc_auth()`` call
#: receives is determined by ``config.auth_mode`` at construction time
#: (``create_authenticator`` below).
Authenticator = TokenAuthenticator | StaticTokenAuthenticator | HmacAuthenticator
def create_authenticator(config: GatewayConfig) -> Authenticator:
"""Construct the authenticator selected by ``config.auth_mode``.
The one seam that turns SPEC-014's three shipped auth strategies into a
single object callers can hold onto: "jwt" (default) ->
:class:`TokenAuthenticator`, "static" (FR-006) ->
:class:`StaticTokenAuthenticator`, "hmac" (FR-007) ->
:class:`HmacAuthenticator`. ``GatewayConfig.__post_init__`` already
validates ``auth_mode`` is one of the three, so no ``else`` branch is
reachable in production.
"""
if config.auth_mode == "static":
return StaticTokenAuthenticator(config)
if config.auth_mode == "hmac":
return HmacAuthenticator(config)
return TokenAuthenticator(config)
@traces(
spec="014",
story="US-006",
reqs=["REQ-014-091", "REQ-014-096"],
)
async def validate_via_mode(
config: GatewayConfig,
authenticator: Authenticator,
payload: dict[str, Any],
) -> dict[str, Any]:
"""Validate AUTH-frame *payload* against the mode-selected authenticator.
Dispatches on ``config.auth_mode`` (SPEC-014 FR-005/006/007): "jwt"
(default) validates a JWT via ``TokenAuthenticator.validate_token`` --
BYTE-IDENTICAL to the call this replaced, so default-mode behaviour is
unchanged; "static" reads ``payload["token"]`` and validates it via
:meth:`StaticTokenAuthenticator.authenticate` (FR-006); "hmac" reads
``client_id``/``timestamp``/``nonce``/``signature`` from *payload* and
validates via :meth:`HmacAuthenticator.authenticate` (FR-007).
Returns:
Claims dict with at least ``sub``.
Raises:
AuthenticationError: On any validation failure.
"""
if config.auth_mode == "static":
token = payload.get("token", "")
return await cast(StaticTokenAuthenticator, authenticator).authenticate(token)
if config.auth_mode == "hmac":
return await cast(HmacAuthenticator, authenticator).authenticate(
client_id=payload.get("client_id", ""),
timestamp=payload.get("timestamp", ""),
nonce=payload.get("nonce", ""),
signature=payload.get("signature", ""),
)
token = payload.get("token", "")
return cast(TokenAuthenticator, authenticator).validate_token(token)
@traces(spec="014", story="US-006", reqs=["REQ-014-089"])
async def send_error(
websocket: Any,
error_msg: str,
correlation_id: str,
) -> None:
"""Send an error frame on *websocket*.
Args:
websocket: Target WebSocket connection.
error_msg: Error description text.
correlation_id: ID of the originating message.
"""
resp = WSMessage(
msg_type=MessageType.ERROR,
payload={"error": error_msg},
correlation_id=correlation_id,
)
await websocket.send(json.dumps(resp.to_dict()))
@traces(
spec="014",
story="US-006",
reqs=["REQ-014-089"],
)
async def authenticate(
websocket: Any,
conn_id: str,
config: GatewayConfig,
auth: Authenticator,
connections: Any,
ws_map: dict[str, Any],
handle_jsonrpc_fn: Any,
) -> str | None:
"""Wait for an AUTH frame and validate the token.
Supports both internal WSMessage AUTH frames and JSON-RPC 2.0
auth.authenticate calls from the VizInt Command Center. The WSMessage
AUTH frame path validates via ``config.auth_mode`` (SPEC-014
FR-005/006/007 -- see :func:`validate_via_mode`), so it supports all
three shipped auth strategies; the payload shape carries whatever the
selected mode needs (``token`` for jwt/static, or
``client_id``/``timestamp``/``nonce``/``signature`` for hmac).
Args:
websocket: The websockets connection object.
conn_id: Connection identifier.
config: Gateway configuration.
auth: The authenticator :func:`create_authenticator` built for
``config.auth_mode`` -- must match that mode.
connections: Connection manager for registration.
ws_map: WebSocket connection map.
handle_jsonrpc_fn: Callback for JSON-RPC message handling.
Returns:
Authenticated user_id, or None on failure.
"""
try:
raw = await asyncio.wait_for(
websocket.recv(),
timeout=AUTH_TIMEOUT_SECONDS,
)
data = json.loads(raw)
# JSON-RPC 2.0 auth from VizInt Command Center
if "jsonrpc" in data:
return await handle_jsonrpc_auth(
websocket,
conn_id,
data,
config,
auth,
connections,
ws_map,
handle_jsonrpc_fn,
)
# Internal WSMessage AUTH frame
msg = WSMessage.from_dict(data)
if msg.msg_type != MessageType.AUTH:
await send_error(
websocket,
"Expected auth",
msg.msg_id,
)
auth_failure()
return None
claims = await validate_via_mode(config, auth, msg.payload)
user_id: str = str(claims["sub"])
resp = WSMessage(
msg_type=MessageType.AUTH,
payload={
"status": "authenticated",
"user_id": user_id,
},
correlation_id=msg.msg_id,
)
await websocket.send(json.dumps(resp.to_dict()))
auth_success()
return user_id
except (TimeoutError, AuthenticationError) as exc:
await send_error(websocket, str(exc), "")
auth_failure()
return None
@traces(spec="014", story="US-006", reqs=["REQ-014-086", "REQ-014-087"])
async def handle_jsonrpc_auth(
websocket: Any,
conn_id: str,
data: dict[str, Any],
config: GatewayConfig,
auth: Authenticator,
connections: Any,
ws_map: dict[str, Any],
handle_jsonrpc_fn: Any,
) -> str | None:
"""Handle JSON-RPC 2.0 authentication message.
Args:
websocket: The websockets connection object.
conn_id: Connection identifier.
data: Parsed JSON-RPC message dict.
config: Gateway configuration.
auth: The authenticator :func:`create_authenticator` built for
``config.auth_mode`` -- must match that mode. This path
previously fabricated a user id and returned authenticated=True
without ever reading a token (CRITICAL bypass, audit
2026-07-06).
connections: Connection manager for registration.
ws_map: WebSocket connection map.
handle_jsonrpc_fn: Callback for JSON-RPC dispatch.
Returns:
Authenticated user_id, or None on failure.
"""
method = data.get("method", "")
msg_id = data.get("id", "")
if method == "auth.authenticate":
if config.auth_mode != "jwt":
# FR-006/FR-007: static/hmac auth is validated over the
# WSMessage AUTH frame (validate_via_mode), which carries the
# richer payload shape hmac needs (client_id/timestamp/nonce/
# signature) -- this VizInt-specific JSON-RPC path only ever
# carried a single "token" field, so reject cleanly here
# instead of calling TokenAuthenticator.validate_token on a
# StaticTokenAuthenticator/HmacAuthenticator (AttributeError).
await websocket.send(
json.dumps(
{
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"authenticated": False,
"error": (
f"auth_mode={config.auth_mode!r} is not "
"supported over JSON-RPC auth.authenticate; "
"use the WSMessage AUTH frame"
),
},
}
)
)
return None
token = (data.get("params") or {}).get("token", "")
if token:
try:
claims = cast(TokenAuthenticator, auth).validate_token(token)
except AuthenticationError as exc:
await websocket.send(
json.dumps(
{
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"authenticated": False,
"error": str(exc),
},
}
)
)
auth_failure()
return None
user_id = str(claims["sub"])
elif config.allow_anonymous:
# Anonymous access is an EXPLICIT deployment opt-in, never the
# silent default.
user_id = f"vizint-{conn_id[:8]}"
else:
await websocket.send(
json.dumps(
{
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"authenticated": False,
"error": "Missing token",
},
}
)
)
auth_failure()
return None
await websocket.send(
json.dumps(
{
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"authenticated": True,
"client_id": user_id,
"scope": "read_write",
},
}
)
)
auth_success()
return user_id
# Non-auth JSON-RPC -- only allow if config permits
if not config.allow_anonymous:
logger.warning(
"Anonymous access denied for method %r on %s",
method,
conn_id,
)
await websocket.close(
4001,
"Authentication required",
)
return None
logger.warning(
"Anonymous access granted for non-auth JSON-RPC method %r on connection %s",
method,
conn_id,
)
user_id = f"anon-{conn_id[:8]}"
await connections.register(conn_id, user_id)
ws_map[conn_id] = websocket
await handle_jsonrpc_fn(websocket, conn_id, data)
return user_id
@traces(spec="014", story="US-008", reqs=["REQ-014-121"])
async def try_connect_redis(ring_buffer: Any) -> None:
"""Attempt Redis connection, log warning on failure.
Args:
ring_buffer: Redis ring buffer instance.
"""
try:
# Bounded: an unresolvable host must not stall gateway boot — DNS
# resolution of a dead name can hang for minutes (2026-07-15: LLMNR
# fallback on silently-dropped multicast turned a fast NXDOMAIN into
# a 120s+ hang and timed out the suite).
await asyncio.wait_for(ring_buffer.connect(), timeout=10.0)
except Exception:
logger.warning("Redis unavailable")
async def try_connect_graph(audit: Any) -> None:
"""Attempt graph connection, log warning on failure.
Args:
audit: Audit writer instance.
"""
try:
# Bounded for the same reason as try_connect_redis above.
await asyncio.wait_for(audit.connect(), timeout=10.0)
except Exception:
logger.warning("Graph unavailable")