210 lines
4.8 KiB
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, ErrUserBadPassword
|
|
}
|
|
|
|
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
|
|
}
|