diff --git a/modules/twitch/client.auth.go b/modules/twitch/client.auth.go index da42a63..4e6bcc6 100644 --- a/modules/twitch/client.auth.go +++ b/modules/twitch/client.auth.go @@ -7,6 +7,8 @@ import ( "net/url" "time" + jsoniter "github.com/json-iterator/go" + "github.com/nicklaw5/helix/v2" ) @@ -26,17 +28,42 @@ func (c *Client) GetAuthorizationURL() string { }) } -func (c *Client) GetLoggedUser() (helix.User, error) { +func (c *Client) GetUserClient() (*helix.Client, error) { var authResp AuthResponse err := c.db.GetJSON(AuthKey, &authResp) if err != nil { - return helix.User{}, err + return nil, err } - client, err := helix.NewClient(&helix.Options{ + // Handle token expiration + if time.Now().After(authResp.Time.Add(time.Duration(authResp.ExpiresIn) * time.Second)) { + // Refresh tokens + refreshed, err := c.refreshAccessToken(authResp.RefreshToken) + if err != nil { + return nil, err + } + authResp.AccessToken = refreshed.AccessToken + authResp.RefreshToken = refreshed.RefreshToken + + // Save new token pair + err = c.db.PutJSON(AuthKey, authResp) + if err != nil { + return nil, err + } + } + + return helix.NewClient(&helix.Options{ ClientID: c.Config.APIClientID, ClientSecret: c.Config.APIClientSecret, UserAccessToken: authResp.AccessToken, }) +} + +func (c *Client) GetLoggedUser() (helix.User, error) { + client, err := c.GetUserClient() + if err != nil { + return helix.User{}, fmt.Errorf("failed getting API client for user: %w", err) + } + users, err := client.GetUsers(&helix.UsersParams{}) if err != nil { return helix.User{}, fmt.Errorf("failed looking up user: %w", err) @@ -44,6 +71,7 @@ func (c *Client) GetLoggedUser() (helix.User, error) { if len(users.Data.Users) < 1 { return helix.User{}, fmt.Errorf("no users found") } + return users.Data.Users[0], nil } @@ -95,6 +123,35 @@ func (c *Client) AuthorizeCallback(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, `

All done, you can close me now!

`) } +type RefreshResponse struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + TokenType string `json:"token_type"` + Scope []string `json:"scope"` +} + +func (c *Client) refreshAccessToken(refreshToken string) (r RefreshResponse, err error) { + // Exchange code for access/refresh tokens + query := url.Values{ + "client_id": {c.Config.APIClientID}, + "client_secret": {c.Config.APIClientSecret}, + "grant_type": {"refresh_token"}, + "refresh_token": {refreshToken}, + } + authRequest, err := http.NewRequest("POST", "https://id.twitch.tv/oauth2/token?"+query.Encode(), nil) + if err != nil { + return RefreshResponse{}, err + } + resp, err := http.DefaultClient.Do(authRequest) + if err != nil { + return RefreshResponse{}, err + } + defer resp.Body.Close() + var refreshResp RefreshResponse + err = jsoniter.ConfigFastest.NewDecoder(resp.Body).Decode(&refreshResp) + return refreshResp, err +} + func (c *Client) getRedirectURI() (string, error) { var severConfig struct { Bind string `json:"bind"` diff --git a/modules/twitch/client.eventsub.go b/modules/twitch/client.eventsub.go index 94ac127..e324186 100644 --- a/modules/twitch/client.eventsub.go +++ b/modules/twitch/client.eventsub.go @@ -118,16 +118,11 @@ func (c *Client) processEvent(message EventSubWebsocketMessage) { } func (c *Client) addSubscriptionsForSession(session string) error { - var authResp AuthResponse - err := c.db.GetJSON(AuthKey, &authResp) + client, err := c.GetUserClient() if err != nil { - return err + return fmt.Errorf("failed getting API client for user: %w", err) } - client, err := helix.NewClient(&helix.Options{ - ClientID: c.Config.APIClientID, - ClientSecret: c.Config.APIClientSecret, - UserAccessToken: authResp.AccessToken, - }) + users, err := client.GetUsers(&helix.UsersParams{}) if err != nil { return fmt.Errorf("failed looking up user: %w", err) @@ -136,6 +131,7 @@ func (c *Client) addSubscriptionsForSession(session string) error { return fmt.Errorf("no users found") } user := users.Data.Users[0] + transport := helix.EventSubTransport{ Method: "websocket", SessionID: session,