user.go 4.2 KB

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