src/Controller/PasswordResetController.php line 62

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controller;
  4. use App\Entity\User;
  5. use App\Form\ForgotPasswordFormType;
  6. use App\Form\ResetPasswordFormType;
  7. use App\Repository\UserRepository;
  8. use App\Service\EmailSenderService;
  9. use App\Service\PasswordResetRateLimiter;
  10. use App\Service\PasswordResetService;
  11. use Doctrine\ORM\EntityManagerInterface;
  12. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\Routing\Annotation\Route;
  16. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  17. /**
  18.  * Contrôleur pour la réinitialisation de mot de passe
  19.  */
  20. class PasswordResetController extends AbstractController
  21. {
  22.     private $passwordResetService;
  23.     private $userRepository;
  24.     private $emailSenderService;
  25.     private $entityManager;
  26.     private $rateLimiter;
  27.     private $appUrl;
  28.     /**
  29.      * @param PasswordResetService $passwordResetService Service de réinitialisation
  30.      * @param UserRepository $userRepository Repository des utilisateurs
  31.      * @param EmailSenderService $emailSenderService Service d'envoi d'emails
  32.      * @param EntityManagerInterface $entityManager Entity Manager
  33.      * @param PasswordResetRateLimiter $rateLimiter Rate limiter
  34.      * @param string $appUrl URL de l'application
  35.      */
  36.     public function __construct(
  37.         PasswordResetService $passwordResetService,
  38.         UserRepository $userRepository,
  39.         EmailSenderService $emailSenderService,
  40.         EntityManagerInterface $entityManager,
  41.         PasswordResetRateLimiter $rateLimiter,
  42.         string $appUrl
  43.     ) {
  44.         $this->passwordResetService $passwordResetService;
  45.         $this->userRepository $userRepository;
  46.         $this->emailSenderService $emailSenderService;
  47.         $this->entityManager $entityManager;
  48.         $this->rateLimiter $rateLimiter;
  49.         $this->appUrl $appUrl;
  50.     }
  51.     /**
  52.      * Affiche le formulaire de demande de réinitialisation
  53.      * 
  54.      * @Route("/forgot-password", name="app_forgot_password")
  55.      */
  56.     public function forgotPassword(Request $request): Response
  57.     {
  58.         // Si l'utilisateur est déjà connecté, rediriger
  59.         if ($this->getUser()) {
  60.             return $this->redirectToRoute('suggestion.index');
  61.         }
  62.         // Rate limiting par IP
  63.         $clientIp $request->getClientIp();
  64.         if (!$this->rateLimiter->canRequestFromIp($clientIp)) {
  65.             $this->addFlash('error''Trop de demandes depuis votre adresse IP. Veuillez réessayer dans 1 heure.');
  66.             return $this->render('security/forgot_password.html.twig', [
  67.                 'form' => $this->createForm(ForgotPasswordFormType::class)->createView(),
  68.             ]);
  69.         }
  70.         $form $this->createForm(ForgotPasswordFormType::class);
  71.         $form->handleRequest($request);
  72.         if ($form->isSubmitted() && $form->isValid()) {
  73.             $email $form->get('email')->getData();
  74.             
  75.             // Rate limiting par email
  76.             if (!$this->rateLimiter->canRequestForEmail($email)) {
  77.                 $this->addFlash('error''Trop de demandes pour cet email. Veuillez attendre 5 minutes avant de réessayer.');
  78.                 return $this->render('security/forgot_password.html.twig', [
  79.                     'form' => $form->createView(),
  80.                 ]);
  81.             }
  82.             $user $this->userRepository->findOneBy(['email' => $email]);
  83.             // Pour la sécurité, on ne révèle pas si l'email existe ou non
  84.             // On affiche toujours le même message
  85.             if ($user && $user->isIsActive()) {
  86.                 // Invalider les anciens tokens pour cet utilisateur
  87.                 $this->passwordResetService->invalidateExistingTokens($user);
  88.                 
  89.                 // Générer un nouveau token
  90.                 $token $this->passwordResetService->generatePasswordResetToken($user);
  91.                 
  92.                 // Générer l'URL de réinitialisation
  93.                 $resetUrl $this->generateUrl(
  94.                     'app_reset_password',
  95.                     ['token' => $token],
  96.                     UrlGeneratorInterface::ABSOLUTE_URL
  97.                 );
  98.                 // Envoyer l'email
  99.                 try {
  100.                     $this->emailSenderService->sendTemplatedEmail(
  101.                         $user->getEmail(),
  102.                         'Réinitialisation de votre mot de passe bambboo',
  103.                         'emails/password_reset.html.twig',
  104.                         [
  105.                             'user' => $user,
  106.                             'resetUrl' => $resetUrl,
  107.                             'token' => $token,
  108.                         ]
  109.                     );
  110.                 } catch (\Exception $e) {
  111.                     // Ne pas révéler l'erreur à l'utilisateur
  112.                     // Logger l'erreur pour investigation
  113.                 }
  114.             }
  115.             // Toujours afficher le même message pour ne pas révéler si l'email existe
  116.             $this->addFlash('success''Si cet email existe dans notre système, vous recevrez un lien de réinitialisation.');
  117.             
  118.             return $this->redirectToRoute('app_login');
  119.         }
  120.         return $this->render('security/forgot_password.html.twig', [
  121.             'form' => $form->createView(),
  122.         ]);
  123.     }
  124.     /**
  125.      * Affiche le formulaire de réinitialisation avec le token
  126.      * 
  127.      * @Route("/reset-password/{token}", name="app_reset_password")
  128.      */
  129.     public function resetPassword(Request $requeststring $token): Response
  130.     {
  131.         // Si l'utilisateur est déjà connecté, rediriger
  132.         if ($this->getUser()) {
  133.             return $this->redirectToRoute('suggestion.index');
  134.         }
  135.         // Rate limiting sur les tentatives de validation
  136.         if (!$this->rateLimiter->canValidateToken($token)) {
  137.             $this->addFlash('error''Trop de tentatives avec ce lien. Veuillez demander un nouveau lien de réinitialisation.');
  138.             return $this->redirectToRoute('app_forgot_password');
  139.         }
  140.         // Valider le token
  141.         $user $this->passwordResetService->validatePasswordResetToken($token);
  142.         if (!$user) {
  143.             $this->addFlash('error''Ce lien de réinitialisation est invalide ou a expiré.');
  144.             return $this->redirectToRoute('app_forgot_password');
  145.         }
  146.         $form $this->createForm(ResetPasswordFormType::class);
  147.         $form->handleRequest($request);
  148.         if ($form->isSubmitted() && $form->isValid()) {
  149.             $newPassword $form->get('plainPassword')->getData();
  150.             
  151.             // Réinitialiser le mot de passe
  152.             $this->passwordResetService->resetPassword($user$newPassword);
  153.             
  154.             // Réinitialiser les compteurs de rate limiting pour cet email
  155.             $this->rateLimiter->resetEmailCounter($user->getEmail());
  156.             $this->addFlash('success''Votre mot de passe a été réinitialisé avec succès. Vous pouvez maintenant vous connecter.');
  157.             
  158.             return $this->redirectToRoute('app_login');
  159.         }
  160.         return $this->render('security/reset_password.html.twig', [
  161.             'form' => $form->createView(),
  162.             'token' => $token,
  163.         ]);
  164.     }
  165. }