Bump Go dependencies in both backend/ and backend/_example/memory_store.
Notable updates:
- github.com/go-pkgz/lgr v0.12.1 -> v0.12.3
- github.com/klauspost/compress v1.18.2 -> v1.18.5
- github.com/PuerkitoBio/goquery v1.11.0 -> v1.12.0
- github.com/montanaflynn/stats v0.7.1 -> v0.9.0
- github.com/redis/go-redis/v9 v9.17.2 -> v9.18.0
- github.com/slack-go/slack v0.17.3 -> v0.21.1
- go.mongodb.org/mongo-driver v1.17.6 -> v1.17.9
- golang.org/x/crypto v0.48.0 -> v0.50.0
- golang.org/x/net v0.49.0 -> v0.53.0
- golang.org/x/image v0.36.0 -> v0.39.0
- golang.org/x/sys v0.41.0 -> v0.43.0
- golang.org/x/{oauth2,sync,text} minor bumps
Key markdown/sanitisation libs (bluemonday v1.0.27,
alecthomas/chroma/v2 v2.23.1, russross/blackfriday/v2 v2.1.0,
Depado/bfchroma/v2 v2.0.0) are already at the latest available
versions and were not bumped.
Verified the Chroma span-class allowlist regex in
backend/app/store/comment.go:128-131 is still fully in sync with
chroma/v2 types.go StandardTypes map (86 classes, byte-equal after
sorting). The inline comment references commit c263f6f which is
stale (Chroma is at v2 now), but the class list content is current.
Ran `go mod tidy` + `go mod vendor` + full race test suite on both
modules. All green. Added a reminder in CLAUDE.md that updating
backend/ Go modules also requires `go mod tidy` in
backend/_example/memory_store since the example module uses a
local replace directive and inherits indirect deps from the main
module.
289 lines
12 KiB
Go
289 lines
12 KiB
Go
package slack
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
)
|
|
|
|
// OAuthResponseIncomingWebhook ...
|
|
type OAuthResponseIncomingWebhook struct {
|
|
URL string `json:"url"`
|
|
Channel string `json:"channel"`
|
|
ChannelID string `json:"channel_id,omitempty"`
|
|
ConfigurationURL string `json:"configuration_url"`
|
|
}
|
|
|
|
// OAuthResponseBot ...
|
|
type OAuthResponseBot struct {
|
|
BotUserID string `json:"bot_user_id"`
|
|
BotAccessToken string `json:"bot_access_token"`
|
|
}
|
|
|
|
// OAuthResponse ...
|
|
type OAuthResponse struct {
|
|
AccessToken string `json:"access_token"`
|
|
Scope string `json:"scope"`
|
|
TeamName string `json:"team_name"`
|
|
TeamID string `json:"team_id"`
|
|
IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
|
|
Bot OAuthResponseBot `json:"bot"`
|
|
UserID string `json:"user_id,omitempty"`
|
|
SlackResponse
|
|
}
|
|
|
|
// OAuthV2Response ...
|
|
type OAuthV2Response struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
Scope string `json:"scope"`
|
|
BotUserID string `json:"bot_user_id"`
|
|
AppID string `json:"app_id"`
|
|
Team OAuthV2ResponseTeam `json:"team"`
|
|
IncomingWebhook OAuthResponseIncomingWebhook `json:"incoming_webhook"`
|
|
Enterprise OAuthV2ResponseEnterprise `json:"enterprise"`
|
|
IsEnterpriseInstall bool `json:"is_enterprise_install"`
|
|
AuthedUser OAuthV2ResponseAuthedUser `json:"authed_user"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
SlackResponse
|
|
}
|
|
|
|
// OAuthV2ResponseTeam ...
|
|
type OAuthV2ResponseTeam struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// OAuthV2ResponseEnterprise ...
|
|
type OAuthV2ResponseEnterprise struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// OAuthV2ResponseAuthedUser ...
|
|
type OAuthV2ResponseAuthedUser struct {
|
|
ID string `json:"id"`
|
|
Scope string `json:"scope"`
|
|
AccessToken string `json:"access_token"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
TokenType string `json:"token_type"`
|
|
}
|
|
|
|
// OpenIDConnectResponse ...
|
|
type OpenIDConnectResponse struct {
|
|
Ok bool `json:"ok"`
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
IdToken string `json:"id_token"`
|
|
SlackResponse
|
|
}
|
|
|
|
type oauthConfig struct {
|
|
apiURL string
|
|
}
|
|
|
|
// OAuthOption configures package-level OAuth functions.
|
|
type OAuthOption func(*oauthConfig)
|
|
|
|
// OAuthOptionAPIURL overrides the default Slack API URL. Useful for testing.
|
|
func OAuthOptionAPIURL(url string) OAuthOption {
|
|
return func(c *oauthConfig) { c.apiURL = url }
|
|
}
|
|
|
|
func resolveOAuthAPIURL(opts []OAuthOption) string {
|
|
c := oauthConfig{apiURL: APIURL}
|
|
for _, o := range opts {
|
|
o(&c)
|
|
}
|
|
return c.apiURL
|
|
}
|
|
|
|
// GetOAuthToken retrieves an AccessToken.
|
|
// For more details, see GetOAuthTokenContext documentation.
|
|
func GetOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, err error) {
|
|
return GetOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...)
|
|
}
|
|
|
|
// GetOAuthTokenContext retrieves an AccessToken with a custom context.
|
|
// For more details, see GetOAuthResponseContext documentation.
|
|
func GetOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, err error) {
|
|
response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI, opts...)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
return response.AccessToken, response.Scope, nil
|
|
}
|
|
|
|
// GetBotOAuthToken retrieves top-level and bot AccessToken - https://api.slack.com/legacy/oauth#bot_user_access_tokens
|
|
// For more details, see GetBotOAuthTokenContext documentation.
|
|
func GetBotOAuthToken(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, bot OAuthResponseBot, err error) {
|
|
return GetBotOAuthTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...)
|
|
}
|
|
|
|
// GetBotOAuthTokenContext retrieves top-level and bot AccessToken with a custom context.
|
|
// For more details, see GetOAuthResponseContext documentation.
|
|
func GetBotOAuthTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (accessToken string, scope string, bot OAuthResponseBot, err error) {
|
|
response, err := GetOAuthResponseContext(ctx, client, clientID, clientSecret, code, redirectURI, opts...)
|
|
if err != nil {
|
|
return "", "", OAuthResponseBot{}, err
|
|
}
|
|
return response.AccessToken, response.Scope, response.Bot, nil
|
|
}
|
|
|
|
// GetOAuthResponse retrieves OAuth response.
|
|
// For more details, see GetOAuthResponseContext documentation.
|
|
func GetOAuthResponse(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthResponse, err error) {
|
|
return GetOAuthResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...)
|
|
}
|
|
|
|
// GetOAuthResponseContext retrieves OAuth response with custom context.
|
|
// Slack API docs: https://api.slack.com/methods/oauth.access
|
|
func GetOAuthResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthResponse, err error) {
|
|
values := url.Values{
|
|
"client_id": {clientID},
|
|
"client_secret": {clientSecret},
|
|
"code": {code},
|
|
"redirect_uri": {redirectURI},
|
|
}
|
|
response := &OAuthResponse{}
|
|
if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"oauth.access", values, response, discard{}); err != nil {
|
|
return nil, err
|
|
}
|
|
return response, response.Err()
|
|
}
|
|
|
|
// GetOAuthV2Response gets a V2 OAuth access token response.
|
|
// For more details, see GetOAuthV2ResponseContext documentation.
|
|
func GetOAuthV2Response(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthV2Response, err error) {
|
|
return GetOAuthV2ResponseContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...)
|
|
}
|
|
|
|
// GetOAuthV2ResponseContext with a context, gets a V2 OAuth access token response.
|
|
// Slack API docs: https://api.slack.com/methods/oauth.v2.access
|
|
func GetOAuthV2ResponseContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OAuthV2Response, err error) {
|
|
values := url.Values{
|
|
"client_id": {clientID},
|
|
"client_secret": {clientSecret},
|
|
"code": {code},
|
|
"redirect_uri": {redirectURI},
|
|
}
|
|
response := &OAuthV2Response{}
|
|
if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"oauth.v2.access", values, response, discard{}); err != nil {
|
|
return nil, err
|
|
}
|
|
return response, response.Err()
|
|
}
|
|
|
|
// RefreshOAuthV2Token with a context, gets a V2 OAuth access token response.
|
|
// For more details, see RefreshOAuthV2TokenContext documentation.
|
|
func RefreshOAuthV2Token(client httpClient, clientID, clientSecret, refreshToken string, opts ...OAuthOption) (resp *OAuthV2Response, err error) {
|
|
return RefreshOAuthV2TokenContext(context.Background(), client, clientID, clientSecret, refreshToken, opts...)
|
|
}
|
|
|
|
// RefreshOAuthV2TokenContext with a context, gets a V2 OAuth access token response.
|
|
// Slack API docs: https://api.slack.com/methods/oauth.v2.access
|
|
func RefreshOAuthV2TokenContext(ctx context.Context, client httpClient, clientID, clientSecret, refreshToken string, opts ...OAuthOption) (resp *OAuthV2Response, err error) {
|
|
values := url.Values{
|
|
"client_id": {clientID},
|
|
"client_secret": {clientSecret},
|
|
"refresh_token": {refreshToken},
|
|
"grant_type": {"refresh_token"},
|
|
}
|
|
response := &OAuthV2Response{}
|
|
if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"oauth.v2.access", values, response, discard{}); err != nil {
|
|
return nil, err
|
|
}
|
|
return response, response.Err()
|
|
}
|
|
|
|
// OpenIDConnectUserInfoResponse contains the response from openid.connect.userInfo.
|
|
//
|
|
// Some of the fields in the response to this method are preceded with https://slack.com/.
|
|
// These fields are Slack-specific, and they're from the perspective of Slack.
|
|
type OpenIDConnectUserInfoResponse struct {
|
|
Ok bool `json:"ok"`
|
|
|
|
Sub string `json:"sub"`
|
|
|
|
UserID string `json:"https://slack.com/user_id"`
|
|
TeamID string `json:"https://slack.com/team_id"`
|
|
|
|
Email string `json:"email"`
|
|
EmailVerified bool `json:"email_verified"`
|
|
DateEmailVerified int64 `json:"date_email_verified"`
|
|
|
|
Name string `json:"name"`
|
|
Picture string `json:"picture"`
|
|
GivenName string `json:"given_name"`
|
|
FamilyName string `json:"family_name"`
|
|
Locale string `json:"locale"`
|
|
|
|
TeamName string `json:"https://slack.com/team_name"`
|
|
TeamDomain string `json:"https://slack.com/team_domain"`
|
|
TeamImage34 string `json:"https://slack.com/team_image_34"`
|
|
TeamImage44 string `json:"https://slack.com/team_image_44"`
|
|
TeamImage68 string `json:"https://slack.com/team_image_68"`
|
|
TeamImage88 string `json:"https://slack.com/team_image_88"`
|
|
TeamImage102 string `json:"https://slack.com/team_image_102"`
|
|
TeamImage132 string `json:"https://slack.com/team_image_132"`
|
|
TeamImage230 string `json:"https://slack.com/team_image_230"`
|
|
|
|
// `TeamImageDefault` indicates whether the image is a default one (true), or someone
|
|
// uploaded their own (false).
|
|
TeamImageDefault bool `json:"https://slack.com/team_image_default"`
|
|
|
|
UserImage24 string `json:"https://slack.com/user_image_24"`
|
|
UserImage32 string `json:"https://slack.com/user_image_32"`
|
|
UserImage48 string `json:"https://slack.com/user_image_48"`
|
|
UserImage72 string `json:"https://slack.com/user_image_72"`
|
|
UserImage192 string `json:"https://slack.com/user_image_192"`
|
|
UserImage512 string `json:"https://slack.com/user_image_512"`
|
|
UserImage1024 string `json:"https://slack.com/user_image_1024"`
|
|
UserImageOriginal string `json:"https://slack.com/user_image_original"`
|
|
|
|
SlackResponse
|
|
}
|
|
|
|
// GetOpenIDConnectUserInfo returns the user info for the token.
|
|
// For more details, see GetOpenIDConnectUserInfoContext documentation.
|
|
func (api *Client) GetOpenIDConnectUserInfo() (*OpenIDConnectUserInfoResponse, error) {
|
|
return api.GetOpenIDConnectUserInfoContext(context.Background())
|
|
}
|
|
|
|
// GetOpenIDConnectUserInfoContext returns identity information about the user associated with the token.
|
|
// Slack API docs: https://docs.slack.dev/reference/methods/openid.connect.userInfo
|
|
func (api *Client) GetOpenIDConnectUserInfoContext(ctx context.Context) (*OpenIDConnectUserInfoResponse, error) {
|
|
values := url.Values{
|
|
"token": {api.token},
|
|
}
|
|
response := &OpenIDConnectUserInfoResponse{}
|
|
err := api.postMethod(ctx, "openid.connect.userInfo", values, response)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return response, response.Err()
|
|
}
|
|
|
|
// GetOpenIDConnectToken exchanges a temporary OAuth verifier code for an access token for Sign in with Slack.
|
|
// For more details, see GetOpenIDConnectTokenContext documentation.
|
|
func GetOpenIDConnectToken(client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OpenIDConnectResponse, err error) {
|
|
return GetOpenIDConnectTokenContext(context.Background(), client, clientID, clientSecret, code, redirectURI, opts...)
|
|
}
|
|
|
|
// GetOpenIDConnectTokenContext with a context, gets an access token for Sign in with Slack.
|
|
// Slack API docs: https://api.slack.com/methods/openid.connect.token
|
|
func GetOpenIDConnectTokenContext(ctx context.Context, client httpClient, clientID, clientSecret, code, redirectURI string, opts ...OAuthOption) (resp *OpenIDConnectResponse, err error) {
|
|
values := url.Values{
|
|
"client_id": {clientID},
|
|
"client_secret": {clientSecret},
|
|
"code": {code},
|
|
"redirect_uri": {redirectURI},
|
|
}
|
|
response := &OpenIDConnectResponse{}
|
|
if _, err = postForm(ctx, client, resolveOAuthAPIURL(opts)+"openid.connect.token", values, response, discard{}); err != nil {
|
|
return nil, err
|
|
}
|
|
return response, response.Err()
|
|
}
|