<?php
/**
* @author Yenier Jimenez <yjmorales86@gmail.com>
*/
namespace App\Controller\Admin\Security\ChangePassword;
use App\Api\Core\Model\ApiEmptyResponse;
use App\Controller\Core\BaseController;
use App\Entity\User;
use App\Form\Admin\Unauthenticated\User\ChangePasswordFormType;
use Common\Communication\HtmlMailer\Mailer;
use Common\Communication\HtmlMailer\MailerMessage;
use Common\DataManagement\Validator\DataValidator;
use Common\DataStorage\Redis\RedisCacheRegistry;
use Common\Security\AntiSpam\ReCaptcha\v3\Exception\ReCaptchaV3Exception;
use Common\Security\AntiSpam\ReCaptcha\v3\ReCaptchaV3Validator;
use Doctrine\Persistence\ManagerRegistry;
use Exception;
use stdClass;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller to change password.
*
* @Route("/admin/change-password")
*/
class ChangePasswordController extends BaseController
{
/**
* Responsible to render the page to enter the email.
*
* @Route("/enter-email", name="bo_change_password_enter_email")
*
* @return Response
*/
public function enterEmail(): Response
{
return $this->render('/admin/unauthenticated/security/recover_password/change_password_enter_email.html.twig', [
'recaptchaV3SiteKey' => $this->getParameter('operation_cpg_recaptcha_v3_site_key'),
]);
}
/**
* Responsible to generate and send the change password link.
*
* @Route("/send-link", name="bo_change_password_send_link")
*
* @param Request $request
* @param Mailer $mailer
* @param RedisCacheRegistry $cache
* @param RouterInterface $router
* @param ManagerRegistry $doctrine
* @param ReCaptchaV3Validator $reCaptchaV3Validator
*
* @return Response
*
* @throws ReCaptchaV3Exception
* @throws Exception
*/
public function sendLink(
Request $request,
Mailer $mailer,
RedisCacheRegistry $cache,
RouterInterface $router,
ManagerRegistry $doctrine,
ReCaptchaV3Validator $reCaptchaV3Validator
): Response {
$validRecaptcha = $reCaptchaV3Validator->validateByUserAction();
if (!$validRecaptcha) {
throw new Exception('You are a robot.');
}
$valid = $this->validator()->isValidString($email = $request->get('email'));
if (!$valid) {
throw new Exception('The email is invalid.');
}
$ttlMinutes = 15;
$hash = md5(uniqid());
$route = $router->generate('bo_change_password_enter_new_password', ['hash' => $hash]);
$link = $_SERVER['HTTP_ORIGIN'] . $route;
$user = $this->repository($doctrine, User::class)->findOneByEmail($email);
$data = new stdClass();
$data->userId = $user->getId();
$data->link = $link;
$cache->set($hash, $data, $ttlMinutes * 60); // Save hash for 15 minutes.
$title = $this->getParameter('system_name');
$msg = new MailerMessage("$title - Recover password.", $email);
$msg->setContext([
'emailAddress' => $email,
'link' => $data->link,
'ttlMinutes' => $ttlMinutes,
]);
$msg->setHtmlTemplate('/admin/email/security/change_password/change_password_link.html.twig');
if (!$mailer->send($msg)) {
throw new Exception('The link to change the password was not able to be sent.');
}
return $this->buildJsonResponse(new ApiEmptyResponse());
}
/**
* Updates the password.
*
* @Route("/enter-new-password", name="bo_change_password_enter_new_password")
*
* @param Request $request
* @param RedisCacheRegistry $cache
*
* @return JsonResponse
*
* @throws Exception
*/
public function enterNewPassword(Request $request, RedisCacheRegistry $cache): Response
{
$title = $this->getParameter('system_name');
$form = $this->createForm(ChangePasswordFormType::class);
$hash = $request->get('hash');
$noSentHash = !$hash;
$expiredHash = !$cache->get($hash);
return $this->render('/admin/unauthenticated/security/recover_password/change_password_enter_new_password.html.twig',
[
'title' => $title,
'noSentHash' => $noSentHash,
'expiredHash' => $expiredHash,
'form' => $form->createView(),
'recaptchaV3SiteKey' => $this->getParameter('operation_cpg_recaptcha_v3_site_key'),
'hash' => $hash
]);
}
/**
* Updates the password.
*
* @Route("/update", name="bo_change_password_update")
*
* @param Request $request
* @param ManagerRegistry $doctrine
* @param RedisCacheRegistry $cache
* @param UserPasswordHasherInterface $passwordHasher
* @param Mailer $mailer
* @param ReCaptchaV3Validator $reCaptchaV3Validator
*
* @return JsonResponse
*
* @throws ReCaptchaV3Exception
* @throws Exception
*/
public function update(
Request $request,
ManagerRegistry $doctrine,
RedisCacheRegistry $cache,
UserPasswordHasherInterface $passwordHasher,
Mailer $mailer,
ReCaptchaV3Validator $reCaptchaV3Validator
): Response {
$validRecaptcha = $reCaptchaV3Validator->validateByUserAction();
if (!$validRecaptcha) {
throw new Exception('You are a robot.');
}
$hash = $request->get('hash');
if (!$cacheData = $cache->get($hash)) {
$this->notifyError('The token to recover the password is already expired.');
throw new NotFoundHttpException('The hash is invalid');
}
$title = $this->getParameter('system_name');
$form = $this->createForm(ChangePasswordFormType::class);
$form->handleRequest($request);
if (!$form->isSubmitted() || !$form->isValid()) {
$this->notifyError('The password you submitted is invalid.');
return $this->redirectToRoute('bo_change_password_enter_new_password', ['hash' => $hash]);
}
$password = $form->get('password')->getData();
$isValid = $this->validator()->isValidString($password);
if (!$isValid) {
$this->notifyError('The password is invalid.');
return $this->redirectToRoute('bo_change_password_enter_new_password', ['hash' => $hash]);
}
$user = $this->repository($doctrine, User::class)->find($cacheData->userId);
$user->setPassword($passwordHasher->hashPassword($user, $password));
$this->em($doctrine)->persist($user);
$this->em($doctrine)->flush();
$msg = new MailerMessage("$title - Password changed.", $user->getEmail());
$msg->setHtmlTemplate('/admin/email/security/change_password/change_password_notification.html.twig');
$mailer->send($msg);
return $this->redirectToRoute('bo_change_password_success', ['hash' => $hash]);
}
/**
* Renders a page to display that the password has been updated successfully.
*
* @Route("/success/{hash}", name="bo_change_password_success")
* @throws Exception
*/
public function success(Request $request, RedisCacheRegistry $cache): Response
{
if (!$hash = $request->get('hash')) {
return $this->redirectToRoute('bo_login');
}
if (!$cache->get($hash)) {
return $this->redirectToRoute('bo_login');
}
$cache->purge($hash);
return $this->render('/admin/unauthenticated/security/recover_password/changed_password_successfully.html.twig',
[
'recaptchaV3SiteKey' => $this->getParameter('operation_cpg_recaptcha_v3_site_key'),
]);
}
}