* @category Class * @license https://www.gnu.org/licenses/lgpl-3.0.en.html GNU Lesser General Public License * @link www.splendidbear.org * @since 2026. 04. 11. */ #[AsController] class SecurityController extends AbstractController { #[Route('/login', name: 'MineSeekerBundle_login')] public function login(AuthenticationUtils $authenticationUtils): Response { if ($this->getUser()) { return $this->redirectToRoute('MineSeekerBundle_homepage'); } return $this->render('Security/login.html.twig', [ 'last_username' => $authenticationUtils->getLastUsername(), 'error' => $authenticationUtils->getLastAuthenticationError(), ]); } #[Route('/logout', name: 'MineSeekerBundle_logout', methods: ['POST'])] public function logout(): never { throw new \LogicException('This action is intercepted by the security firewall.'); } #[Route('/register', name: 'MineSeekerBundle_register')] public function register( Request $request, UserPasswordHasherInterface $hasher, EntityManagerInterface $em, MailerInterface $mailer, ): Response { if ($this->getUser()) { return $this->redirectToRoute('MineSeekerBundle_homepage'); } $user = new User(); $form = $this->createForm(RegistrationFormType::class, $user); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $token = bin2hex(random_bytes(32)); $user ->setIsVerified(false) ->setVerificationToken($token) ->setPassword($hasher->hashPassword($user, $form->get('plainPassword')->getData())); $em->persist($user); $em->flush(); $activationUrl = $this->generateUrl( 'MineSeekerBundle_activate', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL, ); $mailer->send( new TemplatedEmail() ->from('noreply@mineseeker.hu') ->to($user->getEmail()) ->subject('Activate your MineSeeker account') ->htmlTemplate('emails/activation.html.twig') ->context([ 'username' => $user->getUsername(), 'activation_url' => $activationUrl, ]) ); $this->addFlash('verify_email', $user->getEmail()); return $this->redirectToRoute('MineSeekerBundle_register'); } return $this->render('Security/register.html.twig', ['form' => $form]); } #[Route('/forgot-password', name: 'MineSeekerBundle_forgot_password')] public function forgotPassword( Request $request, UserRepository $userRepository, EntityManagerInterface $em, MailerInterface $mailer, ): Response { if ($this->getUser()) { return $this->redirectToRoute('MineSeekerBundle_homepage'); } $form = $this->createForm(ForgotPasswordFormType::class); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $email = $form->get('email')->getData(); $user = $userRepository->findOneByEmail($email); if ($user && $user->isVerified()) { $token = bin2hex(random_bytes(32)); $user ->setResetToken($token) ->setResetTokenExpiresAt(new DateTime('+1 hour')); $em->flush(); $resetUrl = $this->generateUrl( 'MineSeekerBundle_reset_password', ['token' => $token], UrlGeneratorInterface::ABSOLUTE_URL, ); $mailer->send( new TemplatedEmail() ->from('noreply@mineseeker.hu') ->to($email) ->subject('Reset your MineSeeker password') ->htmlTemplate('emails/reset_password.html.twig') ->context([ 'username' => $user->getUsername(), 'reset_url' => $resetUrl, ]) ); } // Always show the same flash to prevent email enumeration $this->addFlash('reset_sent', $email); return $this->redirectToRoute('MineSeekerBundle_forgot_password'); } return $this->render('Security/forgot_password.html.twig', ['form' => $form]); } #[Route('/reset-password/{token}', name: 'MineSeekerBundle_reset_password')] public function resetPassword( string $token, Request $request, UserRepository $userRepository, EntityManagerInterface $em, UserPasswordHasherInterface $hasher, ): Response { $user = $userRepository->findOneByResetToken($token); if (!$user || $user->getResetTokenExpiresAt() < new DateTime()) { $this->addFlash('error', 'This password reset link is invalid or has expired.'); return $this->redirectToRoute('MineSeekerBundle_forgot_password'); } $form = $this->createForm(ResetPasswordFormType::class); $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { $user ->setPassword($hasher->hashPassword($user, $form->get('plainPassword')->getData())) ->setResetToken(null) ->setResetTokenExpiresAt(null); $em->flush(); $this->addFlash('success', 'Your password has been reset. You can now sign in.'); return $this->redirectToRoute('MineSeekerBundle_login'); } return $this->render('Security/reset_password.html.twig', ['form' => $form]); } #[Route('/activate/{token}', name: 'MineSeekerBundle_activate')] public function activate(string $token, EntityManagerInterface $em): Response { $user = $em->getRepository(User::class)->findOneBy(['verificationToken' => $token]); if (!$user) { $this->addFlash('error', 'This activation link is invalid or has already been used.'); return $this->redirectToRoute('MineSeekerBundle_login'); } $user->setIsVerified(true)->setVerificationToken(null); $em->flush(); $this->addFlash('success', 'Your account is now active. Welcome, ' . $user->getUsername() . '!'); return $this->redirectToRoute('MineSeekerBundle_login'); } }