dkforest

A forum and chat platform (onion)
git clone https://git.dasho.dev/n0tr1v/dkforest.git
Log | Files | Refs | LICENSE

tableUsers.go (29901B)


      1 package database
      2 
      3 import (
      4 	"dkforest/pkg/config"
      5 	"errors"
      6 	"fmt"
      7 	"github.com/boombuler/barcode"
      8 	"github.com/boombuler/barcode/qr"
      9 	"image"
     10 	"strings"
     11 	"time"
     12 
     13 	"golang.org/x/crypto/bcrypt"
     14 
     15 	"dkforest/pkg/utils"
     16 	"github.com/asaskevich/govalidator"
     17 	"github.com/sirupsen/logrus"
     18 )
     19 
     20 type UserID int64
     21 
     22 func (u UserID) String() string {
     23 	return utils.FormatInt64(int64(u))
     24 }
     25 
     26 type Username string
     27 
     28 func (u Username) String() string {
     29 	return string(u)
     30 }
     31 
     32 func (u Username) AtStr() string {
     33 	return "@" + string(u)
     34 }
     35 
     36 // IUserRenderMessage is the smallest interface needed to render the chat messages
     37 type IUserRenderMessage interface {
     38 	GetID() UserID
     39 	GetUsername() Username
     40 	GetRefreshRate() int64
     41 	GetChatColor() string
     42 	GetIsIncognito() bool
     43 	GetIsHellbanned() bool
     44 	GetAFK() bool
     45 	GetAfkIndicatorEnabled() bool
     46 	GetDisplayIgnored() bool
     47 	GetDisplayAliveIndicator() bool
     48 	GetDisplayModerators() bool
     49 	GetNotifyNewMessage() bool
     50 	GetNotifyTagged() bool
     51 	GetNotifyPmmed() bool
     52 	GetChatReadMarkerEnabled() bool
     53 	GetHighlightOwnMessages() bool
     54 	GetDisplayDeleteButton() bool
     55 	GetIsAdmin() bool
     56 	GetDisplayHellbanButton() bool
     57 	GetDisplayKickButton() bool
     58 	GetDisplayHellbanned() bool
     59 	GetSyntaxHighlightCode() string
     60 	GetDateFormat() string
     61 	CanSeeHB() bool
     62 	GetConfirmExternalLinks() bool
     63 	GetCanSeeHellbanned() bool
     64 	IsModerator() bool
     65 	CountUIButtons() int64
     66 }
     67 
     68 // User struct an internal representation of a user for our app
     69 type User struct {
     70 	ID                           UserID
     71 	Avatar                       []byte
     72 	Username                     Username
     73 	GPGPublicKey                 string
     74 	AgePublicKey                 string
     75 	Password                     string          `json:"-"`
     76 	DuressPassword               string          `json:"-"`
     77 	TwoFactorSecret              EncryptedString `json:"-"`
     78 	TwoFactorRecovery            string          `json:"-"`
     79 	SecretPhrase                 EncryptedString `json:"-"`
     80 	GpgTwoFactorEnabled          bool
     81 	GpgTwoFactorMode             bool // false -> decrypt; true -> sign
     82 	CreatedAt                    time.Time
     83 	UpdatedAt                    time.Time
     84 	DeletedAt                    *time.Time
     85 	LastSeenAt                   time.Time
     86 	IsUnderDuress                bool
     87 	IsAdmin                      bool
     88 	CanSeeHellbanned             bool
     89 	IsHellbanned                 bool
     90 	IsIncognito                  bool
     91 	IsClubMember                 bool
     92 	DisplayHellbanned            bool
     93 	DisplayModerators            bool
     94 	DisplayKickButton            bool
     95 	DisplayHellbanButton         bool
     96 	DisplayDeleteButton          bool
     97 	PmMode                       int64 // Normal: 0, Whitelist 1
     98 	DisplayPms                   int64 // deprecated
     99 	HellbanOpacity               int64
    100 	CodeBlockHeight              int64
    101 	DisplayIgnored               bool
    102 	DisplayAliveIndicator        bool
    103 	HideIgnoredUsersFromList     bool
    104 	Verified                     bool
    105 	Temp                         bool // Temporary account
    106 	Token                        *string
    107 	Role                         string
    108 	ApiKey                       string
    109 	Lang                         string
    110 	ChatColor                    string
    111 	ChatBackgroundColor          string
    112 	ChatFont                     int64
    113 	ChatBold                     bool
    114 	ChatItalic                   bool
    115 	RefreshRate                  int64
    116 	LoginAttempts                int64
    117 	Karma                        int64
    118 	NotifyChessGames             bool
    119 	NotifyChessMove              bool
    120 	NotifyNewMessage             bool
    121 	NotifyTagged                 bool
    122 	NotifyPmmed                  bool
    123 	NotifyNewMessageSound        int64
    124 	NotifyTaggedSound            int64
    125 	NotifyPmmedSound             int64
    126 	Email                        string
    127 	Website                      string
    128 	ChatTutorial                 int64
    129 	ChatTutorialTime             time.Time
    130 	ChatReadMarkerEnabled        bool
    131 	ChatReadMarkerColor          string
    132 	ChatReadMarkerSize           int64
    133 	CanUploadFile                bool
    134 	CanUseForum                  bool
    135 	CanChangeUsername            bool
    136 	CanUseUppercase              bool
    137 	CanChangeColor               bool
    138 	CanUseMultiline              bool
    139 	ManualMultiline              bool
    140 	CanUseChessAnalyze           bool
    141 	Vetted                       bool
    142 	RegistrationDuration         int64
    143 	LastSeenPublic               bool
    144 	TerminateAllSessionsOnLogout bool
    145 	DateFormat                   int64
    146 	BlockNewUsersPm              bool
    147 	HideRightColumn              bool
    148 	ChatBarAtBottom              bool
    149 	AutocompleteCommandsEnabled  bool
    150 	SpellcheckEnabled            bool
    151 	AfkIndicatorEnabled          bool
    152 	SignupMetadata               string
    153 	CollectMetadata              bool
    154 	CaptchaRequired              bool
    155 	Theme                        int64
    156 	GeneralMessagesCount         int64
    157 	ChipsTest                    PokerChip
    158 	XmrBalance                   Piconero
    159 	AFK                          bool
    160 	UseStream                    bool
    161 	UseStreamMenu                bool
    162 	UseStreamTopBar              bool
    163 	SyntaxHighlightCode          string
    164 	ConfirmExternalLinks         bool
    165 	ChessSoundsEnabled           bool
    166 	PokerSoundsEnabled           bool
    167 	PokerXmrSubAddress           string
    168 	PokerReferredBy              *UserID
    169 	PokerReferralToken           *string
    170 	PokerRakeBack                PokerChip
    171 	HighlightOwnMessages         bool `gorm:"-"`
    172 }
    173 
    174 func (u *User) GetID() UserID                  { return u.ID }
    175 func (u *User) GetUsername() Username          { return u.Username }
    176 func (u *User) GetRefreshRate() int64          { return u.RefreshRate }
    177 func (u *User) GetChatColor() string           { return u.ChatColor }
    178 func (u *User) GetIsIncognito() bool           { return u.IsIncognito }
    179 func (u *User) GetIsHellbanned() bool          { return u.IsHellbanned }
    180 func (u *User) GetAFK() bool                   { return u.AFK }
    181 func (u *User) GetAfkIndicatorEnabled() bool   { return u.AfkIndicatorEnabled }
    182 func (u *User) GetDisplayIgnored() bool        { return u.DisplayIgnored }
    183 func (u *User) GetDisplayAliveIndicator() bool { return u.DisplayAliveIndicator }
    184 func (u *User) GetDisplayModerators() bool     { return u.DisplayModerators }
    185 func (u *User) GetNotifyNewMessage() bool      { return u.NotifyNewMessage }
    186 func (u *User) GetNotifyTagged() bool          { return u.NotifyTagged }
    187 func (u *User) GetNotifyPmmed() bool           { return u.NotifyPmmed }
    188 func (u *User) GetChatReadMarkerEnabled() bool { return u.ChatReadMarkerEnabled }
    189 func (u *User) GetHighlightOwnMessages() bool  { return u.HighlightOwnMessages }
    190 func (u *User) GetDisplayDeleteButton() bool   { return u.DisplayDeleteButton }
    191 func (u *User) GetIsAdmin() bool               { return u.IsAdmin }
    192 func (u *User) GetDisplayHellbanButton() bool  { return u.DisplayHellbanButton }
    193 func (u *User) GetDisplayKickButton() bool     { return u.DisplayKickButton }
    194 func (u *User) GetDisplayHellbanned() bool     { return u.DisplayHellbanned }
    195 func (u *User) GetSyntaxHighlightCode() string { return u.SyntaxHighlightCode }
    196 func (u *User) GetConfirmExternalLinks() bool  { return u.ConfirmExternalLinks }
    197 func (u *User) GetCanSeeHellbanned() bool      { return u.CanSeeHellbanned }
    198 
    199 const (
    200 	ThemeDefault   = 0
    201 	ThemeChristmas = 1
    202 )
    203 
    204 const (
    205 	PmModeStandard  = 0
    206 	PmModeWhitelist = 1
    207 )
    208 
    209 // UserPtrID given a User pointer, return the ID or nil
    210 func UserPtrID(user *User) *UserID {
    211 	if user != nil {
    212 		return &user.ID
    213 	}
    214 	return nil
    215 }
    216 
    217 func (d *DkfDB) GetOnlineChessSubscribers(userIDs []UserID) (out []User, err error) {
    218 	err = d.db.Find(&out, "notify_chess_games == 1 AND id IN (?)", userIDs).Error
    219 	return
    220 }
    221 
    222 func (d *DkfDB) GetChessSubscribers() (out []User, err error) {
    223 	err = d.db.Find(&out, "notify_chess_games == 1").Error
    224 	return
    225 }
    226 
    227 func (u *User) TutorialCompleted() bool {
    228 	return u.ChatTutorial == 3
    229 }
    230 
    231 func (u *User) GetFont() string {
    232 	switch u.ChatFont {
    233 	case 1:
    234 		return `'Courier New', Courier, monospace`
    235 	case 2:
    236 		return `Arial,Helvetica,sans-serif`
    237 	case 3:
    238 		return `Georgia,'Times New Roman',Times,serif`
    239 	case 4:
    240 		return `'Book Antiqua','MS Gothic',serif`
    241 	case 5:
    242 		return `'Comic Sans MS',Papyrus,sans-serif`
    243 	case 6:
    244 		return `Cursive,Papyrus,sans-serif`
    245 	case 7:
    246 		return `Fantasy,Futura,Papyrus,sans`
    247 	case 8:
    248 		return `Garamond,Palatino,serif`
    249 	case 9:
    250 		return `'MS Serif','New York',serif`
    251 	case 10:
    252 		return `System,Chicago,sans-serif`
    253 	case 11:
    254 		return `'Times New Roman',Times,serif`
    255 	case 12:
    256 		return `Verdana,Geneva,Arial,Helvetica,sans-serif`
    257 	default:
    258 		return ""
    259 	}
    260 }
    261 
    262 func (u *User) GetDateFormat() string {
    263 	switch u.DateFormat {
    264 	case 1:
    265 		return "15:04:05"
    266 	case 2:
    267 		return "01-02 03:04:05"
    268 	case 3:
    269 		return "03:04:05"
    270 	case 4:
    271 		return ""
    272 	default:
    273 		return "01-02 15:04:05"
    274 	}
    275 }
    276 
    277 func (u *User) AccountOldEnough() bool {
    278 	return time.Since(u.CreatedAt) > 3*24*time.Hour
    279 }
    280 
    281 func (u *User) CanUseForumFn() bool {
    282 	return u.CanUseForum && (u.AccountOldEnough() || u.Vetted)
    283 }
    284 
    285 func (u *User) CanUpload() bool {
    286 	return u.CanUploadFile && (u.AccountOldEnough() || u.Vetted)
    287 }
    288 
    289 func (u *User) CountUIButtons() int64 {
    290 	bools := []bool{u.DisplayDeleteButton}
    291 	if u.IsModerator() {
    292 		bools = append(bools, u.DisplayHellbanButton, u.DisplayKickButton)
    293 	}
    294 	return utils.CountBools(bools...)
    295 }
    296 
    297 func (u *User) generateBaseStyle() string {
    298 	sb := strings.Builder{}
    299 	sb.WriteString(`color: `)
    300 	sb.WriteString(u.ChatColor)
    301 	sb.WriteString(`; font-weight: `)
    302 	if u.ChatBold {
    303 		sb.WriteString(`bold`)
    304 	} else {
    305 		sb.WriteString(`normal`)
    306 	}
    307 	sb.WriteString(`; font-style: `)
    308 	if u.ChatItalic {
    309 		sb.WriteString(`italic`)
    310 	} else {
    311 		sb.WriteString(`normal`)
    312 	}
    313 	sb.WriteString(`;`)
    314 	font := u.GetFont()
    315 	if font != "" {
    316 		sb.WriteString(` font-family: `)
    317 		sb.WriteString(font)
    318 		sb.WriteString(`;`)
    319 	} else {
    320 		sb.WriteString(` font-family: Arial,Helvetica,sans-serif;`)
    321 	}
    322 	return sb.String()
    323 }
    324 
    325 func (u *User) GenerateChatStyle() string {
    326 	sb := strings.Builder{}
    327 	sb.WriteString(`style="`)
    328 	sb.WriteString(u.generateBaseStyle())
    329 	sb.WriteString(` font-size: 14px;`)
    330 	sb.WriteString(`"`)
    331 	return sb.String()
    332 }
    333 
    334 func (u *User) GenerateChatStyle1() string {
    335 	sb := strings.Builder{}
    336 	sb.WriteString(`style="`)
    337 	sb.WriteString(u.generateBaseStyle())
    338 	sb.WriteString(`"`)
    339 	return sb.String()
    340 }
    341 
    342 func (u *User) HasTotpEnabled() bool {
    343 	return string(u.TwoFactorSecret) != ""
    344 }
    345 
    346 func (u *User) IsModerator() bool {
    347 	return u.IsAdmin || u.Role == "moderator"
    348 }
    349 
    350 func (u *User) CanSeeHB() bool {
    351 	return u.CanSeeHellbanned || u.IsModerator()
    352 }
    353 
    354 func (u *User) GetHellbanOpacityF64() float64 {
    355 	return float64(u.HellbanOpacity) / 100
    356 }
    357 
    358 // Save user in the database
    359 func (u *User) Save(db *DkfDB) error {
    360 	return db.db.Save(u).Error
    361 }
    362 
    363 // DoSave user in the database, ignore error
    364 func (u *User) DoSave(db *DkfDB) {
    365 	if err := u.Save(db); err != nil {
    366 		logrus.Error(err)
    367 	}
    368 }
    369 
    370 func (u *User) DisableTotp2FA(db *DkfDB) {
    371 	db.db.Model(u).Select("TwoFactorSecret", "TwoFactorRecovery").Updates(User{TwoFactorSecret: "", TwoFactorRecovery: ""})
    372 }
    373 
    374 func (u *User) DisableGpg2FA(db *DkfDB) {
    375 	db.db.Model(u).Select("GpgTwoFactorEnabled").Updates(User{GpgTwoFactorEnabled: false})
    376 }
    377 
    378 func (u *User) SetAgePublicKey(db *DkfDB, agePublicKey string) {
    379 	db.db.Model(u).Select("AgePublicKey").Updates(User{AgePublicKey: agePublicKey})
    380 }
    381 
    382 func (u *User) SetApiKey(db *DkfDB, apiKey string) {
    383 	db.db.Model(u).Select("ApiKey").Updates(User{ApiKey: apiKey})
    384 }
    385 
    386 func (u *User) SetPokerReferralToken(db *DkfDB, pokerReferralToken *string) {
    387 	db.db.Model(u).Select("PokerReferralToken").Updates(User{PokerReferralToken: pokerReferralToken})
    388 }
    389 
    390 func (u *User) SetPokerReferredBy(db *DkfDB, pokerReferredBy *UserID) {
    391 	db.db.Model(u).Select("PokerReferredBy").Updates(User{PokerReferredBy: pokerReferredBy})
    392 }
    393 
    394 func (u *User) SetSignupMetadata(db *DkfDB, signupMetadata string) {
    395 	db.db.Model(u).Select("SignupMetadata").Updates(User{SignupMetadata: signupMetadata})
    396 }
    397 
    398 func (u *User) ToggleAutocompleteCommandsEnabled(db *DkfDB) {
    399 	db.db.Model(u).Select("AutocompleteCommandsEnabled").Updates(User{AutocompleteCommandsEnabled: !u.AutocompleteCommandsEnabled})
    400 }
    401 
    402 func (u *User) SetIsUnderDuress(db *DkfDB, isUnderDuress bool) {
    403 	db.db.Model(u).Select("IsUnderDuress").Updates(User{IsUnderDuress: isUnderDuress})
    404 }
    405 
    406 func (u *User) SetCaptchaRequired(db *DkfDB, captchaRequired bool) {
    407 	db.db.Model(u).Select("CaptchaRequired").Updates(User{CaptchaRequired: captchaRequired})
    408 }
    409 
    410 func (u *User) SetSyntaxHighlightCode(db *DkfDB, syntaxHighlightCode string) {
    411 	db.db.Model(u).Select("SyntaxHighlightCode").Updates(User{SyntaxHighlightCode: syntaxHighlightCode})
    412 }
    413 
    414 func (u *User) DecrGeneralMessagesCount(db *DkfDB) {
    415 	db.db.Model(u).Select("GeneralMessagesCount").Updates(User{GeneralMessagesCount: u.GeneralMessagesCount - 1})
    416 }
    417 
    418 func (u *User) SetVerified(db *DkfDB, verified bool) {
    419 	db.db.Model(u).Select("Verified").Updates(User{Verified: verified})
    420 }
    421 
    422 func (u *User) IncrChatTutorial(db *DkfDB) {
    423 	db.db.Model(u).Select("ChatTutorial").Updates(User{ChatTutorial: u.ChatTutorial + 1})
    424 }
    425 
    426 func (u *User) SetChatTutorialTime(db *DkfDB, chatTutorialTime time.Time) {
    427 	db.db.Model(u).Select("ChatTutorialTime").Updates(User{ChatTutorialTime: chatTutorialTime})
    428 }
    429 
    430 func (u *User) SetCanUseForum(db *DkfDB, canUseForum bool) {
    431 	db.db.Model(u).Select("CanUseForum").Updates(User{CanUseForum: canUseForum})
    432 }
    433 
    434 func (u *User) ResetLoginAttempts(db *DkfDB) {
    435 	db.db.Model(u).Select("LoginAttempts").Updates(User{LoginAttempts: 0})
    436 }
    437 
    438 func (u *User) IncrLoginAttempts(db *DkfDB) {
    439 	db.db.Model(u).Select("LoginAttempts").Updates(User{LoginAttempts: u.LoginAttempts + 1})
    440 }
    441 
    442 func (u *User) ResetChipsTest(db *DkfDB) {
    443 	db.db.Model(u).Select("ChipsTest").Updates(User{ChipsTest: 1000})
    444 }
    445 
    446 func (u *User) SetPokerXmrSubAddress(db *DkfDB, newPokerXmrSubAddress string) {
    447 	db.db.Model(u).Select("PokerXmrSubAddress").Updates(User{PokerXmrSubAddress: newPokerXmrSubAddress})
    448 }
    449 
    450 func (u *User) SetPmMode(db *DkfDB, pmMode int64) {
    451 	db.db.Model(u).Select("PmMode").Updates(User{PmMode: pmMode})
    452 }
    453 
    454 func (u *User) ResetTutorial(db *DkfDB) {
    455 	db.db.Model(u).Select("ChatTutorial").Updates(User{ChatTutorial: 0})
    456 }
    457 
    458 func (u *User) ToggleDisplayHellbanned(db *DkfDB) {
    459 	db.db.Model(u).Update("DisplayHellbanned", !u.DisplayHellbanned)
    460 }
    461 
    462 func (u *User) ToggleDisplayModerators(db *DkfDB) {
    463 	db.db.Model(u).Update("DisplayModerators", !u.DisplayModerators)
    464 }
    465 
    466 func (u *User) ToggleDisplayIgnored(db *DkfDB) {
    467 	db.db.Model(u).Update("DisplayIgnored", !u.DisplayIgnored)
    468 }
    469 
    470 func (u *User) ToggleAFK(db *DkfDB) {
    471 	db.db.Model(u).Update("AFK", !u.AFK)
    472 }
    473 
    474 func (u *User) HellBan(db *DkfDB) {
    475 	u.setHellBan(db, true)
    476 }
    477 
    478 func (u *User) UnHellBan(db *DkfDB) {
    479 	u.setHellBan(db, false)
    480 }
    481 
    482 func (u *User) setHellBan(db *DkfDB, hb bool) {
    483 	db.db.Model(u).Select("IsHellbanned", "DisplayHellbanned").Updates(User{IsHellbanned: hb, DisplayHellbanned: false})
    484 	if err := db.db.Model(&ChatMessage{}).Where("user_id = ?", u.ID).Update("is_hellbanned", hb).Error; err != nil {
    485 		logrus.Error(err)
    486 	}
    487 	MsgPubSub.Pub(RefreshTopic, ChatMessageType{Typ: ForceRefresh})
    488 }
    489 
    490 // GetUserBySessionKey ...
    491 func (d *DkfDB) GetUserBySessionKey(user *User, sessionKey string) error {
    492 	return d.db.
    493 		Joins("INNER JOIN sessions s ON s.user_id = users.id").
    494 		Where("s.token = ? AND users.verified = 1 AND s.deleted_at IS NULL AND s.expires_at > DATETIME('now', 'localtime')", sessionKey).
    495 		First(user).Error
    496 }
    497 
    498 // GetUserByApiKey ...
    499 func (d *DkfDB) GetUserByApiKey(user *User, apiKey string) error {
    500 	return d.db.First(user, "api_key = ?", apiKey).Error
    501 }
    502 
    503 // GetUserByID ...
    504 func (d *DkfDB) GetUserByID(userID UserID) (out User, err error) {
    505 	err = d.db.First(&out, "id = ?", userID).Error
    506 	return
    507 }
    508 
    509 func (d *DkfDB) GetUserRenderMessageByID(userID UserID) (out IUserRenderMessage, err error) {
    510 	var out1 User
    511 	err = d.db.Raw(`
    512 SELECT
    513 id,
    514 username,
    515 refresh_rate,
    516 chat_color,
    517 is_incognito,
    518 is_hellbanned,
    519 afk,
    520 afk_indicator_enabled,
    521 display_ignored,
    522 display_moderators,
    523 notify_new_message,
    524 notify_tagged,
    525 notify_pmmed,
    526 chat_read_marker_enabled,
    527 display_delete_button,
    528 is_admin,
    529 display_hellban_button,
    530 display_kick_button,
    531 display_hellbanned,
    532 display_alive_indicator,
    533 syntax_highlight_code,
    534 date_format,
    535 confirm_external_links,
    536 can_see_hellbanned,
    537 role
    538 FROM users WHERE id = ? LIMIT 1
    539 `, userID).Scan(&out1).Error
    540 	return &out1, err
    541 }
    542 
    543 func (d *DkfDB) GetUserByPokerReferralToken(token string) (out User, err error) {
    544 	err = d.db.First(&out, "poker_referral_token = ?", token).Error
    545 	return
    546 }
    547 
    548 func (d *DkfDB) GetUserByPokerXmrSubAddress(pokerXmrSubAddress string) (out User, err error) {
    549 	err = d.db.First(&out, "poker_xmr_sub_address = ?", pokerXmrSubAddress).Error
    550 	return
    551 }
    552 
    553 // GetUserByUsername ...
    554 func (d *DkfDB) GetUserByUsername(username Username) (out User, err error) {
    555 	err = d.db.First(&out, "username = ? COLLATE NOCASE", username).Error
    556 	return
    557 }
    558 
    559 func (d *DkfDB) GetUserIDByUsername(username Username) (out UserID, err error) {
    560 	var tmp struct{ ID UserID }
    561 	err = d.db.Table("users").Select("id").First(&tmp, "username = ? COLLATE NOCASE", username).Error
    562 	return tmp.ID, err
    563 }
    564 
    565 func (d *DkfDB) GetVerifiedUserByUsername(username Username) (out User, err error) {
    566 	err = d.db.First(&out, "username = ? COLLATE NOCASE AND verified = 1", username).Error
    567 	return
    568 }
    569 
    570 func (d *DkfDB) GetUsersByID(ids []UserID) (out []User, err error) {
    571 	err = d.db.Find(&out, "id IN (?)", ids).Error
    572 	return
    573 }
    574 
    575 func (d *DkfDB) GetUsersByUsername(usernames []string) (out []User, err error) {
    576 	err = d.db.Find(&out, "username IN (?)", usernames).Error
    577 	return
    578 }
    579 
    580 func (d *DkfDB) DeleteUserByID(userID UserID) (err error) {
    581 	err = d.db.Unscoped().Delete(User{}, "id = ?", userID).Error
    582 	return
    583 }
    584 
    585 func (d *DkfDB) GetModeratorsUsers() (out []User, err error) {
    586 	err = d.db.Order("username ASC").Find(&out, "role = ? OR is_admin = 1", "moderator").Error
    587 	return
    588 }
    589 
    590 func (d *DkfDB) GetClubMembers() (out []User, err error) {
    591 	err = d.db.Find(&out, "is_club_member = ?", true).Error
    592 	return
    593 }
    594 
    595 // ChangePassword change user's password. Save the user, and delete all active sessions.
    596 // NOTE: When changing the password, it is important to delete any active sessions.
    597 // Assume I realize I left myself logged into a shared computer.
    598 // I change my password to protect myself.
    599 // The session on the public computer needs to be invalidated.
    600 func (u *User) ChangePassword(db *DkfDB, hashedPassword string) error {
    601 	u.Password = hashedPassword
    602 	if err := u.Save(db); err != nil {
    603 		return err
    604 	}
    605 	// Delete active user sessions
    606 	if err := db.DeleteUserSessions(u.ID); err != nil {
    607 		return err
    608 	}
    609 	return nil
    610 }
    611 
    612 func (u *User) ChangeDuressPassword(db *DkfDB, hashedDuressPassword string) error {
    613 	u.DuressPassword = hashedDuressPassword
    614 	if err := u.Save(db); err != nil {
    615 		return err
    616 	}
    617 	// Delete active user sessions
    618 	if err := db.DeleteUserSessions(u.ID); err != nil {
    619 		return err
    620 	}
    621 	return nil
    622 }
    623 
    624 func (u *User) CheckPassword(db *DkfDB, password string) bool {
    625 	if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password)); err != nil {
    626 		if err := bcrypt.CompareHashAndPassword([]byte(u.DuressPassword), []byte(password)); err != nil {
    627 			return false
    628 		}
    629 		u.SetIsUnderDuress(db, true)
    630 	} else {
    631 		u.SetIsUnderDuress(db, false)
    632 	}
    633 	return true
    634 }
    635 
    636 // UserErrors ...
    637 type UserErrors struct {
    638 	Username     string
    639 	Password     string
    640 	GPGPublicKey string
    641 }
    642 
    643 // HasError ...
    644 func (e UserErrors) HasError() bool {
    645 	return e.Username != "" || e.Password != "" || e.GPGPublicKey != ""
    646 }
    647 
    648 var ErrForbiddenUsername = errors.New("forbidden username")
    649 
    650 // ValidateUsername ...
    651 func ValidateUsername(username string, isFirstUser bool) (bool, error) {
    652 	if !govalidator.IsPrintableASCII(username) {
    653 		return false, errors.New("username must be ascii printable only")
    654 	}
    655 	lowerUsername := strings.ToLower(username)
    656 	if !isFirstUser {
    657 		if govalidator.Matches(lowerUsername, "n[o|0]tr[1|i|l][v|y]") ||
    658 			strings.Contains(lowerUsername, "admin") {
    659 			return false, ErrForbiddenUsername
    660 		}
    661 	}
    662 	if strings.Contains(lowerUsername, "pedo") ||
    663 		strings.Contains(lowerUsername, "fuck") ||
    664 		strings.Contains(lowerUsername, "nigger") ||
    665 		strings.Contains(lowerUsername, "nigga") {
    666 		return false, ErrForbiddenUsername
    667 	}
    668 	if !govalidator.Matches(username, "^[a-zA-Z0-9_]+$") {
    669 		return false, errors.New("username must match [a-zA-Z0-9_]+")
    670 	}
    671 	if !govalidator.StringLength(username, "3", "20") {
    672 		return false, errors.New("username must have between 3 and 20 characters")
    673 	}
    674 	return true, nil
    675 }
    676 
    677 func isUsernameReserved(username Username) bool {
    678 	return false
    679 }
    680 
    681 // GetVerifiedUserBySessionID ...
    682 func (d *DkfDB) GetVerifiedUserBySessionID(token string) (out User, err error) {
    683 	err = d.db.First(&out, "token = ? and verified = 1", token).Error
    684 	return
    685 }
    686 
    687 // GetRecentUsersCount ...
    688 func (d *DkfDB) GetRecentUsersCount() int64 {
    689 	var count int64
    690 	d.db.Table("users").Where("created_at > datetime('now', '-1 Minute', 'localtime')").Count(&count)
    691 	return count
    692 }
    693 
    694 // IsUsernameAlreadyTaken ...
    695 func (d *DkfDB) IsUsernameAlreadyTaken(username Username) bool {
    696 	var count int64
    697 	d.db.Table("users").Where("username = ? COLLATE NOCASE", username).Count(&count)
    698 	return count > 0 || isUsernameReserved(username)
    699 }
    700 
    701 // PasswordValidator ...
    702 type PasswordValidator struct {
    703 	password string
    704 	error    error
    705 }
    706 
    707 // NewPasswordValidator ...
    708 func NewPasswordValidator(db *DkfDB, password string) *PasswordValidator {
    709 	p := new(PasswordValidator)
    710 	p.password = password
    711 	if len(password) < 8 {
    712 		p.error = errors.New("password must be at least 8 characters")
    713 	}
    714 	if len(password) > 128 {
    715 		p.error = errors.New("password must be at most 128 characters")
    716 	}
    717 	if db.IsPasswordProhibited(password) {
    718 		p.error = errors.New("this password is too weak")
    719 	}
    720 	return p
    721 }
    722 
    723 // CompareWith ...
    724 func (p *PasswordValidator) CompareWith(repassword string) *PasswordValidator {
    725 	if p.password != repassword {
    726 		p.error = errors.New("passwords are not equal")
    727 	}
    728 	return p
    729 }
    730 
    731 // Hash ...
    732 func (p *PasswordValidator) Hash() (string, error) {
    733 	h := []byte("")
    734 	var err error
    735 	if p.error == nil {
    736 		h, err = bcrypt.GenerateFromPassword([]byte(p.password), 12)
    737 		if err != nil {
    738 			p.error = errors.New("unable to hash password: " + err.Error())
    739 		}
    740 	}
    741 	return string(h), p.error
    742 }
    743 
    744 func (d *DkfDB) CanUseUsername(username Username, isFirstUser bool) error {
    745 	if _, err := ValidateUsername(string(username), isFirstUser); err != nil {
    746 		return err
    747 	} else if d.IsUsernameAlreadyTaken(username) {
    748 		return errors.New("username already taken")
    749 	}
    750 	return nil
    751 }
    752 
    753 func (d *DkfDB) CanRenameTo(oldUsername, newUsername Username) error {
    754 	if _, err := ValidateUsername(string(newUsername), false); err != nil {
    755 		return err
    756 	}
    757 	if strings.ToLower(string(oldUsername)) != strings.ToLower(string(newUsername)) {
    758 		if d.IsUsernameAlreadyTaken(newUsername) {
    759 			return errors.New("username already taken")
    760 		}
    761 	}
    762 	return nil
    763 }
    764 
    765 // CreateUser ...
    766 func (d *DkfDB) CreateUser(username, password, repassword string, registrationDuration int64, signupInfoEnc string) (User, UserErrors) {
    767 	return d.createUser(username, password, repassword, "", false, true, false, false, false, registrationDuration, signupInfoEnc)
    768 }
    769 
    770 func (d *DkfDB) CreateGuestUser(username, password string) (User, UserErrors) {
    771 	return d.createUser(username, password, password, "", false, true, true, false, false, 0, "signupInfoEnc")
    772 }
    773 
    774 func (d *DkfDB) CreateFirstUser(username, password, repassword string) (User, UserErrors) {
    775 	return d.createUser(username, password, repassword, "", true, true, false, true, false, 12000, "")
    776 }
    777 
    778 func (d *DkfDB) CreateZeroUser() (User, UserErrors) {
    779 	password := utils.GenerateToken10()
    780 	return d.createUser(config.NullUsername, password, password, config.NullUserPublicKey, false, true, false, false, true, 12000, "")
    781 }
    782 
    783 // skipUsernameValidation: entirely skip username validation (for "0" user)
    784 // isFirstUser: less strict username validation; can use "admin"/"n0tr1v" usernames
    785 func (d *DkfDB) createUser(usernameStr, password, repassword, gpgPublicKey string, isAdmin, verified, isGuestAcc, isFirstUser, skipUsernameValidation bool, registrationDuration int64, signupInfoEnc string) (User, UserErrors) {
    786 	username := Username(strings.TrimSpace(usernameStr))
    787 	var errs UserErrors
    788 	if !skipUsernameValidation {
    789 		if err := d.CanUseUsername(username, isFirstUser); err != nil {
    790 			errs.Username = err.Error()
    791 		}
    792 	}
    793 	hashedPassword, err := NewPasswordValidator(d, password).CompareWith(repassword).Hash()
    794 	if err != nil {
    795 		errs.Password = err.Error()
    796 	}
    797 	var newUser User
    798 	if !errs.HasError() {
    799 		newUser.Temp = isGuestAcc
    800 		newUser.Role = "member"
    801 		newUser.Username = username
    802 		newUser.Password = hashedPassword
    803 		newUser.GPGPublicKey = gpgPublicKey
    804 		newUser.IsAdmin = isAdmin
    805 		newUser.Verified = verified
    806 		newUser.ChatColor = utils.GetRandomChatColor()
    807 		newUser.RefreshRate = 5
    808 		newUser.ChatReadMarkerEnabled = true
    809 		newUser.ChatReadMarkerColor = "#4e7597"
    810 		newUser.ChatBackgroundColor = "#111111"
    811 		newUser.ChatReadMarkerSize = 1
    812 		newUser.DisplayIgnored = false
    813 		newUser.DisplayPms = 0
    814 		newUser.CanUseForum = true
    815 		newUser.CanUseMultiline = false
    816 		newUser.CanUseChessAnalyze = false
    817 		newUser.CanChangeUsername = true
    818 		newUser.CanUseUppercase = true
    819 		newUser.CanUploadFile = true
    820 		newUser.CanChangeColor = true
    821 		newUser.DisplayDeleteButton = true
    822 		newUser.DisplayHellbanButton = true
    823 		newUser.DisplayModerators = true
    824 		newUser.DisplayHellbanned = false
    825 		newUser.LastSeenPublic = true
    826 		newUser.CollectMetadata = false
    827 		newUser.RegistrationDuration = registrationDuration
    828 		newUser.UseStream = true
    829 		newUser.UseStreamMenu = true
    830 		newUser.UseStreamTopBar = true
    831 		newUser.DisplayAliveIndicator = true
    832 		newUser.CodeBlockHeight = 300
    833 		newUser.HellbanOpacity = 30
    834 		newUser.SignupMetadata = signupInfoEnc
    835 		_, month, _ := time.Now().UTC().Date()
    836 		if month == time.December {
    837 			newUser.Theme = ThemeChristmas
    838 		}
    839 		if !verified {
    840 			token := utils.GenerateToken32()
    841 			newUser.Token = &token
    842 		}
    843 		if err := d.db.Create(&newUser).Error; err != nil {
    844 			logrus.Error(err)
    845 		}
    846 
    847 		return newUser, errs
    848 	}
    849 	return newUser, errs
    850 }
    851 
    852 func (u *User) SetAvatar(b []byte) {
    853 	u.Avatar = b
    854 }
    855 
    856 func (u *User) IncrKarma(db *DkfDB, karma int64, description string) {
    857 	if _, err := db.CreateKarmaHistory(karma, description, u.ID, nil); err != nil {
    858 		logrus.Error(err)
    859 		return
    860 	}
    861 	u.Karma += karma
    862 }
    863 
    864 // CanSendPM you get your first karma point after sending 20 public messages
    865 func (u *User) CanSendPM() bool {
    866 	if u.IsModerator() || u.Vetted {
    867 		return true
    868 	}
    869 	return u.GeneralMessagesCount >= 20
    870 }
    871 
    872 func (u *User) GetURL() string {
    873 	return fmt.Sprintf("monero:%s", u.PokerXmrSubAddress)
    874 }
    875 
    876 func (u *User) GetImage() (image.Image, error) {
    877 	b, err := qr.Encode(u.GetURL(), qr.L, qr.Auto)
    878 	if err != nil {
    879 		return nil, err
    880 	}
    881 	b, err = barcode.Scale(b, 150, 150)
    882 	if err != nil {
    883 		return nil, err
    884 	}
    885 	return b, nil
    886 }
    887 
    888 func (d *DkfDB) GetUserBalances(userID UserID) (xmrBalance Piconero, chipsTest PokerChip, err error) {
    889 	var tmp struct {
    890 		XmrBalance Piconero
    891 		ChipsTest  PokerChip
    892 	}
    893 	err = d.db.Table("users").Select("xmr_balance, chips_test").First(&tmp, "id = ?", userID).Error
    894 	return tmp.XmrBalance, tmp.ChipsTest, err
    895 }
    896 
    897 func (d *DkfDB) DecrUserBalance(userID UserID, isTest bool, amount PokerChip) (err error) {
    898 	if isTest {
    899 		err = d.db.Exec(`UPDATE users SET chips_test = chips_test - ? WHERE id = ?`, amount, userID).Error
    900 	} else {
    901 		err = d.db.Exec(`UPDATE users SET xmr_balance = xmr_balance - ? WHERE id = ?`, amount.ToPiconero(), userID).Error
    902 	}
    903 	return
    904 }
    905 
    906 func (d *DkfDB) IncrUserBalance(userID UserID, isTest bool, amount PokerChip) (err error) {
    907 	if isTest {
    908 		err = d.db.Exec(`UPDATE users SET chips_test = chips_test + ? WHERE id = ?`, amount, userID).Error
    909 	} else {
    910 		err = d.db.Exec(`UPDATE users SET xmr_balance = xmr_balance + ? WHERE id = ?`, amount.ToPiconero(), userID).Error
    911 	}
    912 	return
    913 }
    914 
    915 func (u *User) GetXmrBalance(db *DkfDB) (amount Piconero, err error) {
    916 	var tmp struct{ XmrBalance Piconero }
    917 	err = db.db.Table("users").Select("xmr_balance").First(&tmp, "id = ?", u.ID).Error
    918 	return tmp.XmrBalance, err
    919 }
    920 
    921 func (u *User) IncrXmrBalance(db *DkfDB, amount Piconero) (err error) {
    922 	err = db.db.Exec(`UPDATE users SET xmr_balance = xmr_balance + ? WHERE id = ?`, amount, u.ID).Error
    923 	return
    924 }
    925 
    926 func (u *User) SubXmrBalance(db *DkfDB, amount Piconero) (err error) {
    927 	err = db.db.Exec(`UPDATE users SET xmr_balance = xmr_balance - ? WHERE id = ?`, amount, u.ID).Error
    928 	return
    929 }
    930 
    931 func (d *DkfDB) GetUsersXmrBalance() (out Piconero, err error) {
    932 	var tmp struct{ SumXmrBalance Piconero }
    933 	err = d.db.Raw(`SELECT SUM(xmr_balance) as sum_xmr_balance FROM users`).Scan(&tmp).Error
    934 	return tmp.SumXmrBalance, err
    935 }
    936 
    937 func (d *DkfDB) SetPokerSubAddress(userID UserID, subAddress string) (err error) {
    938 	err = d.db.Exec(`UPDATE users SET poker_xmr_sub_address = ? WHERE id = ?`, subAddress, userID).Error
    939 	return
    940 }
    941 
    942 func (u *User) GetUserChips(isTest bool) PokerChip {
    943 	return utils.Ternary(isTest, u.ChipsTest, u.XmrBalance.ToPokerChip())
    944 }
    945 
    946 func (d *DkfDB) GetUsersRakeBack() (out PokerChip, err error) {
    947 	var tmp struct{ PokerRakeBack PokerChip }
    948 	err = d.db.Raw(`SELECT SUM(poker_rake_back) as poker_rake_back FROM users`).Scan(&tmp).Error
    949 	return tmp.PokerRakeBack, err
    950 }
    951 
    952 func (d *DkfDB) ClaimRakeBack(userID UserID) (err error) {
    953 	err = d.db.Exec(`UPDATE users SET xmr_balance = xmr_balance + (poker_rake_back * 10000000), poker_rake_back = 0 WHERE id = ?`, int64(userID)).Error
    954 	return
    955 }
    956 
    957 func (d *DkfDB) IncrUserRakeBack(referredBy UserID, rakeBack PokerChip) (err error) {
    958 	err = d.db.Exec(`UPDATE users SET poker_rake_back = poker_rake_back + ? WHERE id = ?`, uint64(rakeBack), int64(referredBy)).Error
    959 	return
    960 }
    961 
    962 func (d *DkfDB) GetRakeBackReferredCount(userID UserID) (count int64, err error) {
    963 	var tmp struct{ Count int64 }
    964 	err = d.db.Raw(`SELECT COUNT(id) AS count FROM users WHERE poker_referred_by = ?`, int64(userID)).Scan(&tmp).Error
    965 	return tmp.Count, err
    966 }