To gitea and beyond, let's go(-yco)
This commit is contained in:
135
internal/services/password_reset_service.go
Normal file
135
internal/services/password_reset_service.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"goyco/internal/database"
|
||||
"goyco/internal/repositories"
|
||||
"goyco/internal/validation"
|
||||
)
|
||||
|
||||
type PasswordResetService struct {
|
||||
userRepo repositories.UserRepository
|
||||
emailService *EmailService
|
||||
}
|
||||
|
||||
func NewPasswordResetService(userRepo repositories.UserRepository, emailService *EmailService) *PasswordResetService {
|
||||
return &PasswordResetService{
|
||||
userRepo: userRepo,
|
||||
emailService: emailService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PasswordResetService) RequestPasswordReset(usernameOrEmail string) error {
|
||||
trimmed := TrimString(usernameOrEmail)
|
||||
if trimmed == "" {
|
||||
return fmt.Errorf("username or email is required")
|
||||
}
|
||||
|
||||
var user *database.User
|
||||
var err error
|
||||
|
||||
normalized, emailErr := normalizeEmail(trimmed)
|
||||
if emailErr == nil {
|
||||
user, err = s.userRepo.GetByEmail(normalized)
|
||||
if err != nil && !IsRecordNotFound(err) {
|
||||
return fmt.Errorf("lookup user by email: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
user, err = s.userRepo.GetByUsername(trimmed)
|
||||
if err != nil {
|
||||
if IsRecordNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("lookup user by username: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
token, hashed, err := generateVerificationToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(time.Duration(defaultTokenExpirationHours) * time.Hour)
|
||||
|
||||
user.PasswordResetToken = hashed
|
||||
user.PasswordResetSentAt = &now
|
||||
user.PasswordResetExpiresAt = &expiresAt
|
||||
|
||||
if err := s.userRepo.Update(user); err != nil {
|
||||
return fmt.Errorf("update user: %w", err)
|
||||
}
|
||||
|
||||
if err := s.emailService.SendPasswordResetEmail(user, token); err != nil {
|
||||
user.PasswordResetToken = ""
|
||||
user.PasswordResetSentAt = nil
|
||||
user.PasswordResetExpiresAt = nil
|
||||
_ = s.userRepo.Update(user)
|
||||
return fmt.Errorf("send password reset email: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *PasswordResetService) GetUserByResetToken(token string) (*database.User, error) {
|
||||
trimmed := TrimString(token)
|
||||
if trimmed == "" {
|
||||
return nil, fmt.Errorf("reset token is required")
|
||||
}
|
||||
|
||||
hashed := HashVerificationToken(trimmed)
|
||||
user, err := s.userRepo.GetByPasswordResetToken(hashed)
|
||||
if err != nil {
|
||||
if IsRecordNotFound(err) {
|
||||
return nil, fmt.Errorf("invalid or expired reset token")
|
||||
}
|
||||
return nil, fmt.Errorf("lookup reset token: %w", err)
|
||||
}
|
||||
|
||||
if user.PasswordResetExpiresAt == nil || time.Now().After(*user.PasswordResetExpiresAt) {
|
||||
return nil, fmt.Errorf("invalid or expired reset token")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *PasswordResetService) ResetPassword(token, newPassword string) error {
|
||||
if err := validation.ValidatePassword(newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := s.GetUserByResetToken(token)
|
||||
if err != nil {
|
||||
hashed := HashVerificationToken(TrimString(token))
|
||||
expiredUser, lookupErr := s.userRepo.GetByPasswordResetToken(hashed)
|
||||
if lookupErr == nil && expiredUser != nil {
|
||||
if expiredUser.PasswordResetExpiresAt == nil || time.Now().After(*expiredUser.PasswordResetExpiresAt) {
|
||||
expiredUser.PasswordResetToken = ""
|
||||
expiredUser.PasswordResetSentAt = nil
|
||||
expiredUser.PasswordResetExpiresAt = nil
|
||||
_ = s.userRepo.Update(expiredUser)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
hashedPassword, err := HashPassword(newPassword, DefaultBcryptCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Password = string(hashedPassword)
|
||||
user.PasswordResetToken = ""
|
||||
user.PasswordResetSentAt = nil
|
||||
user.PasswordResetExpiresAt = nil
|
||||
|
||||
if err := s.userRepo.Update(user); err != nil {
|
||||
return fmt.Errorf("update password: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user