diff --git a/pkg/channels/pico/pico_http_test.go b/pkg/channels/pico/pico_http_test.go index c7699db9e..507daaee6 100644 --- a/pkg/channels/pico/pico_http_test.go +++ b/pkg/channels/pico/pico_http_test.go @@ -14,9 +14,15 @@ import ( "github.com/sipeed/picoclaw/pkg/config" ) +func testPicoConfig(token string) config.PicoConfig { + var c config.PicoConfig + c.SetToken(token) + return c +} + func TestPicoChannel_PostSend_Unauthorized(t *testing.T) { t.Parallel() - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } @@ -37,7 +43,7 @@ func TestPicoChannel_PostSend_Unauthorized(t *testing.T) { func TestPicoChannel_PostSend_EmptyContent(t *testing.T) { t.Parallel() - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } @@ -66,7 +72,7 @@ func TestPicoChannel_PostSend_EmptyContent(t *testing.T) { func TestPicoChannel_PostSend_MissingSession(t *testing.T) { t.Parallel() - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } @@ -88,7 +94,7 @@ func TestPicoChannel_PostSend_MissingSession(t *testing.T) { func TestPicoChannel_PostSend_OK(t *testing.T) { t.Parallel() - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } @@ -110,7 +116,7 @@ func TestPicoChannel_PostSend_OK(t *testing.T) { func TestPicoChannel_GetEvents_Unauthorized(t *testing.T) { t.Parallel() - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } @@ -129,7 +135,7 @@ func TestPicoChannel_GetEvents_Unauthorized(t *testing.T) { } func TestPicoChannel_SSE_ReceivesBroadcast(t *testing.T) { - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } @@ -172,7 +178,7 @@ func TestPicoChannel_SSE_ReceivesBroadcast(t *testing.T) { } func TestPicoChannel_SSE_SecondConnectionSameSessionReplacesFirst(t *testing.T) { - ch, err := NewPicoChannel(config.PicoConfig{Token: "tok"}, bus.NewMessageBus()) + ch, err := NewPicoChannel(testPicoConfig("tok"), bus.NewMessageBus()) if err != nil { t.Fatal(err) } diff --git a/pkg/config/security_integration_test.go b/pkg/config/security_integration_test.go index c1e1a2340..59346f293 100644 --- a/pkg/config/security_integration_test.go +++ b/pkg/config/security_integration_test.go @@ -17,10 +17,9 @@ import ( // Test JSON unmarshal of private fields func TestJSONUnmarshalPrivateFields(t *testing.T) { - //nolint: govet type testStruct struct { PublicField string `json:"public"` - privateField string `json:"private"` + privateField string // unexported: encoding/json ignores even if JSON has "private" } data := `{"public": "pub", "private": "priv"}` diff --git a/web/backend/api/gateway_host.go b/web/backend/api/gateway_host.go index 592571a28..ddba7d42c 100644 --- a/web/backend/api/gateway_host.go +++ b/web/backend/api/gateway_host.go @@ -93,6 +93,19 @@ func requestWSScheme(r *http.Request) string { return "ws" } +func requestHTTPScheme(r *http.Request) string { + if forwarded := strings.TrimSpace(r.Header.Get("X-Forwarded-Proto")); forwarded != "" { + proto := strings.ToLower(strings.TrimSpace(strings.Split(forwarded, ",")[0])) + if proto == "https" { + return "https" + } + } + if r.TLS != nil { + return "https" + } + return "http" +} + func (h *Handler) buildWsURL(r *http.Request, cfg *config.Config) string { host := h.effectiveGatewayBindHost(cfg) if host == "" || host == "0.0.0.0" { @@ -106,3 +119,27 @@ func (h *Handler) buildWsURL(r *http.Request, cfg *config.Config) string { } return requestWSScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(wsPort)) + "/pico/ws" } + +func (h *Handler) buildPicoEventsURL(r *http.Request, cfg *config.Config) string { + host := h.effectiveGatewayBindHost(cfg) + if host == "" || host == "0.0.0.0" { + host = requestHostName(r) + } + webPort := h.serverPort + if webPort == 0 { + webPort = 18800 + } + return requestHTTPScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(webPort)) + "/pico/events" +} + +func (h *Handler) buildPicoSendURL(r *http.Request, cfg *config.Config) string { + host := h.effectiveGatewayBindHost(cfg) + if host == "" || host == "0.0.0.0" { + host = requestHostName(r) + } + webPort := h.serverPort + if webPort == 0 { + webPort = 18800 + } + return requestHTTPScheme(r) + "://" + net.JoinHostPort(host, strconv.Itoa(webPort)) + "/pico/send" +} diff --git a/web/backend/api/pico.go b/web/backend/api/pico.go index b4798d280..ad285f6cb 100644 --- a/web/backend/api/pico.go +++ b/web/backend/api/pico.go @@ -67,7 +67,7 @@ func (h *Handler) handleGetPicoToken(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{ - "token": cfg.Channels.Pico.Token, + "token": cfg.Channels.Pico.Token(), "ws_url": wsURL, "events_url": eventsURL, "send_url": sendURL, @@ -168,7 +168,7 @@ func (h *Handler) handlePicoSetup(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]any{ - "token": cfg.Channels.Pico.Token, + "token": cfg.Channels.Pico.Token(), "ws_url": wsURL, "events_url": eventsURL, "send_url": sendURL,