package models import ( "database/sql" "errors" "strings" "time" "github.com/go-sql-driver/mysql" "golang.org/x/crypto/bcrypt" ) type User struct { ID int Name string Email string HashedPassword []byte Created time.Time } type UserModelInterface interface { Insert(name, email, password string) error Authenticate(email, password string) (int, error) Exists(id int) (bool, error) GetById(id int) (*User, error) ChangePassword(id int, curr_pass, new_pass string) error } type UserModel struct { DB *sql.DB } func (m *UserModel) Insert(name, email, password string) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12) if err != nil { return err } stmt := `INSERT INTO users (name, email, hashed_password, created) VALUES(?, ?, ?, UTC_TIMESTAMP())` _, err = m.DB.Exec(stmt, name, email, string(hashedPassword)) if err != nil { var mySQLError *mysql.MySQLError if errors.As(err, &mySQLError) { if mySQLError.Number == 1062 && strings.Contains(mySQLError.Message, "users_uc_email") { return ErrDuplicateEmail } } return err } return nil } func (m *UserModel) Authenticate(email, password string) (int, error) { var id int var hashedPassword []byte stmt := `SELECT id, hashed_password FROM users WHERE email = ?` err := m.DB.QueryRow(stmt, email).Scan(&id, &hashedPassword) if err != nil { if errors.Is(err, sql.ErrNoRows) { return 0, ErrInvalidCredentials } return 0, err } err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)) if err != nil { if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { return 0, ErrInvalidCredentials } return 0, err } return id, nil } func (m *UserModel) Exists(id int) (bool, error) { var exists bool stmt := `SELECT EXISTS(SELECT true FROM users WHERE id = ?)` err := m.DB.QueryRow(stmt, id).Scan(&exists) return exists, err } func (a *UserModel) GetById(id int) (*User, error) { acc := User{} stmt := `SELECT name, email, created FROM users WHERE id = ?` err := a.DB.QueryRow(stmt, id).Scan(&acc.Name, &acc.Email, &acc.Created) if err != nil { if errors.Is(err, sql.ErrNoRows) { return nil, ErrInvalidCredentials } return nil, err } return &acc, nil } func (a *UserModel) ChangePassword(id int, curr_pass, new_pass string) error { hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(new_pass), 12) if err != nil { return err } var hashedDBPassword []byte stmt := `SELECT hashed_password FROM users WHERE id = ?` err = a.DB.QueryRow(stmt, id).Scan(&hashedDBPassword) if err != nil { return err } err = bcrypt.CompareHashAndPassword(hashedDBPassword, []byte(curr_pass)) if err != nil { if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { return ErrInvalidCredentials } return err } stmt = `UPDATE users SET hashed_password = ? WHERE id = ?` _, err = a.DB.Exec(stmt, hashedNewPassword, id) if err != nil { return err } return nil }