configuration->isPasswordResetActive()) { throw $this->createNotFoundException(); } if ($this->isGranted('IS_AUTHENTICATED')) { return $this->redirectToRoute('homepage'); } return $this->render('security/password-reset/request.html.twig'); } /** * Request reset user password: submit form and send email. */ #[Route(path: '/send-email', name: 'resetting_send_email', methods: ['POST'])] public function sendEmailAction( Request $request, TranslatorInterface $translator, CsrfTokenManagerInterface $csrfTokenManager, LoginLinkHandlerInterface $loginLinkHandler, RateLimiterFactory $resetPasswordLimiter ): Response { if (!$this->configuration->isPasswordResetActive()) { throw $this->createNotFoundException(); } if ($this->isGranted('IS_AUTHENTICATED')) { return $this->redirectToRoute('homepage'); } $limiter = $resetPasswordLimiter->create($request->getClientIp()); $limit = $limiter->consume(); if (!$limit->isAccepted()) { return new Response(null, Response::HTTP_TOO_MANY_REQUESTS); } $username = $request->request->get('username'); $token = $request->request->get('_csrf_token'); try { $user = null; if (\is_string($token) && $csrfTokenManager->isTokenValid(new CsrfToken(self::CSRF_TOKEN, $token))) { if (\is_string($username) && $username !== '') { $user = $this->userService->findUserByUsernameOrEmail($username); } } $csrfTokenManager->refreshToken(self::CSRF_TOKEN); // do not leak the information that this user is not registered OR cannot use this type of login if ($user === null || !$user->isInternalUser()) { return $this->redirectToRoute('resetting_check_email'); } if (!$user->isPasswordRequestNonExpired($this->configuration->getPasswordResetRetryLifetime())) { $loginLinkDetails = $loginLinkHandler->createLoginLink($user, $request, $this->configuration->getPasswordResetRetryLifetime()); // @phpstan-ignore-line $loginLink = $loginLinkDetails->getUrl(); $mail = $this->generateResettingEmailMessage($user, $translator, $loginLink); $event = new EmailPasswordResetEvent($user, $mail); $this->eventDispatcher->dispatch($event); // this will send the email $this->eventDispatcher->dispatch(new EmailEvent($event->getEmail())); $user->markPasswordRequested(); $user->setRequiresPasswordReset(true); $this->userService->saveUser($user); } } catch (\Exception $ex) { // this is an expected exception: do not log this attempt } return $this->redirectToRoute('resetting_check_email'); } /** * Tell the user to check his emails. */ #[Route(path: '/check-email', name: 'resetting_check_email', methods: ['GET'])] public function checkEmailAction(): Response { if (!$this->configuration->isPasswordResetActive()) { throw $this->createNotFoundException(); } return $this->render('security/password-reset/check_email.html.twig', [ 'tokenLifetime' => $this->configuration->getPasswordResetRetryLifetime(), ]); } private function generateResettingEmailMessage(User $user, TranslatorInterface $translator, string $url): Email { $username = $user->getDisplayName(); $language = $user->getLanguage(); return (new TemplatedEmail()) ->locale($language) ->to(new Address($user->getEmail())) ->subject( $translator->trans('reset.subject', ['%username%' => $username], 'email', $language) ) ->htmlTemplate('emails/password-reset.html.twig') ->context([ 'user' => $user, 'username' => $username, 'confirmationUrl' => $url, ]) ; } }