src/Security/LoginFormAuthenticator.php line 31

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Security;
  4. use App\Enum\BaseRoleEnum;
  5. use App\Exception\UserImpersonateException;
  6. use App\Repository\UserRepository;
  7. use Gesdinet\JWTRefreshTokenBundle\Generator\RefreshTokenGeneratorInterface;
  8. use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenManagerInterface;
  9. use Lexik\Bundle\JWTAuthenticationBundle\Services\JWTTokenManagerInterface;
  10. use Symfony\Component\HttpFoundation\Cookie;
  11. use Symfony\Component\HttpFoundation\RedirectResponse;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  15. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  16. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  17. use Symfony\Component\Security\Core\Security;
  18. use Symfony\Component\Security\Core\User\UserInterface;
  19. use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
  20. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
  21. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\RememberMeBadge;
  22. use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
  23. use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
  24. use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
  25. use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
  26. use Symfony\Component\Security\Http\Util\TargetPathTrait;
  27. final class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
  28. {
  29.     use TargetPathTrait;
  30.     public const LOGIN_ROUTE 'login';
  31.     public function __construct(
  32.         private readonly UrlGeneratorInterface $urlGenerator,
  33.         private readonly UserRepository $userRepository,
  34.         private readonly UserPasswordHasherInterface $passwordHasher,
  35.         private readonly JWTTokenManagerInterface $JWTTokenManager,
  36.         private readonly RefreshTokenGeneratorInterface $refreshTokenGenerator,
  37.         private readonly RefreshTokenManagerInterface $refreshTokenManager
  38.     )
  39.     {
  40.     }
  41.     public function authenticate(Request $request): Passport
  42.     {
  43.         $email $request->request->get('email''');
  44.         $request->getSession()->set(Security::LAST_USERNAME$email);
  45.         if (str_contains($email'::')) {
  46.             if (null !== $selfValidationPassport $this->impersonateUser($request$email)) {
  47.                 return $selfValidationPassport;
  48.             }
  49.         }
  50.         return new Passport(
  51.             new UserBadge($email),
  52.             new PasswordCredentials($request->request->get('password','')),
  53.             [
  54.                 new CsrfTokenBadge('authenticate'$request->get('_csrf_token','')),
  55.                 new RememberMeBadge()
  56.             ]
  57.         );
  58.     }
  59.     public function onAuthenticationSuccess(Request $requestTokenInterface $tokenstring $firewallName): ?Response
  60.     {
  61.         $route $this->urlGenerator->generate('index');
  62.         if ($targetPath $this->getTargetPath($request->getSession(), $firewallName)) {
  63.             $route $targetPath;
  64.         }
  65.         $response = new RedirectResponse($route);
  66.         if (null !== $authImpersonateToken $request->getSession()->get('auth-token-impersonate')) {
  67.             $authToken $authImpersonateToken;
  68.             $request->getSession()->remove('auth-token-impersonate');
  69.         } else {
  70.             $authToken $this->createAuthToken($token->getUser());
  71.         }
  72.         $response->headers->setCookie(new Cookie(
  73.                 'auth-token',
  74.                 $authToken,
  75.                 time() + (86400 30),
  76.                 '/',
  77.                 null,
  78.                 false,
  79.                 false
  80.             )
  81.         );
  82.         if (null !== $authImpersonateToken $request->getSession()->get('auth-refresh-token-impersonate')) {
  83.             $authRefreshToken $authImpersonateToken;
  84.             $request->getSession()->remove('auth-refresh-token-impersonate');
  85.         } else {
  86.             $authRefreshToken $this->createAuthRefreshToken($token->getUser());
  87.         }
  88.         $response->headers->setCookie(new Cookie(
  89.                 'auth-refresh-token',
  90.                 $authRefreshToken,
  91.                 time() + (86400 30),
  92.                 '/',
  93.                 null,
  94.                 false,
  95.                 false
  96.             )
  97.         );
  98.         return $response;
  99.     }
  100.     protected function getLoginUrl(Request $request): string
  101.     {
  102.         return $this->urlGenerator->generate(self::LOGIN_ROUTE);
  103.     }
  104.     private function impersonateUser(Request $requeststring $email): ?SelfValidatingPassport
  105.     {
  106.         $usernames explode('::'$email);
  107.         $adminUsername $usernames[0];
  108.         $admin $this->userRepository->findOneBy(['email' => $adminUsername]);
  109.         if (
  110.             null !== $admin
  111.             &&
  112.             BaseRoleEnum::ROLE_ADMIN === $admin->getRoles()[0]
  113.             &&
  114.             $this->passwordHasher->isPasswordValid(
  115.                 $admin,
  116.                 $request->request->get('password','')
  117.             )
  118.         ) {
  119.             try {
  120.                 $this->setAuthTokensInSession($request$usernames);
  121.                 return new SelfValidatingPassport(new UserBadge($usernames[1]));
  122.             } catch (UserImpersonateException) {
  123.                 return null;
  124.             }
  125.         }
  126.         return null;
  127.     }
  128.     /**
  129.      * @throws UserImpersonateException
  130.      */
  131.     private function setAuthTokensInSession(Request $request, array $usernames): void
  132.     {
  133.         $userToImpersonate $this->userRepository->findOneBy(['email' => $usernames[1]]);
  134.         if (null === $userToImpersonate) {
  135.             throw UserImpersonateException::forUserForImpersonatingDoesNotExist();
  136.         }
  137.         $request->getSession()->set('auth-token-impersonate'$this->createAuthToken($userToImpersonate));
  138.         $request->getSession()->set('auth-refresh-token-impersonate'$this->createAuthRefreshToken($userToImpersonate));
  139.     }
  140.     private function createAuthToken(UserInterface $user): string
  141.     {
  142.         return $this->JWTTokenManager->create($user);
  143.     }
  144.     private function createAuthRefreshToken(UserInterface $user): string
  145.     {
  146.         $refreshToken $this->refreshTokenGenerator->createForUserWithTtl($user86400 30);
  147.         $this->refreshTokenManager->save($refreshToken);
  148.         return $refreshToken->getRefreshToken();
  149.     }
  150. }