src/Controller/ResetPasswordController.php line 52

Open in your IDE?
  1. <?php
  2. namespace MedBrief\MSR\Controller;
  3. use Doctrine\ORM\EntityManagerInterface;
  4. use MedBrief\MSR\Entity\User;
  5. use MedBrief\MSR\Form\ChangePasswordFormType;
  6. use MedBrief\MSR\Form\ResetPasswordRequestFormType;
  7. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
  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\Mailer\Exception\TransportExceptionInterface;
  14. use Symfony\Component\Mailer\MailerInterface;
  15. use Symfony\Component\Mime\Address;
  16. use Symfony\Component\Routing\Annotation\Route;
  17. use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
  18. use Symfony\Contracts\Translation\TranslatorInterface;
  19. use SymfonyCasts\Bundle\ResetPassword\Controller\ResetPasswordControllerTrait;
  20. use SymfonyCasts\Bundle\ResetPassword\Exception\ResetPasswordExceptionInterface;
  21. use SymfonyCasts\Bundle\ResetPassword\Model\ResetPasswordToken;
  22. use SymfonyCasts\Bundle\ResetPassword\ResetPasswordHelperInterface;
  23. /**
  24. * @Route("/reset-password")
  25. */
  26. class ResetPasswordController extends AbstractController
  27. {
  28. use ResetPasswordControllerTrait;
  29. public function __construct(private ResetPasswordHelperInterface $resetPasswordHelper, private EntityManagerInterface $entityManager)
  30. {
  31. }
  32. /**
  33. * Display & process form to request a password reset.
  34. *
  35. * @Route("", name="forgot_password_request")
  36. *
  37. * @Template("reset_password/request.html.twig")
  38. *
  39. * @param Request $request
  40. * @param MailerInterface $mailer
  41. * @param TranslatorInterface $translator
  42. *
  43. * @throws TransportExceptionInterface
  44. *
  45. * @return Response|array
  46. */
  47. public function request(Request $request, MailerInterface $mailer, TranslatorInterface $translator): RedirectResponse|array
  48. {
  49. $form = $this->createForm(ResetPasswordRequestFormType::class);
  50. $form->handleRequest($request);
  51. if ($form->isSubmitted() && $form->isValid()) {
  52. return $this->processSendingPasswordResetEmail(
  53. $form->get('email')->getData(),
  54. $mailer
  55. );
  56. }
  57. return [
  58. 'requestForm' => $form->createView(),
  59. ];
  60. }
  61. /**
  62. * Confirmation page after a user has requested a password reset.
  63. *
  64. * @Route("/check-email", name="check_email")
  65. *
  66. * @Template("reset_password/check_email.html.twig")
  67. */
  68. public function checkEmail(): array
  69. {
  70. // Generate a fake token if the user does not exist or someone hit this page directly.
  71. // This prevents exposing whether a user was found with the given email address or not
  72. if (!($resetToken = $this->getTokenObjectFromSession()) instanceof ResetPasswordToken) {
  73. $resetToken = $this->resetPasswordHelper->generateFakeResetToken();
  74. }
  75. return [
  76. 'resetToken' => $resetToken,
  77. ];
  78. }
  79. /**
  80. * Validates and process the reset URL that the user clicked in their email.
  81. *
  82. * @Route("/reset/{token}", name="reset_password")
  83. *
  84. * @Template("reset_password/reset.html.twig")
  85. *
  86. * @param Request $request
  87. * @param UserPasswordEncoderInterface $userPasswordEncoder
  88. * @param TranslatorInterface $translator
  89. * @param ?string $token
  90. *
  91. * @return Response|array
  92. */
  93. public function reset(Request $request, UserPasswordEncoderInterface $userPasswordEncoder, TranslatorInterface $translator, ?string $token = null): RedirectResponse|array
  94. {
  95. if ($token) {
  96. // We store the token in session and remove it from the URL, to avoid the URL being
  97. // loaded in a browser and potentially leaking the token to 3rd party JavaScript.
  98. $this->storeTokenInSession($token);
  99. return $this->redirectToRoute('reset_password');
  100. }
  101. $token = $this->getTokenFromSession();
  102. if ($token === null) {
  103. throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
  104. }
  105. try {
  106. /** @var User $user */
  107. $user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
  108. } catch (ResetPasswordExceptionInterface $e) {
  109. $this->addFlash('reset_password_error', sprintf(
  110. '%s - %s',
  111. $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, [], 'ResetPasswordBundle'),
  112. $translator->trans($e->getReason(), [], 'ResetPasswordBundle')
  113. ));
  114. return $this->redirectToRoute('forgot_password_request');
  115. }
  116. // The token is valid; allow the user to change their password.
  117. $form = $this->createForm(ChangePasswordFormType::class);
  118. $form->handleRequest($request);
  119. if ($form->isSubmitted() && $form->isValid()) {
  120. // A password reset token should be used only once, remove it.
  121. $this->resetPasswordHelper->removeResetRequest($token);
  122. // Encode(hash) the plain password, and set it.
  123. $encodedPassword = $userPasswordEncoder->encodePassword(
  124. $user,
  125. $form->get('plainPassword')->getData()
  126. );
  127. $this->addFlash('success', 'Your password has been successfully reset.');
  128. $user->setPassword($encodedPassword);
  129. $user->setEnabled(true);
  130. $this->entityManager->flush();
  131. // The session is cleaned up after the password has been changed.
  132. $this->cleanSessionAfterReset();
  133. return $this->redirectToRoute('login');
  134. }
  135. return [
  136. 'resetForm' => $form->createView(),
  137. ];
  138. }
  139. /**
  140. *
  141. *
  142. *
  143. * @param string $emailFormData
  144. * @param MailerInterface $mailer
  145. *
  146. * @throws TransportExceptionInterface
  147. */
  148. private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer): RedirectResponse
  149. {
  150. $user = $this->entityManager->getRepository(User::class)->findOneBy([
  151. 'email' => $emailFormData,
  152. ]);
  153. // Do not reveal whether a user account was found or not.
  154. if (!$user) {
  155. return $this->redirectToRoute('check_email');
  156. }
  157. try {
  158. $resetToken = $this->resetPasswordHelper->generateResetToken($user);
  159. } catch (ResetPasswordExceptionInterface) {
  160. // If you want to tell the user why a reset email was not sent, uncomment
  161. // the lines below and change the redirect to 'forgot_password_request'.
  162. // Caution: This may reveal if a user is registered or not.
  163. //
  164. // $this->addFlash('reset_password_error', sprintf(
  165. // '%s - %s',
  166. // $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE, [], 'ResetPasswordBundle'),
  167. // $translator->trans($e->getReason(), [], 'ResetPasswordBundle')
  168. // ));
  169. return $this->redirectToRoute('check_email');
  170. }
  171. $email = (new TemplatedEmail())
  172. ->from(new Address($this->getParameter('mailer_from_address'), $this->getParameter('mailer_from_name')))
  173. ->to($user->getEmail())
  174. ->subject('Your password reset request')
  175. ->htmlTemplate('reset_password/email.html.twig')
  176. ->context([
  177. 'resetToken' => $resetToken,
  178. 'user' => $user,
  179. ])
  180. ;
  181. $mailer->send($email);
  182. // Store the token object in session for retrieval in check-email route.
  183. $this->setTokenObjectInSession($resetToken);
  184. return $this->redirectToRoute('check_email');
  185. }
  186. }