2019-11-15 03:03:56 +00:00
|
|
|
package models
|
|
|
|
|
2019-11-15 04:58:55 +00:00
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2019-11-26 19:17:01 +00:00
|
|
|
"git.kirsle.net/apps/gophertype/pkg/console"
|
2019-11-15 04:58:55 +00:00
|
|
|
"git.kirsle.net/apps/gophertype/pkg/constants"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
)
|
|
|
|
|
2020-02-18 02:10:35 +00:00
|
|
|
type userMan struct{}
|
|
|
|
|
|
|
|
// Users is a singleton helper to deal with user models.
|
|
|
|
var Users = userMan{}
|
|
|
|
|
2019-11-15 03:03:56 +00:00
|
|
|
// User account for the site.
|
|
|
|
type User struct {
|
2020-02-18 02:10:35 +00:00
|
|
|
BaseModel
|
|
|
|
|
2019-11-27 00:54:02 +00:00
|
|
|
Email string `gorm:"unique_index"`
|
|
|
|
Name string
|
2019-11-15 04:58:55 +00:00
|
|
|
HashedPassword string `json:"-"`
|
2019-11-27 00:54:02 +00:00
|
|
|
IsAdmin bool `gorm:"index"`
|
|
|
|
|
|
|
|
// Relationships
|
|
|
|
Posts []Post `gorm:"foreignkey:AuthorID"`
|
2019-11-15 04:58:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2019-11-26 03:55:28 +00:00
|
|
|
u.Email = strings.TrimSpace(strings.ToLower(u.Email))
|
|
|
|
u.Name = strings.TrimSpace(u.Name)
|
2019-11-15 04:58:55 +00:00
|
|
|
|
2019-11-26 03:55:28 +00:00
|
|
|
if len(u.Email) == 0 {
|
|
|
|
return errors.New("Email is required")
|
2019-11-15 04:58:55 +00:00
|
|
|
}
|
2019-11-26 03:55:28 +00:00
|
|
|
return nil
|
|
|
|
}
|
2019-11-15 04:58:55 +00:00
|
|
|
|
2019-11-26 03:55:28 +00:00
|
|
|
// AuthenticateUser checks a login for an email and password.
|
2020-02-18 02:10:35 +00:00
|
|
|
func (m userMan) AuthenticateUser(email string, password string) (User, error) {
|
|
|
|
user, err := m.GetUserByEmail(email)
|
2019-11-26 03:55:28 +00:00
|
|
|
if err != nil {
|
2019-11-26 19:17:01 +00:00
|
|
|
console.Error("AuthenticateUser: email %s not found: %s", email, err)
|
2019-11-26 03:55:28 +00:00
|
|
|
return User{}, errors.New("incorrect email or password")
|
2019-11-15 04:58:55 +00:00
|
|
|
}
|
2019-11-26 03:55:28 +00:00
|
|
|
|
|
|
|
if user.VerifyPassword(password) {
|
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return User{}, errors.New("incorrect email or password")
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserByID looks up a user by their ID.
|
2020-02-18 02:10:35 +00:00
|
|
|
func (m userMan) GetUserByID(id int) (User, error) {
|
2019-11-26 03:55:28 +00:00
|
|
|
var user User
|
|
|
|
r := DB.First(&user, id)
|
|
|
|
return user, r.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetUserByEmail looks up a user by their email address.
|
2020-02-18 02:10:35 +00:00
|
|
|
func (m userMan) GetUserByEmail(email string) (User, error) {
|
2019-11-26 03:55:28 +00:00
|
|
|
var user User
|
|
|
|
r := DB.Where("email = ?", strings.ToLower(email)).First(&user)
|
|
|
|
return user, r.Error
|
2019-11-15 04:58:55 +00:00
|
|
|
}
|
|
|
|
|
2020-02-14 06:37:23 +00:00
|
|
|
// ListAdminEmails returns the array of email addresses of all admin users.
|
2020-02-18 02:10:35 +00:00
|
|
|
func (m userMan) ListAdminEmails() ([]string, error) {
|
2020-02-14 06:37:23 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-11-15 04:58:55 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-11-26 03:55:28 +00:00
|
|
|
// VerifyPassword checks if the password matches the user's hashed password.
|
|
|
|
func (u *User) VerifyPassword(password string) bool {
|
|
|
|
if u.HashedPassword == "" {
|
2019-11-26 19:17:01 +00:00
|
|
|
console.Error("ERROR: VerifyPassword: user %s has no HashedPassword", u.Email)
|
2019-11-26 03:55:28 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
err := bcrypt.CompareHashAndPassword([]byte(u.HashedPassword), []byte(password))
|
|
|
|
if err == nil {
|
|
|
|
return true
|
|
|
|
}
|
2019-11-26 19:17:01 +00:00
|
|
|
console.Error("VerifyPassword: %s", err)
|
2019-11-26 03:55:28 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-11-15 04:58:55 +00:00
|
|
|
// FirstAdmin returns the admin user with the lowest ID number.
|
2020-02-18 02:10:35 +00:00
|
|
|
func (m userMan) FirstAdmin() (User, error) {
|
2019-11-15 04:58:55 +00:00
|
|
|
var user User
|
|
|
|
r := DB.First(&user, "is_admin", true)
|
|
|
|
return user, r.Error
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateUser adds a new user to the database.
|
2020-02-18 02:10:35 +00:00
|
|
|
func (m userMan) CreateUser(u User) error {
|
2019-11-15 04:58:55 +00:00
|
|
|
if err := u.Validate(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
r := DB.Create(&u)
|
|
|
|
return r.Error
|
2019-11-15 03:03:56 +00:00
|
|
|
}
|