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 }