add confirmation codes

This commit is contained in:
Sergey Chubaryan 2024-07-31 08:02:10 +03:00
parent edaaffeb61
commit 59e76a4ec1
7 changed files with 143 additions and 23 deletions

View File

@ -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 {

View File

@ -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
}

View File

@ -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
}

9
src/repo/action_token.go Normal file
View File

@ -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
}

5
src/repo/email_repo.go Normal file
View File

@ -0,0 +1,5 @@
package repo
type EmailRepo interface {
SendEmailForgotPassword(email, token string)
}

View File

@ -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
}

View File

@ -6,6 +6,8 @@ import (
"backend/src/utils"
"context"
"fmt"
"github.com/google/uuid"
)
var (
@ -32,6 +34,8 @@ type UserServiceDeps struct {
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