diff --git a/pkg/integrations/discord/send-chat.go b/pkg/integrations/discord/send-chat.go index f3e499a02..4e6024df0 100644 --- a/pkg/integrations/discord/send-chat.go +++ b/pkg/integrations/discord/send-chat.go @@ -51,7 +51,7 @@ func SendChat(ctx context.Context, w *discordtypes.Webhook, did string, scm *pla return fmt.Errorf("failed to marshal payload: %w", err) } - log.Warn(ctx, "sending chat to discord", "payload", string(jsonPayload)) + log.Warn(ctx, "sending chat to discord", "payload", string(jsonPayload), "webhook_url", w.URL) req, err := http.NewRequestWithContext(ctx, "POST", w.URL, bytes.NewReader(jsonPayload)) if err != nil { @@ -71,10 +71,9 @@ func SendChat(ctx context.Context, w *discordtypes.Webhook, did string, scm *pla } if resp.StatusCode != 204 { + log.Error(ctx, "chat webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", string(body)) return fmt.Errorf("failed to send chat to discord: %s", string(body)) } - log.Warn(ctx, "chat sent to discord", "payload", string(body)) - return nil } diff --git a/pkg/integrations/discord/send-livestream.go b/pkg/integrations/discord/send-livestream.go index c7819dcba..c4119393a 100644 --- a/pkg/integrations/discord/send-livestream.go +++ b/pkg/integrations/discord/send-livestream.go @@ -93,7 +93,7 @@ func SendLivestream(ctx context.Context, w *discordtypes.Webhook, pdsURL string, return fmt.Errorf("failed to marshal payload: %w", err) } - log.Warn(ctx, "sending livestream to discord", "payload", string(jsonPayload)) + log.Warn(ctx, "sending livestream to discord", "payload", string(jsonPayload), "webhook_url", w.URL) req, err := http.NewRequestWithContext(ctx, "POST", w.URL, bytes.NewReader(jsonPayload)) if err != nil { @@ -112,6 +112,7 @@ func SendLivestream(ctx context.Context, w *discordtypes.Webhook, pdsURL string, if err != nil { return fmt.Errorf("failed to read response body: %w", err) } + log.Error(ctx, "livestream webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", string(body)) return fmt.Errorf("failed to send request (http %d): %s", resp.StatusCode, string(body)) } diff --git a/pkg/integrations/discord/send-stream-received.go b/pkg/integrations/discord/send-stream-received.go index d12f45996..f22c42af6 100644 --- a/pkg/integrations/discord/send-stream-received.go +++ b/pkg/integrations/discord/send-stream-received.go @@ -11,6 +11,7 @@ import ( "stream.place/streamplace/pkg/aqhttp" "stream.place/streamplace/pkg/integrations/discord/discordtypes" + "stream.place/streamplace/pkg/log" ) func SendStreamReceived(ctx context.Context, w *discordtypes.Webhook, streamerDID string) error { @@ -28,6 +29,8 @@ func SendStreamReceived(ctx context.Context, w *discordtypes.Webhook, streamerDI return fmt.Errorf("failed to marshal payload: %w", err) } + log.Log(ctx, "sending stream.received to discord", "streamerDID", streamerDID, "webhook_url", w.URL) + req, err := http.NewRequestWithContext(ctx, "POST", w.URL, bytes.NewReader(jsonPayload)) if err != nil { return fmt.Errorf("failed to create request: %w", err) @@ -45,6 +48,7 @@ func SendStreamReceived(ctx context.Context, w *discordtypes.Webhook, streamerDI if err != nil { return fmt.Errorf("failed to read response body: %w", err) } + log.Error(ctx, "stream.received webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", string(body)) return fmt.Errorf("failed to send request (http %d): %s", resp.StatusCode, string(body)) } diff --git a/pkg/spxrpc/webhook.go b/pkg/spxrpc/webhook.go index f6ac245a1..895875e91 100644 --- a/pkg/spxrpc/webhook.go +++ b/pkg/spxrpc/webhook.go @@ -44,7 +44,7 @@ func (s *Server) handlePlaceStreamServerCreateWebhook(ctx context.Context, input // Create webhook err = s.statefulDB.CreateWebhook(webhook) if err != nil { - log.Error(ctx, "failed to create webhook", "err", err) + log.Error(ctx, "failed to create webhook in database", "err", err, "url", input.Url, "events", input.Events) return nil, echo.NewHTTPError(http.StatusInternalServerError, "Failed to create webhook") } @@ -83,9 +83,12 @@ func (s *Server) handlePlaceStreamServerListWebhooks(ctx context.Context, active } // Build filters + // active defaults to true (show all active webhooks). When the client + // explicitly passes active=false, we filter to show inactive webhooks. + // When active=true, we show only active webhooks. filters := make(map[string]interface{}) - if !active { - filters["active"] = active + if active { + filters["active"] = true } // Get webhooks diff --git a/pkg/statedb/webhook.go b/pkg/statedb/webhook.go index a65d9c430..b74df539a 100644 --- a/pkg/statedb/webhook.go +++ b/pkg/statedb/webhook.go @@ -225,19 +225,12 @@ func (w *Webhook) ToLexicon() (placestream.ServerDefs_Webhook, error) { // FromLexiconInput converts a placestream.ServerCreateWebhook_Input to a database Webhook func WebhookFromLexiconInput(input placestream.ServerCreateWebhook_Input, userDID, id string) (*Webhook, error) { - // Debug log the raw input - fmt.Printf("DEBUG: WebhookFromLexiconInput input.Events: %+v (type: %T)\n", input.Events, input.Events) - for i, event := range input.Events { - fmt.Printf("DEBUG: Event[%d]: %q (type: %T)\n", i, event, event) - } - var eventsJSON json.RawMessage if len(input.Events) > 0 { jsonBytes, err := json.Marshal(input.Events) if err != nil { return nil, fmt.Errorf("failed to marshal events: %w", err) } - fmt.Printf("DEBUG: Marshaled events JSON: %q\n", string(jsonBytes)) eventsJSON = json.RawMessage(jsonBytes) } else { // Default to empty array if no events provided -- 2.51.2 From 60753841f45ec306c3a00b89a623c1767cac20fa Mon Sep 17 00:00:00 2001 From: Natalie Bridgers Date: Sat, 25 Jul 2026 16:34:21 -0500 Subject: [PATCH 2/2] trim some logs, bound response bodies if we print em out Signed-off-by: Natalie Bridgers --- pkg/integrations/discord/response.go | 28 ++++++++++++++++ pkg/integrations/discord/response_test.go | 33 +++++++++++++++++++ pkg/integrations/discord/send-chat.go | 9 +++-- pkg/integrations/discord/send-livestream.go | 7 ++-- .../discord/send-stream-received.go | 7 ++-- pkg/spxrpc/webhook.go | 10 +++--- 6 files changed, 76 insertions(+), 18 deletions(-) create mode 100644 pkg/integrations/discord/response.go create mode 100644 pkg/integrations/discord/response_test.go diff --git a/pkg/integrations/discord/response.go b/pkg/integrations/discord/response.go new file mode 100644 index 000000000..130f02c01 --- /dev/null +++ b/pkg/integrations/discord/response.go @@ -0,0 +1,28 @@ +package discord + +import ( + "io" + "strings" + "unicode" +) + +// maxResponseBodyLogBytes caps how much of an external webhook response body +// we read for logging and error messages. Webhook endpoints are +// user-configured, so their bodies are untrusted input. +const maxResponseBodyLogBytes = 1024 + +// readResponseBody reads a bounded, log-safe rendering of an external webhook +// response body: truncated to maxResponseBodyLogBytes with control characters +// stripped so external content can't disrupt log parsing. +func readResponseBody(r io.Reader) (string, error) { + body, err := io.ReadAll(io.LimitReader(r, maxResponseBodyLogBytes)) + if err != nil { + return "", err + } + return strings.Map(func(r rune) rune { + if unicode.IsControl(r) { + return -1 + } + return r + }, string(body)), nil +} diff --git a/pkg/integrations/discord/response_test.go b/pkg/integrations/discord/response_test.go new file mode 100644 index 000000000..230cf87fd --- /dev/null +++ b/pkg/integrations/discord/response_test.go @@ -0,0 +1,33 @@ +package discord + +import ( + "bytes" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReadResponseBodyTruncates(t *testing.T) { + body := bytes.Repeat([]byte("a"), maxResponseBodyLogBytes*10) + out, err := readResponseBody(bytes.NewReader(body)) + require.NoError(t, err) + require.Equal(t, maxResponseBodyLogBytes, len(out)) +} + +func TestReadResponseBodyStripsControlCharacters(t *testing.T) { + out, err := readResponseBody(strings.NewReader("bad\x00body\x1b[31m\nwith\ttabs\r\n")) + require.NoError(t, err) + require.NotContains(t, out, "\x00") + require.NotContains(t, out, "\x1b") + require.NotContains(t, out, "\n") + require.NotContains(t, out, "\t") + require.NotContains(t, out, "\r") + require.Contains(t, out, "badbody") +} + +func TestReadResponseBodyKeepsPrintableUnicode(t *testing.T) { + out, err := readResponseBody(strings.NewReader("error: quota exceeded 日本語")) + require.NoError(t, err) + require.Equal(t, "error: quota exceeded 日本語", out) +} diff --git a/pkg/integrations/discord/send-chat.go b/pkg/integrations/discord/send-chat.go index 4e6024df0..2fd08e513 100644 --- a/pkg/integrations/discord/send-chat.go +++ b/pkg/integrations/discord/send-chat.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "strings" @@ -51,7 +50,7 @@ func SendChat(ctx context.Context, w *discordtypes.Webhook, did string, scm *pla return fmt.Errorf("failed to marshal payload: %w", err) } - log.Warn(ctx, "sending chat to discord", "payload", string(jsonPayload), "webhook_url", w.URL) + log.Warn(ctx, "sending chat to discord", "payload", string(jsonPayload), "for_did", w.DID) req, err := http.NewRequestWithContext(ctx, "POST", w.URL, bytes.NewReader(jsonPayload)) if err != nil { @@ -65,14 +64,14 @@ func SendChat(ctx context.Context, w *discordtypes.Webhook, did string, scm *pla } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := readResponseBody(resp.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } if resp.StatusCode != 204 { - log.Error(ctx, "chat webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", string(body)) - return fmt.Errorf("failed to send chat to discord: %s", string(body)) + log.Error(ctx, "chat webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", body) + return fmt.Errorf("failed to send chat to discord: %s", body) } return nil diff --git a/pkg/integrations/discord/send-livestream.go b/pkg/integrations/discord/send-livestream.go index c4119393a..ed4a797b7 100644 --- a/pkg/integrations/discord/send-livestream.go +++ b/pkg/integrations/discord/send-livestream.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "net/url" "strconv" @@ -108,12 +107,12 @@ func SendLivestream(ctx context.Context, w *discordtypes.Webhook, pdsURL string, defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { - body, err := io.ReadAll(resp.Body) + body, err := readResponseBody(resp.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } - log.Error(ctx, "livestream webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", string(body)) - return fmt.Errorf("failed to send request (http %d): %s", resp.StatusCode, string(body)) + log.Error(ctx, "livestream webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", body) + return fmt.Errorf("failed to send request (http %d): %s", resp.StatusCode, body) } return nil diff --git a/pkg/integrations/discord/send-stream-received.go b/pkg/integrations/discord/send-stream-received.go index f22c42af6..7c95d2551 100644 --- a/pkg/integrations/discord/send-stream-received.go +++ b/pkg/integrations/discord/send-stream-received.go @@ -5,7 +5,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net/http" "strings" @@ -44,12 +43,12 @@ func SendStreamReceived(ctx context.Context, w *discordtypes.Webhook, streamerDI defer resp.Body.Close() if resp.StatusCode != http.StatusNoContent { - body, err := io.ReadAll(resp.Body) + body, err := readResponseBody(resp.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } - log.Error(ctx, "stream.received webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", string(body)) - return fmt.Errorf("failed to send request (http %d): %s", resp.StatusCode, string(body)) + log.Error(ctx, "stream.received webhook delivery failed", "webhook_url", w.URL, "status_code", resp.StatusCode, "response_body", body) + return fmt.Errorf("failed to send request (http %d): %s", resp.StatusCode, body) } return nil diff --git a/pkg/spxrpc/webhook.go b/pkg/spxrpc/webhook.go index 895875e91..c49e411cb 100644 --- a/pkg/spxrpc/webhook.go +++ b/pkg/spxrpc/webhook.go @@ -82,13 +82,13 @@ func (s *Server) handlePlaceStreamServerListWebhooks(ctx context.Context, active } } - // Build filters - // active defaults to true (show all active webhooks). When the client - // explicitly passes active=false, we filter to show inactive webhooks. - // When active=true, we show only active webhooks. + // Build filters. The generated stub can't distinguish an absent `active` + // param from an explicit active=false (both arrive as false), so filtering + // only applies when active=true. Omitting the param or passing + // active=false returns all webhooks regardless of status. filters := make(map[string]interface{}) if active { - filters["active"] = true + filters["active"] = active } // Get webhooks