package auth import ( "encoding/json" "log/slog" "net/http" "strings" "time" "github.com/google/go-querystring/query" "golang.org/x/oauth2" ) type Token struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` Expiry time.Time `json:"-"` // Only present when using OIDC. IdToken *string `json:"id_token"` RefreshToken string `json:"refresh_token"` Scope []string `json:"scope"` TokenType string `json:"token_type"` } func (t *Token) Valid() bool { return t.AccessToken != "" && t.Expiry.After(time.Now()) } func (t *Token) Underlying() *oauth2.Token { return &oauth2.Token{ AccessToken: t.AccessToken, TokenType: t.TokenType, RefreshToken: t.RefreshToken, Expiry: t.Expiry, } } const TokenUrl = "https://id.twitch.tv/oauth2/token" type GetTokenParams struct { ClientId string `url:"client_id"` ClientSecret string `url:"client_secret"` Code string `url:"code"` GrantType string `url:"grant_type"` RedirectUri string `url:"redirect_uri"` } func GetToken(params *GetTokenParams) (*Token, error) { v, err := query.Values(params) if err != nil { return nil, err } res, err := http.Post(TokenUrl, "application/x-www-form-urlencoded", strings.NewReader(v.Encode())) if err != nil { return nil, err } defer res.Body.Close() body := make([]byte, res.ContentLength) if _, err := res.Body.Read(body); err != nil { return nil, err } var token Token if err := json.Unmarshal(body, &token); err != nil { slog.Debug("failed to decode token", slog.Any("error", err), slog.String("body", string(body))) return nil, err } token.Expiry = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second) return &token, nil } type TokenHandler interface { Handle(state string, token string) } type TokenHandlerFunc func(state string, token string) var _ TokenHandler = (*TokenHandlerFunc)(nil) func (f TokenHandlerFunc) Handle(state string, token string) { f(state, token) }