diff --git a/server/internal/http/manager.go b/server/internal/http/manager.go index 6176568f..8593acee 100644 --- a/server/internal/http/manager.go +++ b/server/internal/http/manager.go @@ -55,7 +55,8 @@ func New(WebSocketManager types.WebSocketManager, ApiManager types.ApiManager, c router.Route("/api", ApiManager.Route) router.Get("/api/ws", WebSocketManager.Upgrade(func(r *http.Request) bool { - return config.AllowOrigin(r.Header.Get("Origin")) + origin := r.Header.Get("Origin") + return origin == "" || config.AllowOrigin(origin) })) batch := batchHandler{ diff --git a/server/internal/http/manager_test.go b/server/internal/http/manager_test.go index 720698e6..60afbecc 100644 --- a/server/internal/http/manager_test.go +++ b/server/internal/http/manager_test.go @@ -67,3 +67,45 @@ func TestOAuthAutoRedirectSkipsAuthenticatedSessions(t *testing.T) { t.Fatalf("authenticated body = %q", body) } } + +type originCapturingWebSocketManager struct { + checkOrigin types.CheckOrigin +} + +func (*originCapturingWebSocketManager) Start() {} + +func (*originCapturingWebSocketManager) Shutdown() error { return nil } + +func (*originCapturingWebSocketManager) AddHandler(types.WebSocketHandler) {} + +func (manager *originCapturingWebSocketManager) Upgrade(checkOrigin types.CheckOrigin) types.RouterHandler { + manager.checkOrigin = checkOrigin + return func(w http.ResponseWriter, r *http.Request) error { + http.NotFound(w, r) + return nil + } +} + +func TestWebSocketOriginAllowsInternalDialWithoutOrigin(t *testing.T) { + webSocketManager := &originCapturingWebSocketManager{} + serverConfig := &config.Server{CORS: []string{"https://neko.example.test"}} + memberConfig := &config.Member{} + sessionManager := session.New(&config.Session{}) + apiManager := api.New(sessionManager, member.New(sessionManager, memberConfig), nil, nil, memberConfig, serverConfig) + New(webSocketManager, apiManager, serverConfig, memberConfig) + + request := httptest.NewRequest(http.MethodGet, "/api/ws", nil) + if !webSocketManager.checkOrigin(request) { + t.Fatal("internal WebSocket dial without Origin was rejected") + } + + request.Header.Set("Origin", "https://neko.example.test") + if !webSocketManager.checkOrigin(request) { + t.Fatal("configured public Origin was rejected") + } + + request.Header.Set("Origin", "https://attacker.example") + if webSocketManager.checkOrigin(request) { + t.Fatal("unconfigured browser Origin was accepted") + } +}