package pwreset import ( "unicode" "github.com/badoux/checkmail" uuid2 "github.com/google/uuid" "golang.org/x/crypto/bcrypt" "src.dualinventive.com/go/authentication-service/internal/domain" "src.dualinventive.com/go/authentication-service/internal/storage" ) //RequestPasswordReset generates a unique password reset code and sends it to the users email address. func RequestPasswordReset( credentialsRepo storage.CredentialsRepository, emailSender EmailSender, templateRepository TemplateRepository, codeRepo storage.ResetCodeRepository, userName string, ) error { user, err := credentialsRepo.GetUserByUserName(userName, "") if err != nil { return NewErrUserFetchFailed(userName, err) } if user == nil { return NewErrUserNotFound(userName) } toEmail := user.Email if toEmail == "" { return NewErrUserEmailAddressEmpty(userName) } if !isValidEmail(toEmail) { return NewErrUserEmailInvalid(userName, toEmail) } uuid := uuid2.New().String() err = codeRepo.CreateCode(userName, uuid) if err != nil { return NewErrFailedToCreateResetCode(userName, uuid, err) } err = sendEmail(emailSender, templateRepository, userName, emailSender.From(), toEmail, uuid) if err != nil { _ = codeRepo.DeleteCode(userName) //nolint:gosec return NewErrFailedToSendEmail(userName, toEmail, err) } return nil } func isValidEmail(email string) bool { err := checkmail.ValidateFormat(email) return err == nil } func sendEmail( emailSender EmailSender, templateRepo TemplateRepository, userName, fromEmail, toEmail, uuid string, ) error { body, err := templateRepo.Template(userName, fromEmail, toEmail, uuid) if err != nil { return err } err = emailSender.Send([]string{toEmail}, body) if err != nil { return err } return nil } //RedeemPasswordReset will set a password of a user when the given username and password reset code a valid. func RedeemPasswordReset( credentialsRepo storage.CredentialsRepository, codeRepo storage.ResetCodeRepository, userName string, passwordResetCode string, newPassword string, newPasswordVerify string, ) error { user, err := credentialsRepo.GetUserByUserName(userName, "") if err != nil { return NewErrUserFetchFailed(userName, err) } if user == nil { return NewErrUserNotFound(userName) } actualResetCode, err := codeRepo.GetCode(userName) if err != nil { return NewErrFailedToGetResetCode(userName, err) } if actualResetCode == "" { return NewErrResetCodeNotFound(userName) } if passwordResetCode != actualResetCode { return NewErrInvalidResetCode(userName, passwordResetCode) } if newPassword != newPasswordVerify { return ErrVerifyPasswordFailed } if !validPassword(newPassword) { return ErrInvalidPassword } err = setPassword(credentialsRepo, user, newPassword) if err != nil { return NewErrChangePasswordFailed(userName, err) } err = codeRepo.DeleteCode(userName) if err != nil { return NewErrDeleteResetCodeFailed(userName, err) } return nil } func setPassword(credentialsRepo storage.CredentialsRepository, user *domain.User, password string) error { hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return err } err = credentialsRepo.SetPassword(user, hash) if err != nil { return err } return nil } func validPassword(pass string) bool { if len(pass) < 3 { return false } var lower, upper, numeric bool for _, c := range pass { switch { case unicode.IsLower(c): lower = true case unicode.IsUpper(c): upper = true case unicode.IsNumber(c): numeric = true } } return lower && upper && numeric }