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 }