Hermes/src/core/services/user_service.go

210 lines
4.8 KiB
Go

package services
import (
"backend/src/core/models"
"backend/src/core/repos"
"backend/src/core/utils"
"context"
"fmt"
"github.com/google/uuid"
)
var (
ErrUserNotExists = fmt.Errorf("no such user")
ErrUserExists = fmt.Errorf("user with this login already exists")
ErrUserWrongPassword = fmt.Errorf("wrong password")
ErrUserWrongToken = fmt.Errorf("bad user token")
ErrUserBadPassword = fmt.Errorf("password must contain at least 8 characters")
// ErrUserInternal = fmt.Errorf("unexpected error. contact tech support")
)
type UserService interface {
CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error)
AuthenticateUser(ctx context.Context, login, password string) (string, error)
ValidateToken(ctx context.Context, tokenStr string) (*models.UserDTO, error)
}
func NewUserService(deps UserServiceDeps) UserService {
return &userService{deps}
}
type UserServiceDeps struct {
Jwt utils.JwtUtil
Password utils.PasswordUtil
UserRepo repos.UserRepo
UserCache repos.Cache[string, models.UserDTO]
EmailRepo repos.EmailRepo
ActionTokenRepo repos.ActionTokenRepo
}
type userService struct {
deps UserServiceDeps
}
type UserCreateParams struct {
Email string
Password string
Name string
}
func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error) {
exisitngUser, err := u.deps.UserRepo.GetUserByEmail(ctx, params.Email)
if err != nil {
return nil, err
}
if exisitngUser != nil {
return nil, ErrUserExists
}
if err := u.deps.Password.Validate(params.Password); err != nil {
return nil, err
}
secret, err := u.deps.Password.Hash(params.Password)
if err != nil {
return nil, err
}
user := models.UserDTO{
Email: params.Email,
Secret: string(secret),
Name: params.Name,
}
result, err := u.deps.UserRepo.CreateUser(ctx, user)
if err != nil {
return nil, err
}
u.deps.UserCache.Set(result.Id, *result, -1)
return result, nil
}
func (u *userService) AuthenticateUser(ctx context.Context, email, password string) (string, error) {
user, err := u.deps.UserRepo.GetUserByEmail(ctx, email)
if err != nil {
return "", err
}
if user == nil {
return "", ErrUserNotExists
}
if !u.deps.Password.Compare(password, user.Secret) {
return "", ErrUserWrongPassword
}
payload := utils.JwtPayload{UserId: user.Id}
jwt, err := u.deps.Jwt.Create(payload)
if err != nil {
return "", err
}
u.deps.UserCache.Set(user.Id, *user, -1)
return jwt, nil
}
func (u *userService) HelpPasswordForgot(ctx context.Context, userId string) error {
user, err := u.getUserById(ctx, userId)
if err != nil {
return err
}
actionToken, err := u.deps.ActionTokenRepo.CreateActionToken(
ctx,
models.ActionTokenDTO{
UserId: user.Id,
Value: uuid.New().String(),
Target: models.ActionTokenTargetForgotPassword,
},
)
if err != nil {
return err
}
u.deps.EmailRepo.SendEmailForgotPassword(user.Email, actionToken.Value)
return nil
}
func (u *userService) ChangePasswordForgot(ctx context.Context, userId, newPassword, accessCode string) error {
user, err := u.getUserById(ctx, userId)
if err != nil {
return err
}
code, err := u.deps.ActionTokenRepo.PopActionToken(ctx, userId, accessCode, models.ActionTokenTargetForgotPassword)
if err != nil {
return err
}
if code == nil {
return fmt.Errorf("wrong user access code")
}
return u.updatePassword(ctx, *user, newPassword)
}
func (u *userService) ChangePassword(ctx context.Context, userId, oldPassword, newPassword string) error {
user, err := u.getUserById(ctx, userId)
if err != nil {
return err
}
if !u.deps.Password.Compare(oldPassword, user.Secret) {
return ErrUserWrongPassword
}
return u.updatePassword(ctx, *user, newPassword)
}
func (u *userService) updatePassword(ctx context.Context, user models.UserDTO, newPassword string) error {
if err := u.deps.Password.Validate(newPassword); err != nil {
return ErrUserBadPassword
}
u.deps.UserCache.Del(user.Id)
newSecret, err := u.deps.Password.Hash(newPassword)
if err != nil {
return err
}
return u.deps.UserRepo.UpdateUser(ctx, user.Id, models.UserUpdateDTO{
Secret: newSecret,
Name: user.Name,
})
}
func (u *userService) getUserById(ctx context.Context, userId string) (*models.UserDTO, error) {
if user, ok := u.deps.UserCache.Get(userId); ok {
return &user, nil
}
user, err := u.deps.UserRepo.GetUserById(ctx, userId)
if err != nil {
return nil, err
}
if user == nil {
return nil, ErrUserNotExists
}
u.deps.UserCache.Set(user.Id, *user, -1)
return user, nil
}
func (u *userService) ValidateToken(ctx context.Context, tokenStr string) (*models.UserDTO, error) {
payload, err := u.deps.Jwt.Parse(tokenStr)
if err != nil {
return nil, ErrUserWrongToken
}
user, err := u.getUserById(ctx, payload.UserId)
if err != nil {
return nil, err
}
return user, nil
}