Add unit tests for websocket Hub.checkOrigin function

Test coverage for CORS origin validation including:
- No origin header (non-browser clients)
- Same-origin requests with/without proxy headers
- Wildcard and explicit allowed origins
- Private network fallback (192.168.x, 10.x, localhost, .local, .lan)
- Public IP/domain rejection
- X-Forwarded-Proto/Host/Scheme header handling
- WebSocket protocol normalization (ws->http, wss->https)

Coverage: 37.3% → 48.6%
This commit is contained in:
rcourtman 2025-12-01 09:33:40 +00:00
parent 534d6c78a4
commit 8b3d8d3e86

View file

@ -2,6 +2,7 @@ package websocket
import (
"math"
"net/http"
"testing"
"time"
@ -673,3 +674,195 @@ func TestMessage_Fields(t *testing.T) {
t.Errorf("Timestamp = %v, want 2024-01-01T00:00:00Z", msg.Timestamp)
}
}
func TestHub_CheckOrigin(t *testing.T) {
tests := []struct {
name string
origin string
host string
allowedOrigins []string
forwardedProto string
forwardedHost string
expected bool
}{
// No origin header - always allowed for non-browser clients
{
name: "no origin header",
origin: "",
host: "localhost:8080",
expected: true,
},
// Same-origin requests
{
name: "same origin http",
origin: "http://localhost:8080",
host: "localhost:8080",
expected: true,
},
{
name: "same origin with forwarded proto https",
origin: "https://example.com",
host: "example.com",
forwardedProto: "https",
expected: true,
},
{
name: "same origin with forwarded host",
origin: "http://proxy.example.com",
host: "backend:8080",
forwardedHost: "proxy.example.com",
expected: true,
},
// Wildcard allowed origins
{
name: "wildcard allows any origin",
origin: "https://evil.com",
host: "localhost:8080",
allowedOrigins: []string{"*"},
expected: true,
},
// Explicit allowed origins
{
name: "explicit allowed origin matches",
origin: "https://app.example.com",
host: "localhost:8080",
allowedOrigins: []string{"https://app.example.com"},
expected: true,
},
{
name: "explicit allowed origin no match",
origin: "https://other.example.com",
host: "localhost:8080",
allowedOrigins: []string{"https://app.example.com"},
expected: false,
},
{
name: "multiple allowed origins - match second",
origin: "https://second.example.com",
host: "localhost:8080",
allowedOrigins: []string{"https://first.example.com", "https://second.example.com"},
expected: true,
},
// Private network fallback (no allowed origins configured)
{
name: "private IP 192.168.x.x allowed when no origins configured",
origin: "http://192.168.1.100:3000",
host: "localhost:8080",
expected: true,
},
{
name: "private IP 10.x.x.x allowed when no origins configured",
origin: "http://10.0.0.50:3000",
host: "localhost:8080",
expected: true,
},
{
name: "localhost allowed when no origins configured",
origin: "http://localhost:3000",
host: "localhost:8080",
expected: true,
},
{
name: "127.0.0.1 allowed when no origins configured",
origin: "http://127.0.0.1:3000",
host: "localhost:8080",
expected: true,
},
{
name: ".local domain allowed when no origins configured",
origin: "http://myserver.local:3000",
host: "localhost:8080",
expected: true,
},
{
name: ".lan domain allowed when no origins configured",
origin: "http://myserver.lan:3000",
host: "localhost:8080",
expected: true,
},
{
name: "public IP rejected when no origins configured",
origin: "http://8.8.8.8:3000",
host: "localhost:8080",
expected: false,
},
{
name: "public domain rejected when no origins configured",
origin: "https://evil.example.com",
host: "localhost:8080",
expected: false,
},
// HTTPS origin stripping
{
name: "https origin with private IP",
origin: "https://192.168.1.50:443",
host: "localhost:8080",
expected: true,
},
// Forwarded proto normalization
{
name: "wss forwarded proto normalized to https",
origin: "https://example.com",
host: "example.com",
forwardedProto: "wss",
expected: true,
},
{
name: "ws forwarded proto normalized to http",
origin: "http://example.com",
host: "example.com",
forwardedProto: "ws",
expected: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
hub := NewHub(nil)
if len(tc.allowedOrigins) > 0 {
hub.SetAllowedOrigins(tc.allowedOrigins)
}
req := &http.Request{
Host: tc.host,
Header: make(http.Header),
}
if tc.origin != "" {
req.Header.Set("Origin", tc.origin)
}
if tc.forwardedProto != "" {
req.Header.Set("X-Forwarded-Proto", tc.forwardedProto)
}
if tc.forwardedHost != "" {
req.Header.Set("X-Forwarded-Host", tc.forwardedHost)
}
result := hub.checkOrigin(req)
if result != tc.expected {
t.Errorf("checkOrigin() = %v, want %v", result, tc.expected)
}
})
}
}
func TestHub_CheckOrigin_XForwardedScheme(t *testing.T) {
hub := NewHub(nil)
req := &http.Request{
Host: "example.com",
Header: make(http.Header),
}
req.Header.Set("Origin", "https://example.com")
req.Header.Set("X-Forwarded-Scheme", "https")
result := hub.checkOrigin(req)
if !result {
t.Error("checkOrigin should allow same-origin with X-Forwarded-Scheme")
}
}