2021-07-20 04:22:29 +02:00
package user
import (
"database/sql"
"fmt"
"sort"
"strings"
"time"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/utils"
"github.com/teris-io/shortid"
log "github.com/sirupsen/logrus"
)
var _datastore * data . Datastore
2022-03-07 05:09:55 +01:00
const (
moderatorScopeKey = "MODERATOR"
minSuggestedUsernamePoolLength = 10
)
2021-11-03 03:27:41 +01:00
2021-09-12 09:18:15 +02:00
// User represents a single chat user.
2021-07-20 04:22:29 +02:00
type User struct {
2021-09-12 09:18:15 +02:00
ID string ` json:"id" `
2021-07-20 04:22:29 +02:00
AccessToken string ` json:"-" `
DisplayName string ` json:"displayName" `
DisplayColor int ` json:"displayColor" `
CreatedAt time . Time ` json:"createdAt" `
DisabledAt * time . Time ` json:"disabledAt,omitempty" `
PreviousNames [ ] string ` json:"previousNames" `
NameChangedAt * time . Time ` json:"nameChangedAt,omitempty" `
2022-03-07 05:09:55 +01:00
Scopes [ ] string ` json:"scopes,omitempty" `
IsBot bool ` json:"isBot" `
2021-07-20 04:22:29 +02:00
}
2021-09-12 09:18:15 +02:00
// IsEnabled will return if this single user is enabled.
2021-07-20 04:22:29 +02:00
func ( u * User ) IsEnabled ( ) bool {
return u . DisabledAt == nil
}
2021-11-03 03:27:41 +01:00
// IsModerator will return if the user has moderation privileges.
func ( u * User ) IsModerator ( ) bool {
_ , hasModerationScope := utils . FindInSlice ( u . Scopes , moderatorScopeKey )
return hasModerationScope
}
2021-09-12 09:18:15 +02:00
// SetupUsers will perform the initial initialization of the user package.
2021-07-20 04:22:29 +02:00
func SetupUsers ( ) {
_datastore = data . GetDatastore ( )
}
2021-09-12 09:18:15 +02:00
// CreateAnonymousUser will create a new anonymous user with the provided display name.
2022-01-12 19:18:08 +01:00
func CreateAnonymousUser ( displayName string ) ( * User , error ) {
2021-07-20 04:22:29 +02:00
id := shortid . MustGenerate ( )
accessToken , err := utils . GenerateAccessToken ( )
if err != nil {
log . Errorln ( "Unable to create access token for new user" )
return nil , err
}
if displayName == "" {
2022-01-12 19:18:08 +01:00
suggestedUsernamesList := data . GetSuggestedUsernamesList ( )
if len ( suggestedUsernamesList ) >= minSuggestedUsernamePoolLength {
index := utils . RandomIndex ( len ( suggestedUsernamesList ) )
displayName = suggestedUsernamesList [ index ]
} else {
displayName = utils . GeneratePhrase ( )
}
2021-07-20 04:22:29 +02:00
}
displayColor := utils . GenerateRandomDisplayColor ( )
user := & User {
2021-09-12 09:18:15 +02:00
ID : id ,
2021-07-20 04:22:29 +02:00
AccessToken : accessToken ,
DisplayName : displayName ,
DisplayColor : displayColor ,
CreatedAt : time . Now ( ) ,
}
if err := create ( user ) ; err != nil {
return nil , err
}
return user , nil
}
2021-09-12 09:18:15 +02:00
// ChangeUsername will change the user associated to userID from one display name to another.
func ChangeUsername ( userID string , username string ) {
2021-07-20 04:22:29 +02:00
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
tx , err := _datastore . DB . Begin ( )
if err != nil {
log . Debugln ( err )
}
defer func ( ) {
if err := tx . Rollback ( ) ; err != nil {
log . Debugln ( err )
}
} ( )
stmt , err := tx . Prepare ( "UPDATE users SET display_name = ?, previous_names = previous_names || ?, namechanged_at = ? WHERE id = ?" )
if err != nil {
log . Debugln ( err )
}
defer stmt . Close ( )
2021-09-12 09:18:15 +02:00
_ , err = stmt . Exec ( username , fmt . Sprintf ( ",%s" , username ) , time . Now ( ) , userID )
2021-07-20 04:22:29 +02:00
if err != nil {
log . Errorln ( err )
}
if err := tx . Commit ( ) ; err != nil {
2021-09-12 09:18:15 +02:00
log . Errorln ( "error changing display name of user" , userID , err )
2021-07-20 04:22:29 +02:00
}
}
func create ( user * User ) error {
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
tx , err := _datastore . DB . Begin ( )
if err != nil {
log . Debugln ( err )
}
defer func ( ) {
_ = tx . Rollback ( )
} ( )
stmt , err := tx . Prepare ( "INSERT INTO users(id, access_token, display_name, display_color, previous_names, created_at) values(?, ?, ?, ?, ?, ?)" )
if err != nil {
log . Debugln ( err )
}
defer stmt . Close ( )
2021-09-12 09:18:15 +02:00
_ , err = stmt . Exec ( user . ID , user . AccessToken , user . DisplayName , user . DisplayColor , user . DisplayName , user . CreatedAt )
2021-07-20 04:22:29 +02:00
if err != nil {
log . Errorln ( "error creating new user" , err )
}
return tx . Commit ( )
}
2021-11-03 03:27:41 +01:00
// SetEnabled will set the enabled status of a single user by ID.
2021-07-20 04:22:29 +02:00
func SetEnabled ( userID string , enabled bool ) error {
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
tx , err := _datastore . DB . Begin ( )
if err != nil {
return err
}
defer tx . Rollback ( ) //nolint
var stmt * sql . Stmt
if ! enabled {
stmt , err = tx . Prepare ( "UPDATE users SET disabled_at=DATETIME('now', 'localtime') WHERE id IS ?" )
} else {
stmt , err = tx . Prepare ( "UPDATE users SET disabled_at=null WHERE id IS ?" )
}
if err != nil {
return err
}
defer stmt . Close ( )
if _ , err := stmt . Exec ( userID ) ; err != nil {
return err
}
return tx . Commit ( )
}
// GetUserByToken will return a user by an access token.
func GetUserByToken ( token string ) * User {
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
2021-11-03 03:27:41 +01:00
query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, scopes FROM users WHERE access_token = ?"
2021-07-20 04:22:29 +02:00
row := _datastore . DB . QueryRow ( query , token )
return getUserFromRow ( row )
}
2021-11-03 03:27:41 +01:00
// SetModerator will add or remove moderator status for a single user by ID.
func SetModerator ( userID string , isModerator bool ) error {
if isModerator {
return addScopeToUser ( userID , moderatorScopeKey )
}
return removeScopeFromUser ( userID , moderatorScopeKey )
}
func addScopeToUser ( userID string , scope string ) error {
u := GetUserByID ( userID )
scopesString := u . Scopes
scopes := utils . StringSliceToMap ( scopesString )
scopes [ scope ] = true
scopesSlice := utils . StringMapKeys ( scopes )
return setScopesOnUser ( userID , scopesSlice )
}
func removeScopeFromUser ( userID string , scope string ) error {
u := GetUserByID ( userID )
scopesString := u . Scopes
scopes := utils . StringSliceToMap ( scopesString )
delete ( scopes , scope )
scopesSlice := utils . StringMapKeys ( scopes )
return setScopesOnUser ( userID , scopesSlice )
}
func setScopesOnUser ( userID string , scopes [ ] string ) error {
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
tx , err := _datastore . DB . Begin ( )
if err != nil {
return err
}
defer tx . Rollback ( ) //nolint
scopesSliceString := strings . TrimSpace ( strings . Join ( scopes , "," ) )
stmt , err := tx . Prepare ( "UPDATE users SET scopes=? WHERE id IS ?" )
if err != nil {
return err
}
defer stmt . Close ( )
var val * string
if scopesSliceString == "" {
val = nil
} else {
val = & scopesSliceString
}
if _ , err := stmt . Exec ( val , userID ) ; err != nil {
return err
}
return tx . Commit ( )
}
2021-09-12 09:18:15 +02:00
// GetUserByID will return a user by a user ID.
func GetUserByID ( id string ) * User {
2021-07-20 04:22:29 +02:00
_datastore . DbLock . Lock ( )
defer _datastore . DbLock . Unlock ( )
2021-11-03 03:27:41 +01:00
query := "SELECT id, display_name, display_color, created_at, disabled_at, previous_names, namechanged_at, scopes FROM users WHERE id = ?"
2021-07-20 04:22:29 +02:00
row := _datastore . DB . QueryRow ( query , id )
if row == nil {
log . Errorln ( row )
return nil
}
return getUserFromRow ( row )
}
// GetDisabledUsers will return back all the currently disabled users that are not API users.
func GetDisabledUsers ( ) [ ] * User {
2021-11-03 03:27:41 +01:00
query := "SELECT id, display_name, scopes, display_color, created_at, disabled_at, previous_names, namechanged_at FROM users WHERE disabled_at IS NOT NULL AND type IS NOT 'API'"
2021-07-20 04:22:29 +02:00
rows , err := _datastore . DB . Query ( query )
if err != nil {
log . Errorln ( err )
return nil
}
defer rows . Close ( )
users := getUsersFromRows ( rows )
sort . Slice ( users , func ( i , j int ) bool {
return users [ i ] . DisabledAt . Before ( * users [ j ] . DisabledAt )
} )
return users
}
2021-11-03 03:27:41 +01:00
// GetModeratorUsers will return a list of users with moderator access.
func GetModeratorUsers ( ) [ ] * User {
query := ` SELECT id , display_name , scopes , display_color , created_at , disabled_at , previous_names , namechanged_at FROM (
WITH RECURSIVE split ( id , display_name , scopes , display_color , created_at , disabled_at , previous_names , namechanged_at , scope , rest ) AS (
SELECT id , display_name , scopes , display_color , created_at , disabled_at , previous_names , namechanged_at , ' ' , scopes || ',' FROM users
UNION ALL
SELECT id , display_name , scopes , display_color , created_at , disabled_at , previous_names , namechanged_at ,
substr ( rest , 0 , instr ( rest , ',' ) ) ,
substr ( rest , instr ( rest , ',' ) + 1 )
FROM split
WHERE rest < > ' ' )
2022-01-12 19:18:08 +01:00
SELECT id , display_name , scopes , display_color , created_at , disabled_at , previous_names , namechanged_at , scope
FROM split
2021-11-03 03:27:41 +01:00
WHERE scope < > ' '
ORDER BY created_at
) AS token WHERE token . scope = ? `
rows , err := _datastore . DB . Query ( query , moderatorScopeKey )
if err != nil {
log . Errorln ( err )
return nil
}
defer rows . Close ( )
users := getUsersFromRows ( rows )
return users
}
2021-07-20 04:22:29 +02:00
func getUsersFromRows ( rows * sql . Rows ) [ ] * User {
users := make ( [ ] * User , 0 )
for rows . Next ( ) {
var id string
var displayName string
var displayColor int
var createdAt time . Time
var disabledAt * time . Time
var previousUsernames string
var userNameChangedAt * time . Time
2021-11-03 03:27:41 +01:00
var scopesString * string
2021-07-20 04:22:29 +02:00
2021-11-03 03:27:41 +01:00
if err := rows . Scan ( & id , & displayName , & scopesString , & displayColor , & createdAt , & disabledAt , & previousUsernames , & userNameChangedAt ) ; err != nil {
2021-07-20 04:22:29 +02:00
log . Errorln ( "error creating collection of users from results" , err )
return nil
}
2021-11-03 03:27:41 +01:00
var scopes [ ] string
if scopesString != nil {
scopes = strings . Split ( * scopesString , "," )
}
2021-07-20 04:22:29 +02:00
user := & User {
2021-09-12 09:18:15 +02:00
ID : id ,
2021-07-20 04:22:29 +02:00
DisplayName : displayName ,
DisplayColor : displayColor ,
CreatedAt : createdAt ,
DisabledAt : disabledAt ,
PreviousNames : strings . Split ( previousUsernames , "," ) ,
NameChangedAt : userNameChangedAt ,
2021-11-03 03:27:41 +01:00
Scopes : scopes ,
2021-07-20 04:22:29 +02:00
}
users = append ( users , user )
}
sort . Slice ( users , func ( i , j int ) bool {
return users [ i ] . CreatedAt . Before ( users [ j ] . CreatedAt )
} )
return users
}
func getUserFromRow ( row * sql . Row ) * User {
var id string
var displayName string
var displayColor int
var createdAt time . Time
var disabledAt * time . Time
var previousUsernames string
var userNameChangedAt * time . Time
2021-11-03 03:27:41 +01:00
var scopesString * string
2021-07-20 04:22:29 +02:00
2021-11-03 03:27:41 +01:00
if err := row . Scan ( & id , & displayName , & displayColor , & createdAt , & disabledAt , & previousUsernames , & userNameChangedAt , & scopesString ) ; err != nil {
2021-07-20 04:22:29 +02:00
return nil
}
2021-11-03 03:27:41 +01:00
var scopes [ ] string
if scopesString != nil {
scopes = strings . Split ( * scopesString , "," )
}
2021-07-20 04:22:29 +02:00
return & User {
2021-09-12 09:18:15 +02:00
ID : id ,
2021-07-20 04:22:29 +02:00
DisplayName : displayName ,
DisplayColor : displayColor ,
CreatedAt : createdAt ,
DisabledAt : disabledAt ,
PreviousNames : strings . Split ( previousUsernames , "," ) ,
NameChangedAt : userNameChangedAt ,
2021-11-03 03:27:41 +01:00
Scopes : scopes ,
2021-07-20 04:22:29 +02:00
}
}