src.dualinventive.com/go/authentication-service/internal/pwreset/pwreset.go

153 lines
3.5 KiB
Go

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
}