package auth import ( "context" "furtuna-be/config" "furtuna-be/internal/common/mycontext" "furtuna-be/internal/entity" "furtuna-be/internal/utils" "go.uber.org/zap" "furtuna-be/internal/common/errors" "furtuna-be/internal/common/logger" "furtuna-be/internal/repository" ) type AuthServiceImpl struct { authRepo repository.Auth crypto repository.Crypto user repository.User emailSvc repository.EmailService emailCfg config.Email trxRepo repository.TransactionManager license repository.License } func New(authRepo repository.Auth, crypto repository.Crypto, user repository.User, emailSvc repository.EmailService, emailCfg config.Email, trxRepo repository.TransactionManager, license repository.License, ) *AuthServiceImpl { return &AuthServiceImpl{ authRepo: authRepo, crypto: crypto, user: user, emailSvc: emailSvc, emailCfg: emailCfg, trxRepo: trxRepo, license: license, } } func (u *AuthServiceImpl) AuthenticateUser(ctx context.Context, email, password string) (*entity.AuthenticateUser, error) { user, err := u.authRepo.CheckExistsUserAccount(ctx, email) if err != nil { logger.ContextLogger(ctx).Error("error when get user", zap.Error(err)) return nil, errors.ErrorInternalServer } if user == nil { return nil, errors.ErrorUserIsNotFound } if ok := u.crypto.CompareHashAndPassword(user.Password, password); !ok { return nil, errors.ErrorUserInvalidLogin } signedToken, err := u.crypto.GenerateJWT(user.ToUser()) if err != nil { return nil, err } var licensePartner entity.PartnerLicense if user.PartnerID != nil && *user.PartnerID != 0 { parterLicense, err := u.license.FindByPartnerIDMaxEndDate(ctx, user.PartnerID) if err != nil { logger.ContextLogger(ctx).Error("error when get user license", zap.Error(err)) return nil, errors.ErrorInternalServer } if parterLicense != nil { licensePartner = parterLicense.ToPartnerLicense() } } return user.ToUserAuthenticate(signedToken, licensePartner), nil } func (u *AuthServiceImpl) SendPasswordResetLink(ctx context.Context, email string) error { // Check if the user exists user, err := u.authRepo.CheckExistsUserAccount(ctx, email) if err != nil { logger.ContextLogger(ctx).Error("error when getting user", zap.Error(err)) return errors.ErrorInternalServer } if user == nil { return errors.ErrorUserIsNotFound } // Begin a transaction trx, err := u.trxRepo.Begin(ctx) if err != nil { logger.ContextLogger(ctx).Error("error when beginning transaction", zap.Error(err)) return errors.ErrorInternalServer } defer func() { if r := recover(); r != nil { u.trxRepo.Rollback(trx) logger.ContextLogger(ctx).Error("panic recovered in SendPasswordResetLink", zap.Any("recover", r)) err = errors.ErrorInternalServer } }() // Generate a new password generatedPassword := utils.GenerateRandomString(10) hashPassword, err := user.ToUser().HashedPassword(generatedPassword) if err != nil { logger.ContextLogger(ctx).Error("error when generating hashed password", zap.Error(err)) u.trxRepo.Rollback(trx) return errors.ErrorInternalServer } // Update the user's password in the database if err := u.authRepo.UpdatePassword(ctx, trx, hashPassword, user.ID, true); err != nil { logger.ContextLogger(ctx).Error("error when updating user password", zap.Error(err)) u.trxRepo.Rollback(trx) return errors.ErrorInternalServer } // If a custom receiver is specified, override the email if u.emailCfg.CustomReceiver != "" { email = u.emailCfg.CustomReceiver } // Prepare the email notification parameters renewPasswordRequest := entity.SendEmailNotificationParam{ Recipient: email, Subject: u.emailCfg.ResetPassword.Subject, TemplateName: u.emailCfg.ResetPassword.TemplateName, TemplatePath: u.emailCfg.ResetPassword.TemplatePath, Data: map[string]interface{}{ "Name": user.Name, "OpeningWord": u.emailCfg.ResetPassword.OpeningWord, "Password": generatedPassword, "ClosingWord": u.emailCfg.ResetPassword.ClosingWord, "Note": u.emailCfg.ResetPassword.Notes, "Link": u.emailCfg.ResetPassword.Link, }, } defer func() { if r := recover(); r != nil { u.trxRepo.Rollback(trx) logger.ContextLogger(ctx).Error("panic recovered in SendPasswordResetLink", zap.Any("recover", r)) err = errors.ErrorInternalServer } }() // Send the email notification err = u.emailSvc.SendEmailTransactional(ctx, renewPasswordRequest) if err != nil { u.trxRepo.Rollback(trx) logger.ContextLogger(ctx).Error("error when sending password reset email", zap.Error(err)) return errors.ErrorInternalServer } trx.Commit() return nil } func (u *AuthServiceImpl) ResetPassword(ctx mycontext.Context, oldPassword, newPassword string) error { user, err := u.authRepo.CheckExistsUserAccountByID(ctx, ctx.RequestedBy()) if err != nil { return errors.ErrorInvalidRequest } if ok := u.crypto.CompareHashAndPassword(user.Password, oldPassword); !ok { return errors.ErrorUserInvalidLogin } password, err := user.ToUser().HashedPassword(newPassword) if err != nil { return errors.ErrorInvalidRequest } trx, _ := u.trxRepo.Begin(ctx) err = u.authRepo.UpdatePassword(ctx, trx, password, user.ID, false) if err != nil { return errors.ErrorInternalServer } trx.Commit() return nil }