206 lines
4.8 KiB
Go
206 lines
4.8 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
const (
|
|
OtpExpiry = 5 * time.Minute
|
|
)
|
|
|
|
var (
|
|
ErrOtpAlreadyUsed = errors.New("otp already used")
|
|
ErrInvalidOtp = errors.New("invalid otp")
|
|
ErrOtpExpired = errors.New("otp expired")
|
|
)
|
|
|
|
type Service struct {
|
|
userStore UserStore
|
|
otpStore OtpStore
|
|
smsGateway SmsGateway
|
|
emailGateway EmailGateway
|
|
}
|
|
|
|
func NewService(
|
|
userStore UserStore, RefreshExpiry int,
|
|
otpStore OtpStore, smsGateway SmsGateway,
|
|
emailGateway EmailGateway,
|
|
) *Service {
|
|
return &Service{
|
|
userStore: userStore,
|
|
otpStore: otpStore,
|
|
}
|
|
}
|
|
|
|
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
|
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
|
}
|
|
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
|
var err error
|
|
// check if user exists
|
|
switch medium {
|
|
case domain.OtpMediumEmail:
|
|
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
|
case domain.OtpMediumSms:
|
|
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// send otp based on the medium
|
|
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
|
}
|
|
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
|
// get otp
|
|
var sentTo string
|
|
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
|
sentTo = registerReq.Email
|
|
} else {
|
|
sentTo = registerReq.PhoneNumber
|
|
}
|
|
//
|
|
otp, err := s.otpStore.GetOtp(
|
|
ctx, sentTo,
|
|
domain.OtpRegister, registerReq.OtpMedium)
|
|
if err != nil {
|
|
return domain.User{}, err
|
|
}
|
|
// verify otp
|
|
if otp.Used {
|
|
return domain.User{}, ErrOtpAlreadyUsed
|
|
}
|
|
if time.Now().After(otp.ExpiresAt) {
|
|
return domain.User{}, ErrOtpExpired
|
|
}
|
|
if otp.Otp != registerReq.Otp {
|
|
return domain.User{}, ErrInvalidOtp
|
|
}
|
|
|
|
hashedPassword, err := hashPassword(registerReq.Password)
|
|
if err != nil {
|
|
return domain.User{}, err
|
|
}
|
|
userR := domain.User{
|
|
FirstName: registerReq.FirstName,
|
|
LastName: registerReq.LastName,
|
|
Email: registerReq.Email,
|
|
PhoneNumber: registerReq.PhoneNumber,
|
|
Password: hashedPassword,
|
|
Role: "user",
|
|
EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail,
|
|
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
|
|
}
|
|
// create the user and mark otp as used
|
|
user, err := s.userStore.CreateUser(ctx, userR, otp.ID)
|
|
if err != nil {
|
|
return domain.User{}, err
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
|
|
|
var err error
|
|
// check if user exists
|
|
switch medium {
|
|
case domain.OtpMediumEmail:
|
|
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
|
case domain.OtpMediumSms:
|
|
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
|
|
|
}
|
|
|
|
func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error {
|
|
var sentTo string
|
|
if resetReq.OtpMedium == domain.OtpMediumEmail {
|
|
sentTo = resetReq.Email
|
|
} else {
|
|
sentTo = resetReq.PhoneNumber
|
|
}
|
|
otp, err := s.otpStore.GetOtp(
|
|
ctx, sentTo,
|
|
domain.OtpRegister, resetReq.OtpMedium)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
//
|
|
if otp.Used {
|
|
return ErrOtpAlreadyUsed
|
|
}
|
|
if time.Now().After(otp.ExpiresAt) {
|
|
return ErrOtpExpired
|
|
}
|
|
if otp.Otp != resetReq.Otp {
|
|
return ErrInvalidOtp
|
|
}
|
|
// hash password
|
|
hashedPassword, err := hashPassword(resetReq.Password)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// reset pass and mark otp as used
|
|
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error {
|
|
otpCode := "123456" // Generate OTP code
|
|
|
|
otp := domain.Otp{
|
|
SentTo: sentTo,
|
|
Medium: medium,
|
|
For: otpFor,
|
|
Otp: otpCode,
|
|
Used: false,
|
|
CreatedAt: time.Now(),
|
|
ExpiresAt: time.Now().Add(OtpExpiry),
|
|
}
|
|
|
|
err := s.otpStore.CreateOtp(ctx, otp)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch medium {
|
|
case domain.OtpMediumSms:
|
|
return s.smsGateway.SendSMSOTP(ctx, sentTo, otpCode)
|
|
case domain.OtpMediumEmail:
|
|
return s.emailGateway.SendEmailOTP(ctx, sentTo, otpCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
|
// update user
|
|
return s.userStore.UpdateUser(ctx, user)
|
|
|
|
}
|
|
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
|
return s.userStore.GetUserByID(ctx, id)
|
|
}
|
|
|
|
func hashPassword(plaintextPassword string) ([]byte, error) {
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
|
if err != nil {
|
|
return []byte{}, err
|
|
}
|
|
|
|
return hash, nil
|
|
}
|