Magento 2.2 Bug Preventing Users from Updating Their Passwords, and a Method for Fixing It

Magento 2.2 Bug Preventing Users from Updating Their Passwords, and a Method for Fixing It

5 min read

Send to you:

There is a bug in Magento 2.2, starting with version 2.2.6, which prevents users from resetting their passwords if they forget them. When attempting to reset their passwords, users get an error message saying, “Something went wrong while saving the new password.”

The reason for this error was specified here: https://github.com/magento/magento2/issues/18256. The error was resolved in version 2.3, and it involved the sequence of operators responsible for clearing the session of the resetPassword method of the app/code/Magento/Customer/Model/AccountManagement.php class. People who are still using version 2.2.6 or 2.2.7 can install a patch, but there’s another way to fix this problem. You can fix the bug in the third-party module and in so doing avoid this error. We will examine how to do this.

Eliminating the Bug in the Third-Party Module

To eliminate the bug, we have to replace the app/code/Magento/Customer/Model/AccountManagement.php class in the Magento\Customer\Controller\Account\ResetPasswordPost controller, using a dependence-injection like this:

<type name="Magento\Customer\Controller\Account\ResetPasswordPost">
 <arguments>
 <argument name="accountManagement"
 xsi:type="object">Web4pro\Wholesale\Model\AccountManagement</argument>
 </arguments>
 </type>

It’s not advised to redefine through preference, but since we only need to fix the problem within the limits of one controller call, this method will work. Another difficulty is that the majority of the methods and properties of the original class are private. Because of this, a given class will look like this:

class AccountManagement extends \Magento\Customer\Model\AccountManagement
{
protected $customerRepository;
protected $credentialsValidator;
protected $customerRegistry;
protected $sessionManager;
protected $searchCriteriaBuilder;
protected $scopeConfig;
protected $dateTimeFactory;
protected $visitorCollectionFactory;
protected $saveHandler;
public function __construct(CustomerFactory $customerFactory, ManagerInterface $eventManager,
StoreManagerInterface $storeManager, Random $mathRandom, Validator $validator,
ValidationResultsInterfaceFactory $validationResultsDataFactory, AddressRepositoryInterface
$addressRepository, CustomerMetadataInterface $customerMetadataService, CustomerRegistry
$customerRegistry, PsrLogger $logger, Encryptor $encryptor, ConfigShare $configShare, StringHelper
$stringHelper, CustomerRepositoryInterface $customerRepository, ScopeConfigInterface $scopeConfig,
TransportBuilder $transportBuilder, DataObjectProcessor $dataProcessor, Registry $registry, CustomerViewHelper
$customerViewHelper, DateTime $dateTime, CustomerModel $customerModel, ObjectFactory $objectFactory,
ExtensibleDataObjectConverter $extensibleDataObjectConverter, CredentialsValidator $credentialsValidator =
null, DateTimeFactory $dateTimeFactory = null, AccountConfirmation $accountConfirmation = null,
SessionManagerInterface $sessionManager = null, SaveHandlerInterface $saveHandler = null, CollectionFactory
$visitorCollectionFactory = null, SearchCriteriaBuilder $searchCriteriaBuilder = null)
{
$this->customerRepository = $customerRepository;
$this->customerRegistry = $customerRegistry;
$this->sessionManager = $sessionManager;
$this->searchCriteriaBuilder = $searchCriteriaBuilder?:ObjectManager::getInstance()-
>get(SearchCriteriaBuilder::class);
$this->credentialsValidator = $credentialsValidator?: ObjectManager::getInstance()-
>get(CredentialsValidator::class);
$this->scopeConfig = $scopeConfig;

$this->dateTimeFactory = $dateTimeFactory ?: ObjectManager::getInstance()-
>get(DateTimeFactory::class);
$this->visitorCollectionFactory = $visitorCollectionFactory?:ObjectManager::getInstance()-
>get(CollectionFactory::class);
$this->saveHandler = $saveHandler?: ObjectManager::getInstance()-
>get(SaveHandlerInterface::class);
parent::__construct($customerFactory, $eventManager, $storeManager, $mathRandom, $validator,
$validationResultsDataFactory, $addressRepository, $customerMetadataService, $customerRegistry, $logger,
$encryptor, $configShare, $stringHelper, $customerRepository, $scopeConfig, $transportBuilder, $dataProcessor,
$registry, $customerViewHelper, $dateTime, $customerModel, $objectFactory, $extensibleDataObjectConverter,
$credentialsValidator, $dateTimeFactory, $accountConfirmation, $sessionManager, $saveHandler,
$visitorCollectionFactory, $searchCriteriaBuilder);
}
public function resetPassword($email, $resetToken, $newPassword)
{
if (!$email) {
$customer = $this->matchCustomerByRpToken($resetToken);
$email = $customer->getEmail();
} else {
$customer = $this->customerRepository->get($email);
}
//Validate Token and new password strength
$this->validateResetPasswordToken($customer->getId(), $resetToken);
$this->credentialsValidator->checkPasswordDifferentFromEmail(
$email,
$newPassword
);
$this->checkPasswordStrength($newPassword);
//Update secure data
$customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
$customerSecure->setRpToken(null);
$customerSecure->setRpTokenCreatedAt(null);
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
$this->getAuthentication()->unlock($customer->getId());
$this->destroyCustomerSessions($customer->getId());
$this->sessionManager->destroy();
$this->customerRepository->save($customer);
return true;
}
protected function matchCustomerByRpToken($rpToken)
{
$this->searchCriteriaBuilder->addFilter(
'rp_token',
$rpToken
);
$this->searchCriteriaBuilder->setPageSize(1);
$found = $this->customerRepository->getList(
$this->searchCriteriaBuilder->create()
);
if ($found->getTotalCount() > 1) {
//Failed to generated unique RP token
throw new ExpiredException(
new Phrase('Reset password token expired.')
);
}
if ($found->getTotalCount() === 0) {
//Customer with such token not found.
throw NoSuchEntityException::singleField(
'rp_token',

$rpToken
);
}
//Unique customer found.
return $found->getItems()[0];
}
protected function validateResetPasswordToken($customerId, $resetPasswordLinkToken)
{
if (empty($customerId) || $customerId < 0) { //Looking for the customer. $customerId = $this->matchCustomerByRpToken($resetPasswordLinkToken)
->getId();
}
if (!is_string($resetPasswordLinkToken) || empty($resetPasswordLinkToken)) {
$params = ['fieldName' => 'resetPasswordLinkToken'];
throw new InputException(__('%fieldName is a required field.', $params));
}
$customerSecureData = $this->customerRegistry->retrieveSecureData($customerId);
$rpToken = $customerSecureData->getRpToken();
$rpTokenCreatedAt = $customerSecureData->getRpTokenCreatedAt();
if (!Security::compareStrings($rpToken, $resetPasswordLinkToken)) {
throw new InputMismatchException(__('Reset password token mismatch.'));
} elseif ($this->isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)) {
throw new ExpiredException(__('Reset password token expired.'));
}
return true;
}
/**
* Get authentication
*
* @return AuthenticationInterface
*/
protected function getAuthentication()
{
if (!($this->authentication instanceof \Magento\Customer\Model\AuthenticationInterface)) {
return \Magento\Framework\App\ObjectManager::getInstance()->get(
\Magento\Customer\Model\AuthenticationInterface::class
);
} else {
return $this->authentication;
}
}
protected function destroyCustomerSessions($customerId)
{
$sessionLifetime = $this->scopeConfig->getValue(
\Magento\Framework\Session\Config::XML_PATH_COOKIE_LIFETIME,
\Magento\Store\Model\ScopeInterface::SCOPE_STORE
);
$dateTime = $this->dateTimeFactory->create();
$activeSessionsTime = $dateTime->setTimestamp($dateTime->getTimestamp() - $sessionLifetime)
->format(DateTime::DATETIME_PHP_FORMAT);
/** @var \Magento\Customer\Model\ResourceModel\Visitor\Collection $visitorCollection */
$visitorCollection = $this->visitorCollectionFactory->create();
$visitorCollection->addFieldToFilter('customer_id', $customerId);
$visitorCollection->addFieldToFilter('last_visit_at', ['from' => $activeSessionsTime]);
$visitorCollection->addFieldToFilter('session_id', ['neq' => $this->sessionManager->getSessionId()]);
/** @var \Magento\Customer\Model\Visitor $visitor */
foreach ($visitorCollection->getItems() as $visitor) {

$sessionId = $visitor->getSessionId();
$this->saveHandler->destroy($sessionId);
}
}

SUM MARY

We solved the problem by fixing an error in the Magento Account module. If the methods and attributes had even just appeared protected, we wouldn't have had to redefine them. Technically, this problem can be solved with the around plugin, but we provided you with another method which is also compliant with Magento standards. We hope that this case study will help you solve the password reset problem in earlier versions of Magento 2.

Posted on: August 29, 2019

4.7/5.0

Article rating (15 Reviews)

Do you find this article useful? Please, let us know your opinion and rate the post!

  • Not bad
  • Good
  • Very Good
  • Great
  • Awesome