From c7ec1bcab38395f535ba2e549eb16ec963f9c63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Sibiril?= <81782+aureliensibiril@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:13:21 +0200 Subject: [PATCH] Validate Slack OAuth2 token response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check the ok and error fields from Slack's token response and reject flows with a missing access token. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com> --- pkg/connector/slack.go | 12 ++++++++++++ pkg/connector/slack_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/pkg/connector/slack.go b/pkg/connector/slack.go index 52b44f96d..d7dcb6584 100644 --- a/pkg/connector/slack.go +++ b/pkg/connector/slack.go @@ -44,6 +44,8 @@ type ( } SlackTokenResponse struct { + Ok bool `json:"ok"` + Error string `json:"error,omitempty"` IncomingWebhook *IncomingWebhook `json:"incoming_webhook,omitempty"` } ) @@ -115,6 +117,16 @@ func ParseSlackTokenResponse(body []byte, oauth2Conn OAuth2Connection, organizat return nil, nil, fmt.Errorf("cannot decode Slack token response: %w", err) } + if slackResponse.Error != "" { + return nil, nil, fmt.Errorf("cannot complete Slack OAuth2 flow: %s", slackResponse.Error) + } + if !slackResponse.Ok { + return nil, nil, fmt.Errorf("cannot complete Slack OAuth2 flow: ok=false") + } + if oauth2Conn.AccessToken == "" { + return nil, nil, fmt.Errorf("cannot complete Slack OAuth2 flow: missing access token") + } + settings := SlackSettings{} if slackResponse.IncomingWebhook != nil { settings.WebhookURL = slackResponse.IncomingWebhook.URL diff --git a/pkg/connector/slack_test.go b/pkg/connector/slack_test.go index 681819742..50903b99a 100644 --- a/pkg/connector/slack_test.go +++ b/pkg/connector/slack_test.go @@ -64,4 +64,28 @@ func TestParseSlackTokenResponse(t *testing.T) { assert.Empty(t, conn.Settings.ChannelID) assert.Equal(t, "xoxb-test", conn.AccessToken) }) + + t.Run("slack error response", func(t *testing.T) { + t.Parallel() + body := []byte(`{"ok":false,"error":"invalid_code"}`) + + conn, returnedOrgID, err := ParseSlackTokenResponse(body, base, orgID) + require.Error(t, err) + assert.Nil(t, conn) + assert.Nil(t, returnedOrgID) + assert.ErrorContains(t, err, "invalid_code") + }) + + t.Run("missing access token", func(t *testing.T) { + t.Parallel() + body := []byte(`{"ok":true}`) + connWithoutToken := base + connWithoutToken.AccessToken = "" + + conn, returnedOrgID, err := ParseSlackTokenResponse(body, connWithoutToken, orgID) + require.Error(t, err) + assert.Nil(t, conn) + assert.Nil(t, returnedOrgID) + assert.ErrorContains(t, err, "missing access token") + }) }