Summary
All WebSocket connections to /api/v1/ws?ticket=... are rejected with 403 Forbidden. The WS icon in the dashboard is permanently red. No real-time updates reach the frontend.
Investigation Findings
The ticket-based WS auth flow works partially:
POST /api/v1/auth/ws-ticket succeeds (200, ticket issued, logged as api.ws.ticket_issued)
WebSocket /api/v1/ws?ticket=... is rejected with 403 before the handler runs
- No
api.ws.ticket_invalid, api.ws.connected, or any WS handler log lines appear -- the rejection happens at the Litestar framework level
What was ruled out
- Auth middleware exclude path:
^/api/v1/ws$ correctly matches scope["path"] (path without query string). Verified with build_exclude_path_pattern -- returns bypass=True for /api/v1/ws.
- Router guard:
require_password_changed returns immediately when user is None (line 198-199 of auth/controller.py). Not the cause.
- Channels plugin:
create_ws_route_handlers=False -- plugin doesn't create competing routes.
- Route registration:
WebSocketRoute at /api/v1/ws is registered with a valid route_handler.
- Nginx proxy: WS connections go directly to backend (port 8000), not through nginx. The 403 is from Litestar/uvicorn.
What needs investigation
The 403 comes from the Litestar ASGI pipeline itself, somewhere between the middleware stack and the WS handler. Possible causes:
- Litestar's internal WS upgrade handling may require a non-None
user scope even when middleware is excluded
- The
ScopeType.WEBSOCKET in the auth middleware scopes may cause an unexpected interaction
- A Litestar version-specific behavior with WS + auth middleware exclusion
Reproducer
synthorg start
# Open http://localhost:3000 -- WS icon is red
# Backend logs show repeated:
# INFO: 172.18.0.3:NNNNN - "WebSocket /api/v1/ws?ticket=..." 403
# INFO: connection rejected (403 Forbidden)
Acceptance Criteria
Summary
All WebSocket connections to
/api/v1/ws?ticket=...are rejected with 403 Forbidden. The WS icon in the dashboard is permanently red. No real-time updates reach the frontend.Investigation Findings
The ticket-based WS auth flow works partially:
POST /api/v1/auth/ws-ticketsucceeds (200, ticket issued, logged asapi.ws.ticket_issued)WebSocket /api/v1/ws?ticket=...is rejected with 403 before the handler runsapi.ws.ticket_invalid,api.ws.connected, or any WS handler log lines appear -- the rejection happens at the Litestar framework levelWhat was ruled out
^/api/v1/ws$correctly matchesscope["path"](path without query string). Verified withbuild_exclude_path_pattern-- returnsbypass=Truefor/api/v1/ws.require_password_changedreturns immediately whenuser is None(line 198-199 ofauth/controller.py). Not the cause.create_ws_route_handlers=False-- plugin doesn't create competing routes.WebSocketRouteat/api/v1/wsis registered with a validroute_handler.What needs investigation
The 403 comes from the Litestar ASGI pipeline itself, somewhere between the middleware stack and the WS handler. Possible causes:
userscope even when middleware is excludedScopeType.WEBSOCKETin the auth middleware scopes may cause an unexpected interactionReproducer
Acceptance Criteria
api.ws.connectedafter successful ticket validation