refactor: Move GetToken method into auth client and rename auth client from Client to Auth
This commit is contained in:
		
							parent
							
								
									adf49ce4bb
								
							
						
					
					
						commit
						02d09446ce
					
				| 
						 | 
					@ -41,7 +41,7 @@ const HelixBaseUrl = "https://api.twitch.tv/helix"
 | 
				
			||||||
type API struct {
 | 
					type API struct {
 | 
				
			||||||
	client  *http.Client
 | 
						client  *http.Client
 | 
				
			||||||
	baseUrl *url.URL
 | 
						baseUrl *url.URL
 | 
				
			||||||
	Auth    *auth.Client
 | 
						Auth    *auth.Auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Ads           *ads.Ads
 | 
						Ads           *ads.Ads
 | 
				
			||||||
	Analytics     *analytics.Analytics
 | 
						Analytics     *analytics.Analytics
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ type API struct {
 | 
				
			||||||
	Whispers      *whispers.Whispers
 | 
						Whispers      *whispers.Whispers
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func New(client *http.Client, baseUrl *url.URL, authClient *auth.Client) *API {
 | 
					func New(client *http.Client, baseUrl *url.URL, authClient *auth.Auth) *API {
 | 
				
			||||||
	return &API{
 | 
						return &API{
 | 
				
			||||||
		client:  client,
 | 
							client:  client,
 | 
				
			||||||
		baseUrl: baseUrl,
 | 
							baseUrl: baseUrl,
 | 
				
			||||||
| 
						 | 
					@ -117,7 +117,7 @@ func NewDefault(clientId, clientSecret, redirectUri string) *API {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	baseUrl, _ := url.Parse(HelixBaseUrl)
 | 
						baseUrl, _ := url.Parse(HelixBaseUrl)
 | 
				
			||||||
	authClient := auth.NewClient(clientId, clientSecret, redirectUri)
 | 
						authClient := auth.New(clientId, clientSecret, redirectUri)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return New(client, baseUrl, authClient)
 | 
						return New(client, baseUrl, authClient)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,118 @@
 | 
				
			||||||
 | 
					package auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/google/go-querystring/query"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Auth struct {
 | 
				
			||||||
 | 
						client *http.Client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						clientId     string
 | 
				
			||||||
 | 
						clientSecret string
 | 
				
			||||||
 | 
						redirectUri  string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						stateStorage StateStorage
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func New(clientId string, clientSecret string, redirectUri string) *Auth {
 | 
				
			||||||
 | 
						return NewWithClient(clientId, clientSecret, redirectUri, http.DefaultClient)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func NewWithClient(clientId string, clientSecret string, redirectUri string, client *http.Client) *Auth {
 | 
				
			||||||
 | 
						return &Auth{
 | 
				
			||||||
 | 
							client: client,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							clientId:     clientId,
 | 
				
			||||||
 | 
							clientSecret: clientSecret,
 | 
				
			||||||
 | 
							redirectUri:  redirectUri,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							stateStorage: NewHttpCookieStateStorage(StateStorageCookie),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetToken exchanges an authorization code or refresh token for an access token.
 | 
				
			||||||
 | 
					func (a *Auth) GetToken(ctx context.Context, params *GetTokenParams) (*Token, error) {
 | 
				
			||||||
 | 
						v, err := query.Values(params)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						req, err := http.NewRequestWithContext(ctx, http.MethodPost, TokenUrl, strings.NewReader(v.Encode()))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res, err := a.client.Do(req)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer res.Body.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						statusOK := res.StatusCode >= 200 && res.StatusCode < 300
 | 
				
			||||||
 | 
						if !statusOK {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("failed to get token (%d)", res.StatusCode)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var token Token
 | 
				
			||||||
 | 
						if err := json.NewDecoder(res.Body).Decode(&token); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						token.Expiry = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &token, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetTokenFromCode exchanges an authorization code for an access token.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/#oidc-authorization-code-grant-flow
 | 
				
			||||||
 | 
					func (a *Auth) GetTokenFromCode(ctx context.Context, code string) (*Token, error) {
 | 
				
			||||||
 | 
						return a.GetToken(ctx, &GetTokenParams{
 | 
				
			||||||
 | 
							ClientId:     a.clientId,
 | 
				
			||||||
 | 
							ClientSecret: a.clientSecret,
 | 
				
			||||||
 | 
							Code:         code,
 | 
				
			||||||
 | 
							GrantType:    "authorization_code",
 | 
				
			||||||
 | 
							RedirectUri:  a.redirectUri,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RefreshToken exchanges a refresh token for an access token.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// https://dev.twitch.tv/docs/authentication/refresh-tokens/
 | 
				
			||||||
 | 
					func (a *Auth) RefreshToken(ctx context.Context, token *Token) (*Token, error) {
 | 
				
			||||||
 | 
						return a.GetToken(ctx, &GetTokenParams{
 | 
				
			||||||
 | 
							ClientId:     a.clientId,
 | 
				
			||||||
 | 
							ClientSecret: a.clientSecret,
 | 
				
			||||||
 | 
							Code:         token.RefreshToken,
 | 
				
			||||||
 | 
							GrantType:    "refresh_token",
 | 
				
			||||||
 | 
							RedirectUri:  a.redirectUri,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// WithStateStorage sets the instance's state storage,
 | 
				
			||||||
 | 
					// which is used to store the state parameter between requests.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// By default, the http cookie state storage is used.
 | 
				
			||||||
 | 
					func (a *Auth) WithStateStorage(storage StateStorage) *Auth {
 | 
				
			||||||
 | 
						a.stateStorage = storage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return a
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -51,7 +51,7 @@ type AuthorizeParams struct {
 | 
				
			||||||
const AuthorizeUrl = "https://id.twitch.tv/oauth2/authorize"
 | 
					const AuthorizeUrl = "https://id.twitch.tv/oauth2/authorize"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AuthorizeUrl returns the URL to redirect the user to for authorization.
 | 
					// AuthorizeUrl returns the URL to redirect the user to for authorization.
 | 
				
			||||||
func (c *Client) AuthorizeUrl(params *AuthorizeParams) *url.URL {
 | 
					func (c *Auth) AuthorizeUrl(params *AuthorizeParams) *url.URL {
 | 
				
			||||||
	v, _ := query.Values(params)
 | 
						v, _ := query.Values(params)
 | 
				
			||||||
	v.Set("client_id", c.clientId)
 | 
						v.Set("client_id", c.clientId)
 | 
				
			||||||
	v.Set("redirect_uri", c.redirectUri)
 | 
						v.Set("redirect_uri", c.redirectUri)
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ func (c *Client) AuthorizeUrl(params *AuthorizeParams) *url.URL {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type AuthorizeHandler struct {
 | 
					type AuthorizeHandler struct {
 | 
				
			||||||
	client *Client
 | 
						client *Auth
 | 
				
			||||||
	scopes []Scope
 | 
						scopes []Scope
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -69,7 +69,7 @@ var _ http.Handler = (*AuthorizeHandler)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AuthorizeHandler returns an http.Handler that redirects the user to the
 | 
					// AuthorizeHandler returns an http.Handler that redirects the user to the
 | 
				
			||||||
// authorization URL.
 | 
					// authorization URL.
 | 
				
			||||||
func (c *Client) AuthorizeHandler(scopes []Scope) http.Handler {
 | 
					func (c *Auth) AuthorizeHandler(scopes []Scope) http.Handler {
 | 
				
			||||||
	return &AuthorizeHandler{
 | 
						return &AuthorizeHandler{
 | 
				
			||||||
		client: c,
 | 
							client: c,
 | 
				
			||||||
		scopes: scopes,
 | 
							scopes: scopes,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CallbackHandler struct {
 | 
					type CallbackHandler struct {
 | 
				
			||||||
	client  *Client
 | 
						client  *Auth
 | 
				
			||||||
	handler TokenHandler
 | 
						handler TokenHandler
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ var _ http.Handler = (*CallbackHandler)(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CallbackHandler returns an http.Handler that handles callback responses
 | 
					// CallbackHandler returns an http.Handler that handles callback responses
 | 
				
			||||||
// from the twitch authentication server.
 | 
					// from the twitch authentication server.
 | 
				
			||||||
func (c *Client) CallbackHandler(h TokenHandler) http.Handler {
 | 
					func (c *Auth) CallbackHandler(h TokenHandler) http.Handler {
 | 
				
			||||||
	return &CallbackHandler{
 | 
						return &CallbackHandler{
 | 
				
			||||||
		client:  c,
 | 
							client:  c,
 | 
				
			||||||
		handler: h,
 | 
							handler: h,
 | 
				
			||||||
| 
						 | 
					@ -56,7 +56,7 @@ func (c *CallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
	scope := q.Get("scope")
 | 
						scope := q.Get("scope")
 | 
				
			||||||
	_ = scope
 | 
						_ = scope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	token, err := c.client.GetToken(r.Context(), code)
 | 
						token, err := c.client.GetTokenFromCode(r.Context(), code)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
							http.Error(w, err.Error(), http.StatusInternalServerError)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
package auth
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "context"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type Client struct {
 | 
					 | 
				
			||||||
	clientId     string
 | 
					 | 
				
			||||||
	clientSecret string
 | 
					 | 
				
			||||||
	redirectUri  string
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	stateStorage StateStorage
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func NewClient(clientId string, clientSecret string, redirectUri string) *Client {
 | 
					 | 
				
			||||||
	return &Client{
 | 
					 | 
				
			||||||
		clientId:     clientId,
 | 
					 | 
				
			||||||
		clientSecret: clientSecret,
 | 
					 | 
				
			||||||
		redirectUri:  redirectUri,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		stateStorage: NewHttpCookieStateStorage(StateStorageCookie),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// GetToken exchanges an authorization code for an access token.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// https://dev.twitch.tv/docs/authentication/getting-tokens-oidc/#oidc-authorization-code-grant-flow
 | 
					 | 
				
			||||||
func (c *Client) GetToken(ctx context.Context, code string) (*Token, error) {
 | 
					 | 
				
			||||||
	return GetToken(ctx, &GetTokenParams{
 | 
					 | 
				
			||||||
		ClientId:     c.clientId,
 | 
					 | 
				
			||||||
		ClientSecret: c.clientSecret,
 | 
					 | 
				
			||||||
		Code:         code,
 | 
					 | 
				
			||||||
		GrantType:    "authorization_code",
 | 
					 | 
				
			||||||
		RedirectUri:  c.redirectUri,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RefreshToken exchanges a refresh token for an access token.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// https://dev.twitch.tv/docs/authentication/refresh-tokens/
 | 
					 | 
				
			||||||
func (c *Client) RefreshToken(ctx context.Context, token *Token) (*Token, error) {
 | 
					 | 
				
			||||||
	return GetToken(ctx, &GetTokenParams{
 | 
					 | 
				
			||||||
		ClientId:     c.clientId,
 | 
					 | 
				
			||||||
		ClientSecret: c.clientSecret,
 | 
					 | 
				
			||||||
		Code:         token.RefreshToken,
 | 
					 | 
				
			||||||
		GrantType:    "refresh_token",
 | 
					 | 
				
			||||||
		RedirectUri:  c.redirectUri,
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// WithStateStorage sets the instance's state storage,
 | 
					 | 
				
			||||||
// which is used to store the state parameter between requests.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
// By default, the http cookie state storage is used.
 | 
					 | 
				
			||||||
func (c *Client) WithStateStorage(storage StateStorage) *Client {
 | 
					 | 
				
			||||||
	c.stateStorage = storage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return c
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,8 @@
 | 
				
			||||||
package auth
 | 
					package auth
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
					 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"fmt"
 | 
					 | 
				
			||||||
	"net/http"
 | 
					 | 
				
			||||||
	"strings"
 | 
					 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/google/go-querystring/query"
 | 
					 | 
				
			||||||
	"golang.org/x/oauth2"
 | 
						"golang.org/x/oauth2"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,49 +32,6 @@ func (t *Token) Underlying() *oauth2.Token {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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(ctx context.Context, params *GetTokenParams) (*Token, error) {
 | 
					 | 
				
			||||||
	v, err := query.Values(params)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, TokenUrl, strings.NewReader(v.Encode()))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	res, err := http.DefaultClient.Do(req)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer res.Body.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	statusOK := res.StatusCode >= 200 && res.StatusCode < 300
 | 
					 | 
				
			||||||
	if !statusOK {
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("failed to get token (%d)", res.StatusCode)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var token Token
 | 
					 | 
				
			||||||
	if err := json.NewDecoder(res.Body).Decode(&token); err != nil {
 | 
					 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	token.Expiry = time.Now().Add(time.Duration(token.ExpiresIn) * time.Second)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &token, nil
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type TokenHandler interface {
 | 
					type TokenHandler interface {
 | 
				
			||||||
	Handle(state string, token string)
 | 
						Handle(state string, token string)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,13 +8,13 @@ import (
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type TokenSource struct {
 | 
					type TokenSource struct {
 | 
				
			||||||
	client *Client
 | 
						client *Auth
 | 
				
			||||||
	token  *Token
 | 
						token  *Token
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mu sync.Mutex
 | 
						mu sync.Mutex
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (c *Client) TokenSource(token *Token) oauth2.TokenSource {
 | 
					func (c *Auth) TokenSource(token *Token) oauth2.TokenSource {
 | 
				
			||||||
	return &TokenSource{
 | 
						return &TokenSource{
 | 
				
			||||||
		client: c,
 | 
							client: c,
 | 
				
			||||||
		token:  token,
 | 
							token:  token,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue