github.com/grafviktor/keep-my-secret@v0.9.10-0.20230908165355-19f35cce90e5/internal/model/user.go (about) 1 package model 2 3 import ( 4 "errors" 5 6 "golang.org/x/crypto/bcrypt" 7 8 "github.com/grafviktor/keep-my-secret/internal/api/utils" 9 ) 10 11 func hashString(s string) (string, error) { 12 bytes, err := bcrypt.GenerateFromPassword([]byte(s), bcrypt.DefaultCost) 13 14 return string(bytes), err 15 } 16 17 type User struct { 18 ID int64 `json:"id"` 19 Login string `json:"login,omitempty"` 20 HashedPassword string `json:"-"` 21 // RestorePassword was not implemented and not used anywhere 22 RestorePassword string `json:"-"` 23 DataKey string `json:"-"` 24 } 25 26 // NewUser creates a new New User model with a random data key. The key should never be given to a user. 27 // They will be automatically restored from the database when user logs in. 28 func NewUser(login, password string) (*User, error) { 29 hashedPassword, err := hashString(password) 30 if err != nil { 31 return nil, err 32 } 33 34 // When we create a new user, we generate a new random password 35 // this password is used to encrypt user's data internally. For security 36 // reasons, the user never knows his own 'data' password. 37 // 'data' password is stored in the database and encrypted by the user's 38 // original password. When user logs in, we decrypt 'data' password and save 39 // it into RAM. This process is transparent for the users. 40 key := utils.GenerateRandomPassword() 41 encryptedKey, err := utils.Encrypt([]byte(key), password) 42 if err != nil { 43 return nil, err 44 } 45 46 u := User{ 47 Login: login, 48 HashedPassword: hashedPassword, 49 RestorePassword: hashedPassword, 50 DataKey: string(encryptedKey), 51 } 52 53 return &u, nil 54 } 55 56 // PasswordMatches check if password which was provided by the user during login process is correct 57 func (u *User) PasswordMatches(plainText string) (bool, error) { 58 err := bcrypt.CompareHashAndPassword([]byte(u.HashedPassword), []byte(plainText)) 59 if err != nil { 60 switch { 61 case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword): 62 // invalid password 63 return false, nil 64 default: 65 return false, err 66 } 67 } 68 69 return true, nil 70 } 71 72 func (u *User) GetDataKey(password string) (string, error) { 73 if len(password) == 0 { 74 return "", errors.New("cannot decrypt data key - no password set") 75 } 76 77 key, err := utils.Decrypt([]byte(u.DataKey), password) 78 if err != nil { 79 return "", err 80 } 81 82 return string(key), nil 83 }