package models import ( "errors" "fmt" "strings" "git.kirsle.net/apps/gophertype/pkg/console" "git.kirsle.net/apps/gophertype/pkg/constants" "golang.org/x/crypto/bcrypt" ) type userMan struct{} // Users is a singleton helper to deal with user models. var Users = userMan{} // User account for the site. type User struct { BaseModel Email string `gorm:"unique_index"` Name string HashedPassword string `json:"-"` IsAdmin bool `gorm:"index"` // Relationships Posts []Post `gorm:"foreignkey:AuthorID"` } // Validate the User object has everything filled in. Fixes what it can, // returns an error if something is wrong. Ensures the HashedPassword is hashed. func (u *User) Validate() error { u.Email = strings.TrimSpace(strings.ToLower(u.Email)) u.Name = strings.TrimSpace(u.Name) if len(u.Email) == 0 { return errors.New("Email is required") } return nil } // AuthenticateUser checks a login for an email and password. func (m userMan) AuthenticateUser(email string, password string) (User, error) { user, err := m.GetUserByEmail(email) if err != nil { console.Error("AuthenticateUser: email %s not found: %s", email, err) return User{}, errors.New("incorrect email or password") } if user.VerifyPassword(password) { return user, nil } return User{}, errors.New("incorrect email or password") } // GetUserByID looks up a user by their ID. func (m userMan) GetUserByID(id int) (User, error) { var user User r := DB.First(&user, id) return user, r.Error } // GetUserByEmail looks up a user by their email address. func (m userMan) GetUserByEmail(email string) (User, error) { var user User r := DB.Where("email = ?", strings.ToLower(email)).First(&user) return user, r.Error } // ListAdminEmails returns the array of email addresses of all admin users. func (m userMan) ListAdminEmails() ([]string, error) { var ( users []User emails []string ) r := DB.Where("is_admin=true AND email IS NOT NULL").Find(&users) for _, user := range users { if len(user.Email) > 0 { emails = append(emails, user.Email) } } return emails, r.Error } // SetPassword stores the hashed password for a user. func (u *User) SetPassword(password string) error { hash, err := bcrypt.GenerateFromPassword([]byte(password), constants.BcryptCost) if err != nil { return fmt.Errorf("SetPassword: %s", err) } u.HashedPassword = string(hash) return nil } // VerifyPassword checks if the password matches the user's hashed password. func (u *User) VerifyPassword(password string) bool { if u.HashedPassword == "" { console.Error("ERROR: VerifyPassword: user %s has no HashedPassword", u.Email) return false } err := bcrypt.CompareHashAndPassword([]byte(u.HashedPassword), []byte(password)) if err == nil { return true } console.Error("VerifyPassword: %s", err) return false } // FirstAdmin returns the admin user with the lowest ID number. func (m userMan) FirstAdmin() (User, error) { var user User r := DB.First(&user, "is_admin = ?", true) return user, r.Error } // CreateUser adds a new user to the database. func (m userMan) CreateUser(u User) error { if err := u.Validate(); err != nil { return err } r := DB.Create(&u) return r.Error }