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 { type createUserInput struct {
Login string Email string
Password string Password string
Name string Name string
} }
type createUserOutput struct { type createUserOutput struct {
Id string `json:"id"` Id string `json:"id"`
Login string `json:"login"` Email string `json:"email"`
Name string `json:"name"` Name string `json:"name"`
} }
@ -28,7 +28,7 @@ func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc {
} }
dto, err := userService.CreateUser(ctx, services.UserCreateParams{ dto, err := userService.CreateUser(ctx, services.UserCreateParams{
Login: params.Login, Email: params.Email,
Password: params.Password, Password: params.Password,
Name: params.Name, Name: params.Name,
}) })
@ -43,7 +43,7 @@ func NewUserCreateHandler(userService services.UserService) gin.HandlerFunc {
resultBody, err := json.Marshal(createUserOutput{ resultBody, err := json.Marshal(createUserOutput{
Id: dto.Id, Id: dto.Id,
Login: dto.Login, Email: dto.Email,
Name: dto.Name, Name: dto.Name,
}) })
if err != nil { 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 { type UserDTO struct {
Id string Id string
Login string Email string
Secret string
Name string
}
type UserUpdateDTO struct {
Secret string Secret string
Name 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 { type UserRepo interface {
CreateUser(ctx context.Context, dto models.UserDTO) (*models.UserDTO, error) 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) 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 { 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) { 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;` query := `insert into users (email, secret, name) values ($1, $2, $3) returning id;`
row := u.db.QueryRowContext(ctx, query, dto.Login, dto.Secret, dto.Name) row := u.db.QueryRowContext(ctx, query, dto.Email, dto.Secret, dto.Name)
id := "" id := ""
if err := row.Scan(&id); err != nil { 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{ return &models.UserDTO{
Id: id, Id: id,
Login: dto.Login, Email: dto.Email,
Secret: dto.Secret, Secret: dto.Secret,
Name: dto.Name, Name: dto.Name,
}, nil }, 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) { 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) row := u.db.QueryRowContext(ctx, query, id)
dto := &models.UserDTO{} 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 { if err == nil {
return dto, nil return dto, nil
} }
@ -61,12 +72,12 @@ func (u *userRepo) GetUserById(ctx context.Context, id string) (*models.UserDTO,
return nil, err return nil, err
} }
func (u *userRepo) GetUserByLogin(ctx context.Context, login string) (*models.UserDTO, error) { func (u *userRepo) GetUserByEmail(ctx context.Context, login string) (*models.UserDTO, error) {
query := `select id, login, secret, name from users where login = $1;` query := `select id, email, secret, name from users where email = $1;`
row := u.db.QueryRowContext(ctx, query, login) row := u.db.QueryRowContext(ctx, query, login)
dto := &models.UserDTO{} 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 { if err == nil {
return dto, nil return dto, nil
} }

View File

@ -6,6 +6,8 @@ import (
"backend/src/utils" "backend/src/utils"
"context" "context"
"fmt" "fmt"
"github.com/google/uuid"
) )
var ( var (
@ -32,6 +34,8 @@ type UserServiceDeps struct {
Password utils.PasswordUtil Password utils.PasswordUtil
UserRepo repo.UserRepo UserRepo repo.UserRepo
UserCache repo.Cache[string, models.UserDTO] UserCache repo.Cache[string, models.UserDTO]
EmailRepo repo.EmailRepo
ActionTokenRepo repo.ActionTokenRepo
} }
type userService struct { type userService struct {
@ -39,13 +43,13 @@ type userService struct {
} }
type UserCreateParams struct { type UserCreateParams struct {
Login string Email string
Password string Password string
Name string Name string
} }
func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (*models.UserDTO, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -63,7 +67,7 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (
} }
user := models.UserDTO{ user := models.UserDTO{
Login: params.Login, Email: params.Email,
Secret: string(secret), Secret: string(secret),
Name: params.Name, Name: params.Name,
} }
@ -78,8 +82,8 @@ func (u *userService) CreateUser(ctx context.Context, params UserCreateParams) (
return result, nil return result, nil
} }
func (u *userService) AuthenticateUser(ctx context.Context, login, password string) (string, error) { func (u *userService) AuthenticateUser(ctx context.Context, email, password string) (string, error) {
user, err := u.deps.UserRepo.GetUserByLogin(ctx, login) user, err := u.deps.UserRepo.GetUserByEmail(ctx, email)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -102,6 +106,77 @@ func (u *userService) AuthenticateUser(ctx context.Context, login, password stri
return jwt, nil 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) { func (u *userService) getUserById(ctx context.Context, userId string) (*models.UserDTO, error) {
if user, ok := u.deps.UserCache.Get(userId); ok { if user, ok := u.deps.UserCache.Get(userId); ok {
return &user, nil return &user, nil