Commit graph

1 commit

Author SHA1 Message Date
Ben
bb72e4614a feat(dashboard-auth): _ws_auth_ok helper + ticket auth on all 4 WS endpoints
Phase 5 task 5.2. Four WebSocket endpoints — /api/pty, /api/ws, /api/pub,
/api/events — previously authed with the same constant-time check against
`_SESSION_TOKEN`. Replaced with a single helper that branches on
`app.state.auth_required`:

  Loopback / --insecure: legacy ?token=<_SESSION_TOKEN> path (unchanged).
  Gated:                  ?ticket=<single-use> consumed against the
                          dashboard-auth ticket store.

Critical security property: gated mode UNCONDITIONALLY rejects the
?token= path. A leaked _SESSION_TOKEN value from a log line is not
replayable for WS access in gated deployments.

`_build_sidecar_url` now branches too: loopback uses the legacy token;
gated mode mints a server-internal ticket via mint_ticket() with
pseudo-user 'pty-sidecar' / provider 'server-internal' so audit logs can
distinguish PTY-internal sidecar tickets from browser tickets. PTY
children open /api/pub exactly once at startup so single-use suffices.

Ticket rejections audit-log as WS_TICKET_REJECTED with truncated reason
+ client IP + WS path. Operators debugging 'WS keeps closing' issues see
which endpoint and why.

17 new tests:
- POST /api/auth/ws-ticket: 200 with cookie, 401/302 without, distinct
  per call, GET-not-allowed.
- _ws_auth_ok loopback: token accept/reject, missing-token reject,
  ticket-param-ignored.
- _ws_auth_ok gated: ticket accept, single-use rejection, unknown reject,
  legacy-token-rejected-in-gated assertion, audit-log emission.
- _build_sidecar_url: loopback uses token=, gated uses ticket=, no-bound
  returns None.
2026-05-21 16:02:29 +10:00