From 59e76a4ec18c9bc7f3f51eb00f543578ec560fe7 Mon Sep 17 00:00:00 2001 From: Sergey Chubaryan Date: Wed, 31 Jul 2024 08:02:10 +0300 Subject: [PATCH] add confirmation codes --- src/handlers/user_create_handler.go | 8 +-- src/models/action_token.go | 15 +++++ src/models/user.go | 7 ++- src/repo/action_token.go | 9 +++ src/repo/email_repo.go | 5 ++ src/repo/user_repo.go | 29 ++++++--- src/services/user_service.go | 93 ++++++++++++++++++++++++++--- 7 files changed, 143 insertions(+), 23 deletions(-) create mode 100644 src/models/action_token.go create mode 100644 src/repo/action_token.go create mode 100644 src/repo/email_repo.go diff --git a/src/handlers/user_create_handler.go b/src/handlers/user_create_handler.go index 3ab2269..b50f336 100644 --- a/src/handlers/user_create_handler.go +++ b/src/handlers/user_create_handler.go @@ -8,14 +8,14 @@ import ( ) type createUserInput struct { - Login string + Email string Password string Name string } type createUserOutput struct { Id string `json:"id"` - Login string `json:"login"` + Email string `json:"email"` Name string `json:"name"` } @@ -28,7 +28,7 @@ func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc { } dto, err := userService.CreateUser(ctx, services.UserCreateParams{ - Login: params.Login, + Email: params.Email, Password: params.Password, Name: params.Name, }) @@ -43,7 +43,7 @@ func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc { resultBody, err := json.Marshal(createUserOutput{ Id: dto.Id, - Login: dto.Login, + Email: dto.Email, Name: dto.Name, }) if err != nil { diff --git a/src/models/action_token.go b/src/models/action_token.go new file mode 100644 index 0000000..a8b01d5 --- /dev/null +++ b/src/models/action_token.go @@ -0,0 +1,15 @@ +package models + +type ActionTokenTarget int + +const ( + ActionTokenTargetForgotPassword ActionTokenTarget = iota + ActionTokenTargetLogin2FA +) + +type ActionTokenDTO struct { + Id string + UserId string + Value string + Target ActionTokenTarget +} diff --git a/src/models/user.go b/src/models/user.go index 1deb356..ced8300 100644 --- a/src/models/user.go +++ b/src/models/user.go @@ -2,7 +2,12 @@ package models type UserDTO struct { Id string - Login string + Email string + Secret string + Name string +} + +type UserUpdateDTO struct { Secret string Name string } diff --git a/src/repo/action_token.go b/src/repo/action_token.go new file mode 100644 index 0000000..8e4d659 --- /dev/null +++ b/src/repo/action_token.go @@ -0,0 +1,9 @@ +package repo + +import "backend/src/models" + +type ActionTokenRepo interface { + CreateActionToken(actionToken models.ActionTokenDTO) (*models.ActionTokenDTO, error) + FindActionToken(userId, val string, target models.ActionTokenTarget) (*models.ActionTokenDTO, error) + DeleteActionToken(id string) error +} diff --git a/src/repo/email_repo.go b/src/repo/email_repo.go new file mode 100644 index 0000000..3b8c256 --- /dev/null +++ b/src/repo/email_repo.go @@ -0,0 +1,5 @@ +package repo + +type EmailRepo interface { + SendEmailForgotPassword(email, token string) +} diff --git a/src/repo/user_repo.go b/src/repo/user_repo.go index a7d4e61..c2651bd 100644 --- a/src/repo/user_repo.go +++ b/src/repo/user_repo.go @@ -16,8 +16,9 @@ import ( type UserRepo interface { CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) + UpdateUser(ctx context.Context, userId string, dto models.UserUpdateDTO) error GetUserById(ctx context.Context, id string) (*models.UserDTO, error) - GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error) + GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error) } func NewUserRepo(db *sql.DB) UserRepo { @@ -29,8 +30,8 @@ type userRepo struct { } func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) { - query := `insert into users (login, secret, name) values ($1, $2, $3) returning id;` - row := u.db.QueryRowContext(ctx, query, dto.Login, dto.Secret, dto.Name) + query := `insert into users (email, secret, name) values ($1, $2, $3) returning id;` + row := u.db.QueryRowContext(ctx, query, dto.Email, dto.Secret, dto.Name) id := "" if err := row.Scan(&id); err != nil { @@ -39,18 +40,28 @@ func (u *userRepo) CreateUser(ctx context.Context, dto models.UserDTO) (*models. return &models.UserDTO{ Id: id, - Login: dto.Login, + Email: dto.Email, Secret: dto.Secret, Name: dto.Name, }, nil } +func (u *userRepo) UpdateUser(ctx context.Context, userId string, dto models.UserUpdateDTO) error { + query := `update users set secret=$1, name=$2 where id = $3;` + _, err := u.db.ExecContext(ctx, query, dto.Secret, dto.Name, userId) + if err != nil { + return err + } + + return nil +} + func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO, error) { - query := `select id, login, secret, name from users where id = $1;` + query := `select id, email, secret, name from users where id = $1;` row := u.db.QueryRowContext(ctx, query, id) dto := &models.UserDTO{} - err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name) + err := row.Scan(&dto.Id, &dto.Email, &dto.Secret, &dto.Name) if err == nil { return dto, nil } @@ -61,12 +72,12 @@ func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO, return nil, err } -func (u *userRepo) GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error) { - query := `select id, login, secret, name from users where login = $1;` +func (u *userRepo) GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error) { + query := `select id, email, secret, name from users where email = $1;` row := u.db.QueryRowContext(ctx, query, login) dto := &models.UserDTO{} - err := row.Scan(&dto.Id, &dto.Login, &dto.Secret, &dto.Name) + err := row.Scan(&dto.Id, &dto.Email, &dto.Secret, &dto.Name) if err == nil { return dto, nil } diff --git a/src/services/user_service.go b/src/services/user_service.go index 8c8a6ce..6e11d62 100644 --- a/src/services/user_service.go +++ b/src/services/user_service.go @@ -6,6 +6,8 @@ import ( "backend/src/utils" "context" "fmt" + + "github.com/google/uuid" ) var ( @@ -28,10 +30,12 @@ func NewUserService(deps UserServiceDeps) UserService { } type UserServiceDeps struct { - Jwt utils.JwtUtil - Password utils.PasswordUtil - UserRepo repo.UserRepo - UserCache repo.Cache[string, models.UserDTO] + Jwt utils.JwtUtil + Password utils.PasswordUtil + UserRepo repo.UserRepo + UserCache repo.Cache[string, models.UserDTO] + EmailRepo repo.EmailRepo + ActionTokenRepo repo.ActionTokenRepo } type userService struct { @@ -39,13 +43,13 @@ type userService struct { } type UserCreateParams struct { - Login string + Email string Password string Name string } func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error) { - exisitngUser, err := u.deps.UserRepo.GetUserByLogin(ctx, params.Login) + exisitngUser, err := u.deps.UserRepo.GetUserByEmail(ctx, params.Email) if err != nil { return nil, err } @@ -63,7 +67,7 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) ( } user := models.UserDTO{ - Login: params.Login, + Email: params.Email, Secret: string(secret), Name: params.Name, } @@ -78,8 +82,8 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) ( return result, nil } -func (u *userService) AuthenticateUser(ctx context.Context, login, password string) (string, error) { - user, err := u.deps.UserRepo.GetUserByLogin(ctx, login) +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 } @@ -102,6 +106,77 @@ func (u *userService) AuthenticateUser(ctx context.Context, login, password stri return jwt, nil } +func (u *userService) HelpPasswordForgot(ctx context.Context, userId string) error { + user, err := u.deps.UserRepo.GetUserById(ctx, userId) + if err != nil { + return err + } + + actionToken, err := u.deps.ActionTokenRepo.CreateActionToken(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.deps.UserRepo.GetUserById(ctx, userId) + if err != nil { + return err + } + + code, err := u.deps.ActionTokenRepo.FindActionToken(userId, accessCode, models.ActionTokenTargetForgotPassword) + if err != nil { + return err + } + if code == nil { + return fmt.Errorf("wrong user access code") + } + + if err := u.deps.ActionTokenRepo.DeleteActionToken(code.Id); err != nil { + return fmt.Errorf("internal error occured: %w", err) + } + + 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