137 lines
3 KiB
Go
137 lines
3 KiB
Go
|
package service
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"encoding/hex"
|
||
|
"errors"
|
||
|
"net/mail"
|
||
|
|
||
|
normalizer "github.com/dimuska139/go-email-normalizer"
|
||
|
"github.com/labstack/echo-contrib/session"
|
||
|
"github.com/labstack/echo/v4"
|
||
|
"golang.org/x/crypto/bcrypt"
|
||
|
"gorm.io/gorm"
|
||
|
|
||
|
"gitrepo.ru/neonxp/nquest/pkg/models"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ErrInvalidUsername = errors.New("invalid username")
|
||
|
ErrInvalidPassword = errors.New("invalid password")
|
||
|
ErrInvalidEmail = errors.New("invalid email")
|
||
|
ErrDiffferentPassword = errors.New("different password")
|
||
|
ErrDuplicateUser = errors.New("duplicate user")
|
||
|
ErrIvalidUserOrPassword = errors.New("invalid user or password")
|
||
|
)
|
||
|
|
||
|
type User struct {
|
||
|
DB *gorm.DB
|
||
|
}
|
||
|
|
||
|
// NewUser returns new User.
|
||
|
func NewUser(db *gorm.DB) *User {
|
||
|
return &User{
|
||
|
DB: db,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (s *User) Register(ctx context.Context, username, email, password, password2 string) (*models.User, error) {
|
||
|
if len(username) < 3 || len(username) > 36 {
|
||
|
return nil, ErrInvalidUsername
|
||
|
}
|
||
|
|
||
|
if len(password) < 8 {
|
||
|
return nil, ErrInvalidPassword
|
||
|
}
|
||
|
|
||
|
if password != password2 {
|
||
|
return nil, ErrDiffferentPassword
|
||
|
}
|
||
|
|
||
|
if !isValidEmail(email) {
|
||
|
return nil, ErrInvalidEmail
|
||
|
}
|
||
|
|
||
|
hashed, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
u := &models.User{
|
||
|
Username: username,
|
||
|
Email: normalizer.NewNormalizer().Normalize(email),
|
||
|
Password: hex.EncodeToString(hashed),
|
||
|
Role: models.RoleUser,
|
||
|
}
|
||
|
|
||
|
err = s.DB.WithContext(ctx).Create(u).Error
|
||
|
switch {
|
||
|
case errors.Is(err, models.ErrEmptyPassword):
|
||
|
return nil, ErrInvalidPassword
|
||
|
case errors.Is(err, gorm.ErrDuplicatedKey):
|
||
|
return nil, ErrDuplicateUser
|
||
|
case err != nil:
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return u, nil
|
||
|
}
|
||
|
|
||
|
func (s *User) Login(ctx context.Context, email, password string) (*models.User, error) {
|
||
|
u := new(models.User)
|
||
|
nemail := normalizer.NewNormalizer().Normalize(email)
|
||
|
err := s.DB.
|
||
|
WithContext(ctx).
|
||
|
Where("email = ?", nemail).
|
||
|
Preload("Team").Preload("Team.Team").
|
||
|
First(u).
|
||
|
Error
|
||
|
if err != nil {
|
||
|
return nil, ErrIvalidUserOrPassword
|
||
|
}
|
||
|
|
||
|
b, err := hex.DecodeString(u.Password)
|
||
|
if err != nil {
|
||
|
return nil, ErrIvalidUserOrPassword
|
||
|
}
|
||
|
if err := bcrypt.CompareHashAndPassword(b, []byte(password)); err != nil {
|
||
|
return nil, ErrIvalidUserOrPassword
|
||
|
}
|
||
|
|
||
|
return u, nil
|
||
|
}
|
||
|
|
||
|
func (s *User) GetUserByID(ctx context.Context, userID uint) (*models.User, error) {
|
||
|
u := new(models.User)
|
||
|
|
||
|
return u, s.DB.WithContext(ctx).Preload("Team").Preload("Team.Team").First(u, userID).Error
|
||
|
}
|
||
|
|
||
|
func (s *User) GetUser(c echo.Context) *models.User {
|
||
|
sess, err := session.Get("session", c)
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
userID, ok := sess.Values["userID"].(uint)
|
||
|
if !ok {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
user, err := s.GetUserByID(c.Request().Context(), userID)
|
||
|
if err != nil {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return user
|
||
|
}
|
||
|
|
||
|
func (s *User) Update(ctx context.Context, user *models.User) error {
|
||
|
return s.DB.WithContext(ctx).Session(&gorm.Session{FullSaveAssociations: true}).Save(user).Error
|
||
|
}
|
||
|
|
||
|
func isValidEmail(email string) bool {
|
||
|
_, err := mail.ParseAddress(email)
|
||
|
return err == nil
|
||
|
}
|