chillLogger = $chillLogger; $this->passwordEncoder = $passwordEncoder; $this->translator = $translator; $this->tokenManager = $tokenManager; $this->recoverPasswordHelper = $recoverPasswordHelper; $this->eventDispatcher = $eventDispatcher; } /** * @return Response * @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-changed", name="password_request_recover_changed") */ public function changeConfirmedAction() { return $this->render('@ChillMain/Password/recover_password_changed.html.twig'); } /** * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/recover", name="password_recover") */ public function recoverAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response { if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { return new Response($this->translator->trans('You are not allowed ' . 'to try to recover password, due to mitigating possible ' . 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN); } $query = $request->query; $username = $query->get(TokenManager::USERNAME_CANONICAL); $hash = $query->getAlnum(TokenManager::HASH); $token = $query->getAlnum(TokenManager::TOKEN); $timestamp = $query->getAlnum(TokenManager::TIMESTAMP); $user = $this->getDoctrine()->getRepository(User::class) ->findOneByUsernameCanonical($username); if (null === $user) { $this->eventDispatcher->dispatch( new PasswordRecoverEvent($token, null, $request->getClientIp()), PasswordRecoverEvent::INVALID_TOKEN ); throw $this->createNotFoundException(sprintf('User %s not found', $username)); } if (true !== $this->tokenManager->verify($hash, $token, $user, $timestamp)) { $this->eventDispatcher->dispatch( new PasswordRecoverEvent($token, $user, $request->getClientIp()), PasswordRecoverEvent::INVALID_TOKEN ); return new Response('Invalid token', Response::HTTP_FORBIDDEN); } $form = $this->passwordForm($user); $form->remove('actual_password'); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $password = $form->get('new_password')->getData(); $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); // logging for prod $this ->chillLogger ->notice( 'setting new password for user', [ 'user' => $user->getUsername(), ] ); $this->getDoctrine()->getManager()->flush(); return $this->redirectToRoute('password_request_recover_changed'); } return $this->render('@ChillMain/Password/recover_password_form.html.twig', [ 'form' => $form->createView(), ]); } /** * @throws \Doctrine\ORM\NoResultException * @throws \Doctrine\ORM\NonUniqueResultException * * @return Response|\Symfony\Component\HttpFoundation\RedirectResponse * @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-recover", name="password_request_recover") */ public function requestRecoverAction(Request $request): \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response { if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN)) { return new Response($this->translator->trans('You are not allowed ' . 'to try to recover password, due to mitigating possible ' . 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN); } $form = $this->requestRecoverForm(); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { /** @var \Doctrine\ORM\QueryBuilder $qb */ $qb = $this->getDoctrine()->getManager() ->createQueryBuilder(); $qb->select('u') ->from(User::class, 'u') ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))')) ->setParameter('pattern', $form->get('username_or_email')->getData()); $user = $qb->getQuery()->getSingleResult(); if (empty($user->getEmail())) { $this->addFlash('error', $this->translator->trans('This account does not have an email address. ' . 'Please ask your administrator to renew your password.')); } else { if (false === $this->isGranted(PasswordRecoverVoter::ASK_TOKEN, $user)) { return new Response($this->translator->trans('You are not allowed ' . 'to try to recover password, due to mitigating possible ' . 'attack. Try to contact your system administrator'), Response::HTTP_FORBIDDEN); } $this->recoverPasswordHelper->sendRecoverEmail( $user, (new DateTimeImmutable('now'))->add(new DateInterval('PT30M')) ); // logging for prod $this ->chillLogger ->notice( 'Sending an email for password recovering', [ 'user' => $user->getUsername(), ] ); $this->eventDispatcher->dispatch( new PasswordRecoverEvent(null, $user, $request->getClientIp()), PasswordRecoverEvent::ASK_TOKEN_SUCCESS ); return $this->redirectToRoute('password_request_recover_confirm'); } } elseif ($form->isSubmitted() && false === $form->isValid()) { $this->eventDispatcher->dispatch( new PasswordRecoverEvent(null, null, $request->getClientIp()), PasswordRecoverEvent::ASK_TOKEN_INVALID_FORM ); } return $this->render('@ChillMain/Password/request_recover_password.html.twig', [ 'form' => $form->createView(), ]); } /** * @return Response * @\Symfony\Component\Routing\Annotation\Route(path="/public/{_locale}/password/request-confirm", name="password_request_recover_confirm") */ public function requestRecoverConfirmAction() { return $this->render('@ChillMain/Password/request_recover_password_confirm.html.twig'); } /** * @return Response * @Route("/{_locale}/my/password", name="change_my_password") */ public function UserPasswordAction(Request $request) { // get authentified user $user = $this->getUser(); // create a form for password_encoder $form = $this->passwordForm($user); // process the form $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $password = $form->get('new_password')->getData(); // logging for prod $this ->chillLogger ->notice( 'update password for an user', [ 'method' => $request->getMethod(), 'user' => $user->getUsername(), ] ); $user->setPassword($this->passwordEncoder->encodePassword($user, $password)); $em = $this->getDoctrine()->getManager(); $em->flush(); $this->addFlash('success', $this->translator->trans('Password successfully updated!')); return $this->redirectToRoute('change_my_password'); } // render into a template return $this->render('@ChillMain/Password/password.html.twig', [ 'form' => $form->createView(), ]); } /** * @return \Symfony\Component\Form\FormInterface */ protected function requestRecoverForm() { $builder = $this->createFormBuilder(); $builder ->add('username_or_email', TextType::class, [ 'label' => 'Username or email', 'constraints' => [ new Callback([ 'callback' => function ($pattern, ExecutionContextInterface $context, $payload) { $qb = $this->getDoctrine()->getManager() ->createQueryBuilder(); $qb->select('COUNT(u)') ->from(User::class, 'u') ->where($qb->expr()->eq('u.usernameCanonical', 'UNACCENT(LOWER(:pattern))')) ->orWhere($qb->expr()->eq('u.emailCanonical', 'UNACCENT(LOWER(:pattern))')) ->setParameter('pattern', $pattern); if ((int) $qb->getQuery()->getSingleScalarResult() !== 1) { $context->addViolation('This username or email does not exists'); } }, ]), ], ]) ->add('submit', SubmitType::class, [ 'label' => 'Request recover', ]); return $builder->getForm(); } /** * @return \Symfony\Component\Form\Form */ private function passwordForm(User $user) { return $this ->createForm( UserPasswordType::class, [], ['user' => $user] ) ->add('submit', SubmitType::class, ['label' => 'Change password']); } }