fix: resolve WebSocket connection issues on Windows

The WebSocket endpoint /pico/ws was previously requiring dashboard session
cookies, which are not always sent by browsers (especially on Windows)
during the WebSocket handshake. This change makes /pico/ws a public path
in the dashboard middleware, allowing it to fall back to its own
subprotocol-based token authentication.

Additionally, standardize all occurrences of 'Sec-WebSocket-Protocol' to
the canonical Go form 'Sec-Websocket-Protocol' in pkg/channels/pico/pico.go
to ensure header consistency and avoid potential protocol mismatches in
different network stacks.

- Modified web/backend/middleware/launcher_dashboard_auth.go to allow /pico/ws.
- Updated pkg/channels/pico/pico.go to use canonical header casing.
- Verified changes with existing tests and code review.

Co-authored-by: nglmercer <128845117+nglmercer@users.noreply.github.com>
This commit is contained in:
google-labs-jules[bot] 2026-04-05 14:34:18 +00:00
parent 15a70ac45c
commit 9086f0483f
2 changed files with 6 additions and 3 deletions

View file

@ -349,7 +349,7 @@ func (c *PicoChannel) handleWebSocket(w http.ResponseWriter, r *http.Request) {
// Echo the matched subprotocol back so the browser accepts the upgrade. // Echo the matched subprotocol back so the browser accepts the upgrade.
var responseHeader http.Header var responseHeader http.Header
if proto := c.matchedSubprotocol(r); proto != "" { if proto := c.matchedSubprotocol(r); proto != "" {
responseHeader = http.Header{"Sec-WebSocket-Protocol": {proto}} responseHeader = http.Header{"Sec-Websocket-Protocol": {proto}}
} }
conn, err := c.upgrader.Upgrade(w, r, responseHeader) conn, err := c.upgrader.Upgrade(w, r, responseHeader)
@ -387,7 +387,7 @@ func (c *PicoChannel) handleWebSocket(w http.ResponseWriter, r *http.Request) {
// authenticate checks the request for a valid token: // authenticate checks the request for a valid token:
// 1. Authorization: Bearer <token> header // 1. Authorization: Bearer <token> header
// 2. Sec-WebSocket-Protocol "token.<value>" (for browsers that can't set headers) // 2. Sec-Websocket-Protocol "token.<value>" (for browsers that can't set headers)
// 3. Query parameter "token" (only when AllowTokenQuery is on) // 3. Query parameter "token" (only when AllowTokenQuery is on)
func (c *PicoChannel) authenticate(r *http.Request) bool { func (c *PicoChannel) authenticate(r *http.Request) bool {
token := c.config.Token.String() token := c.config.Token.String()
@ -403,7 +403,7 @@ func (c *PicoChannel) authenticate(r *http.Request) bool {
} }
} }
// Check Sec-WebSocket-Protocol subprotocol ("token.<value>") // Check Sec-Websocket-Protocol subprotocol ("token.<value>")
if c.matchedSubprotocol(r) != "" { if c.matchedSubprotocol(r) != "" {
return true return true
} }

View file

@ -166,6 +166,9 @@ func isPublicLauncherDashboardPath(method, p string) bool {
if isPublicLauncherDashboardStatic(method, p) { if isPublicLauncherDashboardStatic(method, p) {
return true return true
} }
if p == "/pico/ws" && method == http.MethodGet {
return true
}
switch p { switch p {
case "/api/auth/login": case "/api/auth/login":
return method == http.MethodPost return method == http.MethodPost