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);
}
}