<?php
declare(strict_types=1);
namespace App\EventSubscriber;
use App\Entity\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Security;
class TwoFactorSetupSubscriber implements EventSubscriberInterface
{
protected $logger;
protected $security;
protected $router;
public function __construct(
LoggerInterface $logger,
Security $security,
RouterInterface $router
) {
$this->logger = $logger;
$this->security = $security;
$this->router = $router;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => [['onKernelRequest', 4]],
];
}
public function onKernelRequest(RequestEvent $event): void
{
$request = $event->getRequest();
$path = $request->getPathInfo();
// Only intercept /app routes — not login, 2fa, logout, setup itself, or API
$allowedPaths = ['/2fa', '/2fa/check', '/2fa/resend', '/logout', '/app/2fa/setup', '/app/security/enable-2fa', '/security_questions', '/reset', '/forgot', '/resetting'];
foreach ($allowedPaths as $allowed) {
if (str_starts_with($path, $allowed)) {
return;
}
}
if (!str_starts_with($path, '/app')) {
return;
}
$user = $this->security->getUser();
if (!$user instanceof User) {
return;
}
// Skip admins
if (in_array(User::ROLE_ADMIN, $user->getRoles()) || in_array(User::ROLE_SUPER_ADMIN, $user->getRoles())) {
return;
}
if (!$user->getEmailAuthEnabled()) {
$this->logger->debug('***** TwoFactorSetupSubscriber: user has no 2FA, redirecting to setup');
$event->setResponse(new RedirectResponse($this->router->generate('app_2fa_setup')));
}
}
}