fix(websocket): add support for WSS in WebSocket URL construction based on forwarded headers
This commit is contained in:
parent
c2c2d7674f
commit
01705249af
3 changed files with 84 additions and 1 deletions
|
|
@ -75,6 +75,29 @@ func requestHostName(r *http.Request) string {
|
|||
return "127.0.0.1"
|
||||
}
|
||||
|
||||
// forwardedRFC7239Proto returns the proto value from the first Forwarded element (RFC 7239), or "".
|
||||
func forwardedRFC7239Proto(r *http.Request) string {
|
||||
raw := strings.TrimSpace(r.Header.Get("Forwarded"))
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
first := strings.TrimSpace(strings.Split(raw, ",")[0])
|
||||
for _, part := range strings.Split(first, ";") {
|
||||
part = strings.TrimSpace(part)
|
||||
idx := strings.IndexByte(part, '=')
|
||||
if idx <= 0 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(part[:idx]))
|
||||
if key != "proto" {
|
||||
continue
|
||||
}
|
||||
v := strings.Trim(strings.TrimSpace(part[idx+1:]), `"'`)
|
||||
return strings.ToLower(v)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func requestWSScheme(r *http.Request) string {
|
||||
if forwarded := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); forwarded != "" {
|
||||
proto := strings.ToLower(strings.TrimSpace(strings.Split(forwarded, ",")[0]))
|
||||
|
|
@ -86,6 +109,19 @@ func requestWSScheme(r *http.Request) string {
|
|||
}
|
||||
}
|
||||
|
||||
if p := forwardedRFC7239Proto(r); p != "" {
|
||||
if p == "https" || p == "wss" {
|
||||
return "wss"
|
||||
}
|
||||
if p == "http" || p == "ws" {
|
||||
return "ws"
|
||||
}
|
||||
}
|
||||
|
||||
if strings.EqualFold(strings.TrimSpace(r.Header.Get("X-Forwarded-Ssl")), "on") {
|
||||
return "wss"
|
||||
}
|
||||
|
||||
if r.TLS != nil {
|
||||
return "wss"
|
||||
}
|
||||
|
|
@ -100,6 +136,12 @@ func requestHTTPScheme(r *http.Request) string {
|
|||
return "https"
|
||||
}
|
||||
}
|
||||
if p := forwardedRFC7239Proto(r); p == "https" {
|
||||
return "https"
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(r.Header.Get("X-Forwarded-Ssl")), "on") {
|
||||
return "https"
|
||||
}
|
||||
if r.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,3 +186,37 @@ func TestBuildWsURLPrefersForwardedHTTPOverTLS(t *testing.T) {
|
|||
t.Fatalf("buildWsURL() = %q, want %q", got, "ws://chat.example.com:18800/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWsURLUsesWSSWhenForwardedHeaderProtoIsHTTPS(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = "0.0.0.0"
|
||||
cfg.Gateway.Port = 18790
|
||||
|
||||
req := httptest.NewRequest("GET", "http://10.0.0.1/api/pico/token", nil)
|
||||
req.Host = "chat.example.com"
|
||||
req.Header.Set("Forwarded", `for=203.0.113.1;proto=https;host=chat.example.com`)
|
||||
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com:18800/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18800/pico/ws")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWsURLUsesWSSWhenXForwardedSslOn(t *testing.T) {
|
||||
configPath := filepath.Join(t.TempDir(), "config.json")
|
||||
h := NewHandler(configPath)
|
||||
|
||||
cfg := config.DefaultConfig()
|
||||
cfg.Gateway.Host = "0.0.0.0"
|
||||
cfg.Gateway.Port = 18790
|
||||
|
||||
req := httptest.NewRequest("GET", "http://10.0.0.1/api/pico/token", nil)
|
||||
req.Host = "chat.example.com"
|
||||
req.Header.Set("X-Forwarded-Ssl", "on")
|
||||
|
||||
if got := h.buildWsURL(req, cfg); got != "wss://chat.example.com:18800/pico/ws" {
|
||||
t.Fatalf("buildWsURL() = %q, want %q", got, "wss://chat.example.com:18800/pico/ws")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,15 @@ export function normalizeWsUrlForBrowser(wsUrl: string): string {
|
|||
|
||||
if (isLocalHost && !isBrowserLocal) {
|
||||
parsedUrl.hostname = window.location.hostname
|
||||
finalWsUrl = parsedUrl.toString()
|
||||
}
|
||||
|
||||
// HTTPS pages cannot open ws:// (mixed content). Backend may still return ws
|
||||
// when TLS terminates at a reverse proxy without X-Forwarded-Proto.
|
||||
if (window.location.protocol === "https:" && parsedUrl.protocol === "ws:") {
|
||||
parsedUrl.protocol = "wss:"
|
||||
}
|
||||
|
||||
finalWsUrl = parsedUrl.toString()
|
||||
} catch (error) {
|
||||
console.warn("Could not parse ws_url:", error)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue