user authentication structure in place
This commit is contained in:
115
ttv/auth.go
115
ttv/auth.go
@@ -2,83 +2,96 @@ package ttv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/adeithe/go-twitch/api"
|
||||
"zomo.dev/largehadroncollider/db"
|
||||
"zomo.dev/largehadroncollider/db/db_cold"
|
||||
"zomo.dev/largehadroncollider/util"
|
||||
)
|
||||
|
||||
// sign in to twitch with each saved tokens
|
||||
const TWITCH_AUTH_URL = "https://id.twitch.tv/oauth2/token"
|
||||
|
||||
func initAuth(conf *util.Config, dbConn *db.DBConn) (*TwitchAuth, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
tokens, err := dbConn.Cold.GetAllUserAuth()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := api.New(conf.ClientID)
|
||||
twitchAuth := &TwitchAuth{ctx, client}
|
||||
|
||||
accounts, err := testTokens(ctx, client, tokens)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// run once synchronously then start looping in a thread
|
||||
twitchAuth.updateDetailsRefreshTokens(conf, dbConn)
|
||||
go twitchAuth.loopUpdateDetailsRefreshTokens(conf, dbConn)
|
||||
|
||||
for _, account := range accounts {
|
||||
err := dbConn.Cold.UpdateUserAuth(account.UserID, account.UserName, account.UserLogin, account.AccessToken, account.RefreshToken, account.TokenExpires)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &TwitchAuth{ ctx, client, accounts }, nil
|
||||
return twitchAuth, nil
|
||||
}
|
||||
|
||||
type TwitchAuth struct {
|
||||
Ctx context.Context
|
||||
Ctx context.Context
|
||||
Client *api.Client
|
||||
Accounts []db_cold.UserAuth
|
||||
}
|
||||
|
||||
func testTokens(ctx context.Context, client *api.Client, tokens []db_cold.UserAuth) ([]db_cold.UserAuth, error) {
|
||||
accounts := make([]db_cold.UserAuth, 0)
|
||||
for _, token := range tokens {
|
||||
account, err := testToken(ctx, client, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts = append(accounts, account)
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func testToken(ctx context.Context, client *api.Client, token db_cold.UserAuth) (db_cold.UserAuth, error) {
|
||||
// TODO check refresh time, refresh token if needed
|
||||
|
||||
users, err := client.Users.List().Do(ctx, api.WithBearerToken(token.AccessToken))
|
||||
func (twitch *TwitchAuth) doAuth(formData url.Values) (TwitchAuthTokenResp, error) {
|
||||
resp, err := http.PostForm(TWITCH_AUTH_URL, formData)
|
||||
if err != nil {
|
||||
return db_cold.UserAuth{}, err
|
||||
return TwitchAuthTokenResp{}, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return TwitchAuthTokenResp{}, err
|
||||
}
|
||||
|
||||
var authResp TwitchAuthTokenResp
|
||||
err = json.Unmarshal(bodyBytes, &authResp)
|
||||
if err != nil {
|
||||
return TwitchAuthTokenResp{}, err
|
||||
}
|
||||
|
||||
return authResp, nil
|
||||
}
|
||||
|
||||
func (twitch *TwitchAuth) DoAuth(conf *util.Config, code string) (TwitchAuthTokenResp, error) {
|
||||
formData := url.Values{
|
||||
"client_id": {conf.ClientID},
|
||||
"client_secret": {conf.ClientSecret},
|
||||
"redirect_uri": {conf.RedirectURI},
|
||||
"code": {code},
|
||||
"grant_type": {"authorization_code"},
|
||||
}
|
||||
|
||||
return twitch.doAuth(formData)
|
||||
}
|
||||
|
||||
func (twitch *TwitchAuth) DoRefresh(conf *util.Config, refreshToken string) (TwitchAuthTokenResp, error) {
|
||||
formData := url.Values{
|
||||
"client_id": {conf.ClientID},
|
||||
"client_secret": {conf.ClientSecret},
|
||||
"refresh_token": {refreshToken},
|
||||
"grant_type": {"refresh_token"},
|
||||
}
|
||||
|
||||
return twitch.doAuth(formData)
|
||||
}
|
||||
|
||||
func (twitch *TwitchAuth) GetTokenUser(accessToken string) (api.User, error) {
|
||||
return getTokenUser(twitch.Ctx, twitch.Client, accessToken)
|
||||
}
|
||||
|
||||
func getTokenUser(ctx context.Context, client *api.Client, accessToken string) (api.User, error) {
|
||||
users, err := client.Users.List().Do(ctx, api.WithBearerToken(accessToken))
|
||||
if err != nil {
|
||||
return api.User{}, err
|
||||
}
|
||||
|
||||
usersData := users.Data
|
||||
|
||||
if len(usersData) <= 0 {
|
||||
return db_cold.UserAuth{}, errors.New("user data returned an empty array")
|
||||
return api.User{}, errors.New("user data returned an empty array")
|
||||
}
|
||||
|
||||
// from twitch
|
||||
mainUser := usersData[0]
|
||||
token.UserLogin = mainUser.UserLogin
|
||||
token.UserName = mainUser.UserName
|
||||
token.UserEmail = mainUser.Email
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func refreshToken(token db_cold.UserAuth) (db_cold.UserAuth, error) {
|
||||
// TODO get new access token using refresh token
|
||||
// TODO this should be called regularly, as needed based on Expires
|
||||
return usersData[0], nil
|
||||
}
|
||||
|
||||
69
ttv/expiration.go
Normal file
69
ttv/expiration.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package ttv
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"zomo.dev/largehadroncollider/db"
|
||||
"zomo.dev/largehadroncollider/util"
|
||||
)
|
||||
|
||||
const REFRESH_INTERVAL = 5 * time.Minute
|
||||
|
||||
// token expiration thread
|
||||
|
||||
func (twitch *TwitchAuth) loopUpdateDetailsRefreshTokens(conf *util.Config, dbConn *db.DBConn) {
|
||||
for {
|
||||
// sleep until next interval
|
||||
now := time.Now()
|
||||
nextTime := now.Truncate(REFRESH_INTERVAL).Add(REFRESH_INTERVAL)
|
||||
time.Sleep(nextTime.Sub(now))
|
||||
|
||||
// check tokens
|
||||
err := twitch.updateDetailsRefreshTokens(conf, dbConn)
|
||||
if err != nil {
|
||||
log.Printf("Error Updating Tokens: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (twitch *TwitchAuth) updateDetailsRefreshTokens(conf *util.Config, dbConn *db.DBConn) error {
|
||||
now := time.Now().Add(REFRESH_INTERVAL)
|
||||
|
||||
tokens, err := dbConn.Cold.GetAllUserAuth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
if token.TokenExpires.Before(now) {
|
||||
// refresh and update details
|
||||
authResp, err := twitch.DoRefresh(conf, token.RefreshToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = twitch.UpdateUserDetails(dbConn, authResp.AccessToken, authResp.RefreshToken, authResp.ExpiresIn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// only update details
|
||||
err = twitch.UpdateUserDetails(dbConn, token.AccessToken, token.RefreshToken, token.TokenExpires)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/** tokenExpires: time.Time | time.Duration | int */
|
||||
func (twitch *TwitchAuth) UpdateUserDetails(dbConn *db.DBConn, accessToken, refreshToken string, tokenExpires any) error {
|
||||
user, err := twitch.GetTokenUser(accessToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbConn.Cold.UpdateUserAuth(user.UserID, user.UserName, user.UserLogin, accessToken, refreshToken, tokenExpires)
|
||||
}
|
||||
22
ttv/main.go
22
ttv/main.go
@@ -11,9 +11,29 @@ func InitTwitchConn(conf *util.Config, dbConn *db.DBConn) (*TwitchConn, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &TwitchConn{ auth }, nil
|
||||
return &TwitchConn{auth}, nil
|
||||
}
|
||||
|
||||
type TwitchConn struct {
|
||||
Auth *TwitchAuth
|
||||
}
|
||||
|
||||
type TwitchAuthRespOk struct {
|
||||
Code string `json:"code"`
|
||||
Scope string `json:"scope"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type TwitchAuthRespErr struct {
|
||||
Err string `json:"error"`
|
||||
ErrDesc string `json:"error_description"`
|
||||
State string `json:"state"`
|
||||
}
|
||||
|
||||
type TwitchAuthTokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
Scope []string `json:"scope"`
|
||||
TokenType string `json:"token_type"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user