/** * @param string $email * @param string $previousPassword * @param string $newPassword * @param int $max the maximum number of changes before a password can be re-used * @return bool * @throws UserAuthenticationException * @throws PasswordChangeException */ public function changePassword($email, $previousPassword, $newPassword, $max = UserPasswordChange::MAX_PASSWORD_CHANGES_BEFORE_REUSE) { $user = $this->getUserByEmail($email); if ($user == false) { throw new PasswordChangeException(ErrorMessages::EMAIL_DOES_NOT_EXIST); } //validate password if (!Utils::verifyPassword($previousPassword, $user->password)) { throw new PasswordChangeException(ErrorMessages::OLD_PASSWORD_INVALID); } //check if new password matches user's current password if (Utils::verifyPassword($newPassword, $user->password)) { throw new PasswordChangeException("You cannot use any of your last {$max} passwords"); } //check user's status $this->validateStatus($user->status); //Validate new password UserPasswordChange::validateNewPassword((int) $user->id, $newPassword, $max); //if all goes well, proceed to update the password return $this->updatePassword((int) $user->id, $newPassword); }
/** * check if the new password does not correspond to the previous max passwords * We use max-1 in the query because we are assuming that the user's current password is * inclusive of the last max passwords used and this has already been checked above * * @param int $userId * @param string $newPassword * @param int $max * @throws PasswordChangeException */ public static function validateNewPassword($userId, $newPassword, $max = self::MAX_PASSWORD_CHANGES_BEFORE_REUSE) { $recentPasswords = UserPasswordChange::query()->where("user_id = :user_id:")->bind(["user_id" => $userId])->orderBy("date_changed DESC")->limit($max - 1)->execute()->toArray(); foreach ($recentPasswords as $aRecentPassword) { if (Utils::verifyPassword($newPassword, $aRecentPassword['password_hash'])) { throw new PasswordChangeException("You cannot use any of your last {$max} passwords"); } } }