4
0

user.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package state
  2. import (
  3. "bytes"
  4. "errors"
  5. "strings"
  6. "github.com/google/uuid"
  7. "github.com/mk6i/retro-aim-server/wire"
  8. )
  9. // BlockedState represents the blocked status between two users
  10. type BlockedState int
  11. var (
  12. // ErrDupUser indicates that a user already exists.
  13. ErrDupUser = errors.New("user already exists")
  14. // ErrNoUser indicates that a user does not exist.
  15. ErrNoUser = errors.New("user does not exist")
  16. )
  17. const (
  18. // BlockedNo indicates that neither user blocks the other.
  19. BlockedNo BlockedState = iota
  20. // BlockedA indicates that user A blocks user B.
  21. BlockedA
  22. // BlockedB indicates that user B blocks user A.
  23. BlockedB
  24. )
  25. // IdentScreenName struct stores the normalized version of a user's screen name.
  26. // This format is used for uniformity in storage and comparison by removing spaces
  27. // and converting all characters to lowercase.
  28. type IdentScreenName struct {
  29. // screenName contains the identifier screen name value. Do not assign this
  30. // value directly. Rather, set it through NewIdentScreenName. This ensures
  31. // that when an instance of IdentScreenName is present, it's guaranteed to
  32. // have a normalized value.
  33. screenName string
  34. }
  35. // String returns the string representation of the IdentScreenName.
  36. func (i IdentScreenName) String() string {
  37. return i.screenName
  38. }
  39. // NewIdentScreenName creates a new IdentScreenName.
  40. func NewIdentScreenName(screenName string) IdentScreenName {
  41. str := strings.ReplaceAll(screenName, " ", "")
  42. str = strings.ToLower(str)
  43. return IdentScreenName{screenName: str}
  44. }
  45. // DisplayScreenName type represents the screen name in the user-defined format.
  46. // This includes the original casing and spacing as defined by the user.
  47. type DisplayScreenName string
  48. // IdentScreenName converts the DisplayScreenName to an IdentScreenName by applying
  49. // the normalization process defined in NewIdentScreenName.
  50. func (s DisplayScreenName) IdentScreenName() IdentScreenName {
  51. return NewIdentScreenName(string(s))
  52. }
  53. // String returns the original display string of the screen name, preserving the user-defined
  54. // casing and spaces.
  55. func (s DisplayScreenName) String() string {
  56. return string(s)
  57. }
  58. // NewStubUser creates a new user with canned credentials. The default password
  59. // is "welcome1". This is typically used for development purposes.
  60. func NewStubUser(screenName DisplayScreenName) (User, error) {
  61. uid, err := uuid.NewRandom()
  62. if err != nil {
  63. return User{}, err
  64. }
  65. u := User{
  66. IdentScreenName: NewIdentScreenName(string(screenName)),
  67. DisplayScreenName: screenName,
  68. AuthKey: uid.String(),
  69. }
  70. err = u.HashPassword("welcome1")
  71. return u, err
  72. }
  73. // User represents a user account.
  74. type User struct {
  75. // IdentScreenName is the AIM screen name.
  76. IdentScreenName IdentScreenName `json:"-"`
  77. // DisplayScreenName is the formatted screen name.
  78. DisplayScreenName DisplayScreenName `json:"screen_name"`
  79. // AuthKey is the salt for the MD5 password hash.
  80. AuthKey string `json:"-"`
  81. // StrongMD5Pass is the MD5 password hash format used by AIM v4.8-v5.9.
  82. StrongMD5Pass []byte `json:"-"`
  83. // WeakMD5Pass is the MD5 password hash format used by AIM v3.5-v4.7. This
  84. // hash is used to authenticate roasted passwords for AIM v1.0-v3.0.
  85. WeakMD5Pass []byte `json:"-"`
  86. }
  87. // ValidateHash checks if md5Hash is identical to one of the password hashes.
  88. func (u *User) ValidateHash(md5Hash []byte) bool {
  89. return bytes.Equal(u.StrongMD5Pass, md5Hash) || bytes.Equal(u.WeakMD5Pass, md5Hash)
  90. }
  91. // ValidateRoastedPass checks if the provided roasted password matches the MD5
  92. // hash of the user's actual password. A roasted password is a XOR-obfuscated
  93. // form of the real password, intended to add a simple layer of security.
  94. func (u *User) ValidateRoastedPass(roastedPass []byte) bool {
  95. clearPass := wire.RoastPassword(roastedPass)
  96. md5Hash := wire.WeakMD5PasswordHash(string(clearPass), u.AuthKey) // todo remove string conversion
  97. return bytes.Equal(u.WeakMD5Pass, md5Hash)
  98. }
  99. // HashPassword computes MD5 hashes of the user's password. It computes both
  100. // weak and strong variants and stores them in the struct.
  101. func (u *User) HashPassword(passwd string) error {
  102. u.WeakMD5Pass = wire.WeakMD5PasswordHash(passwd, u.AuthKey)
  103. u.StrongMD5Pass = wire.StrongMD5PasswordHash(passwd, u.AuthKey)
  104. return nil
  105. }