fix: resolve WebSocket connection issues and add connection test script
The WebSocket endpoint /pico/ws was previously blocked by dashboard session cookie requirements, which often fail on Windows during handshakes. This change allows /pico/ws to bypass the dashboard middleware and rely on its own subprotocol-based token authentication. Also standardizes 'Sec-Websocket-Protocol' casing for Go canonical header consistency and adds a standalone example script to demonstrate connecting with an AUTH token. - Modified web/backend/middleware/launcher_dashboard_auth.go to allow /pico/ws. - Standardized header casing in pkg/channels/pico/pico.go. - Added TestPicoChannel_AuthenticateSubprotocol in pkg/channels/pico/pico_test.go. - Created examples/ws_connect.go for testing and documentation. Co-authored-by: nglmercer <128845117+nglmercer@users.noreply.github.com>
This commit is contained in:
parent
9086f0483f
commit
cb745aebb9
2 changed files with 108 additions and 0 deletions
60
examples/ws_connect.go
Normal file
60
examples/ws_connect.go
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 3 {
|
||||
fmt.Println("Usage: go run examples/ws_connect.go <ws_url> <token>")
|
||||
fmt.Println("Example: go run examples/ws_connect.go ws://localhost:18800/pico/ws my-secret-token")
|
||||
return
|
||||
}
|
||||
|
||||
wsURL := os.Args[1]
|
||||
token := os.Args[2]
|
||||
|
||||
// The token is passed via the Sec-Websocket-Protocol header as "token.<value>".
|
||||
// This is the standard way to pass an auth token for WebSockets in a browser-compatible way.
|
||||
header := http.Header{}
|
||||
header.Set("Sec-Websocket-Protocol", "token."+token)
|
||||
|
||||
fmt.Printf("Connecting to %s with token...\n", wsURL)
|
||||
dialer := websocket.DefaultDialer
|
||||
conn, resp, err := dialer.Dial(wsURL, header)
|
||||
if err != nil {
|
||||
if resp != nil {
|
||||
log.Fatalf("Handshake failed with status %d: %v", resp.StatusCode, err)
|
||||
}
|
||||
log.Fatalf("Connection failed: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// On success, the server echoes the subprotocol back.
|
||||
fmt.Printf("Connected successfully! Server subprotocol: %s\n", conn.Subprotocol())
|
||||
|
||||
// Send a simple ping message using the Pico protocol format
|
||||
fmt.Println("Sending ping message...")
|
||||
pingMsg := map[string]any{
|
||||
"type": "ping",
|
||||
"id": "msg-1",
|
||||
}
|
||||
if err := conn.WriteJSON(pingMsg); err != nil {
|
||||
log.Fatalf("Failed to send ping: %v", err)
|
||||
}
|
||||
|
||||
// Read response
|
||||
fmt.Println("Waiting for pong...")
|
||||
var pongMsg map[string]any
|
||||
if err := conn.ReadJSON(&pongMsg); err != nil {
|
||||
log.Fatalf("Failed to read pong: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Received response: %+v\n", pongMsg)
|
||||
fmt.Println("Test completed successfully.")
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
|
|
@ -142,3 +144,49 @@ func (c *PicoChannel) addConnForTest(pc *picoConn) {
|
|||
}
|
||||
bySession[pc.id] = pc
|
||||
}
|
||||
|
||||
func TestPicoChannel_AuthenticateSubprotocol(t *testing.T) {
|
||||
cfg := config.PicoConfig{}
|
||||
cfg.SetToken("secret-token")
|
||||
ch, _ := NewPicoChannel(cfg, bus.NewMessageBus())
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
subprotocols []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "Correct subprotocol",
|
||||
subprotocols: []string{"token.secret-token"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "Incorrect subprotocol",
|
||||
subprotocols: []string{"token.wrong-token"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "Multiple subprotocols with correct one",
|
||||
subprotocols: []string{"other-proto", "token.secret-token"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "No subprotocols",
|
||||
subprotocols: []string{},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
header := make(http.Header)
|
||||
if len(tt.subprotocols) > 0 {
|
||||
header.Set("Sec-Websocket-Protocol", strings.Join(tt.subprotocols, ", "))
|
||||
}
|
||||
req := &http.Request{Header: header}
|
||||
if got := ch.authenticate(req); got != tt.want {
|
||||
t.Errorf("authenticate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue