src/Infrastructure/Controller/Security/ResetPasswordController.php line 51

Open in your IDE?
  1. <?php
  2. namespace App\Infrastructure\Controller\Security;
  3. use App\Infrastructure\Doctrine\Repository\DoctrineUserRepository;
  4. use App\Infrastructure\Form\ChangePasswordFormType;
  5. use App\Infrastructure\Form\ResetPasswordRequestFormType;
  6. use App\Infrastructure\Service\Mailer;
  7. use Doctrine\ORM\EntityManagerInterface;
  8. use Symfony\Bridge\Twig\Mime\TemplatedEmail;
  9. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  10. use Symfony\Component\HttpFoundation\RedirectResponse;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\Response;
  13. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  14. use Symfony\Component\Routing\Annotation\Route;
  15. use Symfony\Contracts\Translation\TranslatorInterface;
  16. use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
  17. use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
  18. use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
  19. #[Route('/reset-password')]
  20. class ResetPasswordController extends AbstractController
  21. {
  22. use ResetPasswordControllerTrait;
  23. private ResetPasswordHelperInterface $resetPasswordHelper;
  24. private EntityManagerInterface $entityManager;
  25. private DoctrineUserRepository $userRepository;
  26. private Mailer $mailerService;
  27. private TranslatorInterface $translator;
  28. public function __construct(
  29. ResetPasswordHelperInterface $resetPasswordHelper,
  30. EntityManagerInterface $entityManager,
  31. DoctrineUserRepository $userRepository,
  32. Mailer $mailerService,
  33. TranslatorInterface $translator,
  34. ) {
  35. $this->resetPasswordHelper = $resetPasswordHelper;
  36. $this->entityManager = $entityManager;
  37. $this->userRepository = $userRepository;
  38. $this->mailerService = $mailerService;
  39. $this->translator = $translator;
  40. }
  41. /**
  42. * Display & process form to request a password reset.
  43. */
  44. #[Route('', name: 'app_forgot_password_request')]
  45. public function request(Request $request): Response
  46. {
  47. $form = $this->createForm(ResetPasswordRequestFormType::class);
  48. $form->handleRequest($request);
  49. if ($form->isSubmitted() && $form->isValid()) {
  50. return $this->processSendingPasswordResetEmail($form->get('email')->getData());
  51. }
  52. return $this->render('ResetPassword/request.html.twig', [
  53. 'requestForm' => $form->createView(),
  54. ]);
  55. }
  56. /**
  57. * Confirmation page after a user has requested a password reset.
  58. */
  59. #[Route('/check-email', name: 'app_check_email')]
  60. public function checkEmail(): Response
  61. {
  62. // Generate a fake token if the user does not exist or someone hit this page directly.
  63. // This prevents exposing whether or not a user was found with the given email address or not
  64. if (null === ($resetToken = $this->getTokenObjectFromSession())) {
  65. $resetToken = $this->resetPasswordHelper->generateFakeResetToken();
  66. }
  67. return $this->render('ResetPassword/check_email.html.twig', [
  68. 'resetToken' => $resetToken,
  69. ]);
  70. }
  71. /**
  72. * Validates and process the reset URL that the user clicked in their email.
  73. */
  74. #[Route('/reset/{token}', name: 'app_reset_password')]
  75. public function reset(Request $request, UserPasswordHasherInterface $userPasswordHasher, string $token = null): Response
  76. {
  77. if ($token) {
  78. // We store the token in session and remove it from the URL, to avoid the URL being
  79. // loaded in a browser and potentially leaking the token to 3rd party JavaScript.
  80. $this->storeTokenInSession($token);
  81. return $this->redirectToRoute('app_reset_password');
  82. }
  83. $token = $this->getTokenFromSession();
  84. if (null === $token) {
  85. throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
  86. }
  87. try {
  88. $user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
  89. } catch (ResetPasswordExceptionInterface $e) {
  90. $this->addFlash('reset_password_error', sprintf(
  91. 'There was a problem validating your reset request - %s',
  92. $e->getReason()
  93. ));
  94. return $this->redirectToRoute('app_forgot_password_request');
  95. }
  96. // The token is valid; allow the user to change their password.
  97. $form = $this->createForm(ChangePasswordFormType::class);
  98. $form->handleRequest($request);
  99. if ($form->isSubmitted() && $form->isValid()) {
  100. // A password reset token should be used only once, remove it.
  101. $this->resetPasswordHelper->removeResetRequest($token);
  102. // Encode(hash) the plain password, and set it.
  103. $encodedPassword = $userPasswordHasher->hashPassword(
  104. $user,
  105. $form->get('plainPassword')->getData()
  106. );
  107. $user->setPassword($encodedPassword);
  108. $this->entityManager->flush();
  109. // The session is cleaned up after the password has been changed.
  110. $this->cleanSessionAfterReset();
  111. return $this->redirectToRoute('app_login');
  112. }
  113. return $this->render('ResetPassword/reset.html.twig', [
  114. 'resetForm' => $form->createView(),
  115. ]);
  116. }
  117. private function processSendingPasswordResetEmail(string $emailFormData): RedirectResponse
  118. {
  119. $user = $this->userRepository->findOneBy([
  120. 'email' => $emailFormData,
  121. ]);
  122. // Do not reveal whether a user account was found or not.
  123. if (!$user) {
  124. return $this->redirectToRoute('app_check_email');
  125. }
  126. try {
  127. $resetToken = $this->resetPasswordHelper->generateResetToken($user);
  128. } catch (ResetPasswordExceptionInterface $e) {
  129. return $this->redirectToRoute('app_check_email');
  130. }
  131. $email = (new TemplatedEmail())
  132. ->to($user->getEmail())
  133. ->subject($this->translator->trans('email.subject', [], 'reset_password'))
  134. ->htmlTemplate('ResetPassword/email.html.twig')
  135. ->context([
  136. 'resetToken' => $resetToken,
  137. ])
  138. ;
  139. $this->mailerService->send($email);
  140. // Store the token object in session for retrieval in check-email route.
  141. $this->setTokenObjectInSession($resetToken);
  142. return $this->redirectToRoute('app_check_email');
  143. }
  144. }