package user import ( "Yimaru-Backend/internal/domain" "context" "crypto/subtle" "errors" "time" ) func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error { var user domain.User var err error // Look up user by email or phone to get the actual userID switch medium { case domain.OtpMediumEmail: user, err = s.userStore.GetUserByEmailPhone(ctx, sentTo, "") case domain.OtpMediumSms: user, err = s.userStore.GetUserByEmailPhone(ctx, "", sentTo) default: return errors.New("invalid OTP medium") } if err != nil { return err } // Use the actual user ID when storing OTP return s.SendOtp(ctx, user.ID, sentTo, domain.OtpReset, medium, provider) } func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error { // Look up user by email or phone (don't trust client-provided user_id) var user domain.User var err error if resetReq.Email != "" { user, err = s.userStore.GetUserByEmailPhone(ctx, resetReq.Email, "") } else if resetReq.PhoneNumber != "" { user, err = s.userStore.GetUserByEmailPhone(ctx, "", resetReq.PhoneNumber) } else { return errors.New("email or phone number is required") } if err != nil { return err } // Get OTP for the actual user otp, err := s.otpStore.GetOtp(ctx, user.ID) if err != nil { return err } // Validate OTP purpose (should be for reset, not registration) if otp.For != domain.OtpReset { return domain.ErrInvalidOtp } if otp.Used { return domain.ErrOtpAlreadyUsed } if time.Now().After(otp.ExpiresAt) { return domain.ErrOtpExpired } // Use constant-time comparison for OTP if subtle.ConstantTimeCompare([]byte(otp.Otp), []byte(resetReq.OtpCode)) != 1 { return domain.ErrInvalidOtp } // Hash the new password before storing hashedPassword, err := hashPassword(resetReq.Password) if err != nil { return err } // Update password with hashed value err = s.userStore.UpdatePasswordHash(ctx, hashedPassword, user.ID) if err != nil { return err } // Mark OTP as used to prevent replay attacks err = s.otpStore.MarkOtpAsUsed(ctx, otp) if err != nil { return err } return nil }