2022-09-10 11:39:07 +00:00
|
|
|
package access
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
2022-10-22 22:08:02 +00:00
|
|
|
"gorm.io/gorm"
|
|
|
|
"gorm.io/gorm/clause"
|
2022-09-10 11:39:07 +00:00
|
|
|
|
|
|
|
"github.com/Luzifer/go_helpers/v2/str"
|
|
|
|
"github.com/Luzifer/twitch-bot/pkg/database"
|
|
|
|
"github.com/Luzifer/twitch-bot/pkg/twitch"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
coreMetaKeyBotToken = "bot_access_token"
|
|
|
|
coreMetaKeyBotRefreshToken = "bot_refresh_token" //#nosec:G101 // That's a key, not a credential
|
|
|
|
)
|
|
|
|
|
|
|
|
type (
|
|
|
|
ClientConfig struct {
|
|
|
|
TwitchClient string
|
|
|
|
TwitchClientSecret string
|
|
|
|
|
|
|
|
FallbackToken string
|
|
|
|
|
|
|
|
TokenUpdateHook func()
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
extendedPermission struct {
|
|
|
|
Channel string `gorm:"primaryKey"`
|
|
|
|
AccessToken string
|
|
|
|
RefreshToken string
|
|
|
|
Scopes string
|
|
|
|
}
|
|
|
|
|
2022-09-10 11:39:07 +00:00
|
|
|
Service struct{ db database.Connector }
|
|
|
|
)
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
func New(db database.Connector) (*Service, error) {
|
|
|
|
return &Service{db}, errors.Wrap(
|
|
|
|
db.DB().AutoMigrate(&extendedPermission{}),
|
|
|
|
"migrating database schema",
|
|
|
|
)
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) GetBotTwitchClient(cfg ClientConfig) (*twitch.Client, error) {
|
|
|
|
var botAccessToken, botRefreshToken string
|
|
|
|
|
|
|
|
err := s.db.ReadEncryptedCoreMeta(coreMetaKeyBotToken, &botAccessToken)
|
|
|
|
switch {
|
|
|
|
case errors.Is(err, nil):
|
|
|
|
// This is fine
|
|
|
|
|
|
|
|
case errors.Is(err, database.ErrCoreMetaNotFound):
|
|
|
|
botAccessToken = cfg.FallbackToken
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.Wrap(err, "getting bot access token from database")
|
|
|
|
}
|
|
|
|
|
2022-09-10 18:36:55 +00:00
|
|
|
if err = s.db.ReadEncryptedCoreMeta(coreMetaKeyBotRefreshToken, &botRefreshToken); err != nil && !errors.Is(err, database.ErrCoreMetaNotFound) {
|
2022-09-10 11:39:07 +00:00
|
|
|
return nil, errors.Wrap(err, "getting bot refresh token from database")
|
|
|
|
}
|
|
|
|
|
|
|
|
twitchClient := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, botAccessToken, botRefreshToken)
|
|
|
|
twitchClient.SetTokenUpdateHook(s.SetBotTwitchCredentials)
|
|
|
|
|
|
|
|
return twitchClient, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) GetTwitchClientForChannel(channel string, cfg ClientConfig) (*twitch.Client, error) {
|
2022-10-22 22:08:02 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
perm extendedPermission
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
if err = s.db.DB().First(&perm, "channel = ?", channel).Error; err != nil {
|
|
|
|
return nil, errors.Wrap(err, "getting twitch credential from database")
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
if perm.AccessToken, err = s.db.DecryptField(perm.AccessToken); err != nil {
|
2022-09-10 11:39:07 +00:00
|
|
|
return nil, errors.Wrap(err, "decrypting access token")
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
if perm.RefreshToken, err = s.db.DecryptField(perm.RefreshToken); err != nil {
|
2022-09-10 11:39:07 +00:00
|
|
|
return nil, errors.Wrap(err, "decrypting refresh token")
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
scopes := strings.Split(perm.Scopes, " ")
|
2022-09-10 11:39:07 +00:00
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
tc := twitch.New(cfg.TwitchClient, cfg.TwitchClientSecret, perm.AccessToken, perm.RefreshToken)
|
2022-09-10 11:39:07 +00:00
|
|
|
tc.SetTokenUpdateHook(func(at, rt string) error {
|
|
|
|
return errors.Wrap(s.SetExtendedTwitchCredentials(channel, at, rt, scopes), "updating extended permissions token")
|
|
|
|
})
|
|
|
|
|
|
|
|
return tc, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) HasAnyPermissionForChannel(channel string, scopes ...string) (bool, error) {
|
2022-10-22 22:08:02 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
perm extendedPermission
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
if err = s.db.DB().First(&perm, "channel = ?", channel).Error; err != nil {
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
2022-09-10 11:39:07 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
2022-10-22 22:08:02 +00:00
|
|
|
return false, errors.Wrap(err, "getting twitch credential from database")
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
storedScopes := strings.Split(perm.Scopes, " ")
|
2022-09-10 11:39:07 +00:00
|
|
|
|
|
|
|
for _, scope := range scopes {
|
|
|
|
if str.StringInSlice(scope, storedScopes) {
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) HasPermissionsForChannel(channel string, scopes ...string) (bool, error) {
|
2022-10-22 22:08:02 +00:00
|
|
|
var (
|
|
|
|
err error
|
|
|
|
perm extendedPermission
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
if err = s.db.DB().First(&perm, "channel = ?", channel).Error; err != nil {
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
2022-09-10 11:39:07 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
2022-10-22 22:08:02 +00:00
|
|
|
return false, errors.Wrap(err, "getting twitch credential from database")
|
2022-09-10 11:39:07 +00:00
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
storedScopes := strings.Split(perm.Scopes, " ")
|
2022-09-10 11:39:07 +00:00
|
|
|
|
|
|
|
for _, scope := range scopes {
|
|
|
|
if !str.StringInSlice(scope, storedScopes) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) RemoveExendedTwitchCredentials(channel string) error {
|
2022-10-22 22:08:02 +00:00
|
|
|
return errors.Wrap(
|
|
|
|
s.db.DB().Delete(&extendedPermission{}, "channel = ?", channel).Error,
|
|
|
|
"deleting data from table",
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) SetBotTwitchCredentials(accessToken, refreshToken string) (err error) {
|
|
|
|
if err = s.db.StoreEncryptedCoreMeta(coreMetaKeyBotToken, accessToken); err != nil {
|
|
|
|
return errors.Wrap(err, "storing bot access token")
|
|
|
|
}
|
|
|
|
|
|
|
|
if err = s.db.StoreEncryptedCoreMeta(coreMetaKeyBotRefreshToken, refreshToken); err != nil {
|
|
|
|
return errors.Wrap(err, "storing bot refresh token")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s Service) SetExtendedTwitchCredentials(channel, accessToken, refreshToken string, scope []string) (err error) {
|
|
|
|
if accessToken, err = s.db.EncryptField(accessToken); err != nil {
|
|
|
|
return errors.Wrap(err, "encrypting access token")
|
|
|
|
}
|
|
|
|
|
|
|
|
if refreshToken, err = s.db.EncryptField(refreshToken); err != nil {
|
|
|
|
return errors.Wrap(err, "encrypting refresh token")
|
|
|
|
}
|
|
|
|
|
2022-10-22 22:08:02 +00:00
|
|
|
return errors.Wrap(
|
|
|
|
s.db.DB().Clauses(clause.OnConflict{
|
|
|
|
Columns: []clause.Column{{Name: "channel"}},
|
|
|
|
DoUpdates: clause.AssignmentColumns([]string{"access_token", "refresh_token", "scopes"}),
|
|
|
|
}).Create(extendedPermission{
|
|
|
|
Channel: channel,
|
|
|
|
AccessToken: accessToken,
|
|
|
|
RefreshToken: refreshToken,
|
|
|
|
Scopes: strings.Join(scope, " "),
|
|
|
|
}).Error,
|
|
|
|
"inserting data into table",
|
2022-09-10 11:39:07 +00:00
|
|
|
)
|
|
|
|
}
|