fix(player): enforce same-origin WebSockets

This commit is contained in:
Lukáš Lipinský
2026-09-04 22:34:35 +02:00
committed by Tobias Gesellchen
parent 6689bbbe23
commit 32c1040554
2 changed files with 77 additions and 3 deletions
+2 -3
View File
@@ -147,12 +147,11 @@ type DeviceEntry struct {
// NewWebApp creates a new WebApp instance for SPA mode
func NewWebApp() *WebApp {
// Leave Upgrader.CheckOrigin nil to use Gorilla's same-origin policy while
// retaining support for non-browser clients that omit the Origin header.
return &WebApp{
devices: make(map[string]*webtypes.DeviceConnection),
WSClients: make(map[*websocket.Conn]*sync.Mutex),
Upgrader: websocket.Upgrader{
CheckOrigin: func(_ *http.Request) bool { return true },
},
}
}
@@ -0,0 +1,75 @@
package soundtouchweb
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/gorilla/websocket"
)
func TestWebSocketOriginPolicy(t *testing.T) {
app := NewWebApp()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := app.Upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
_ = conn.Close()
}))
defer server.Close()
webSocketURL := "ws" + strings.TrimPrefix(server.URL, "http")
tests := []struct {
name string
origin string
wantStatus int
}{
{name: "originless non-browser client", wantStatus: http.StatusSwitchingProtocols},
{name: "same origin", origin: server.URL, wantStatus: http.StatusSwitchingProtocols},
{name: "cross origin", origin: "https://attacker.example", wantStatus: http.StatusForbidden},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
header := http.Header{}
if test.origin != "" {
header.Set("Origin", test.origin)
}
conn, response, err := websocket.DefaultDialer.Dial(webSocketURL, header)
if response != nil {
defer response.Body.Close()
}
if test.wantStatus == http.StatusSwitchingProtocols {
if err != nil {
t.Fatalf("WebSocket handshake failed: %v", err)
}
if conn == nil {
t.Fatal("WebSocket handshake returned no connection")
}
_ = conn.Close()
return
}
if err == nil {
_ = conn.Close()
t.Fatal("cross-origin WebSocket handshake unexpectedly succeeded")
}
if response == nil {
t.Fatal("rejected WebSocket handshake returned no HTTP response")
}
if response.StatusCode != test.wantStatus {
t.Fatalf("status = %d, want %d", response.StatusCode, test.wantStatus)
}
})
}
}