vendor/shopware/storefront/Controller/AuthController.php line 79

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Storefront\Controller;
  3. use Shopware\Core\Checkout\Customer\Exception\BadCredentialsException;
  4. use Shopware\Core\Checkout\Customer\Exception\CustomerAuthThrottledException;
  5. use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundByHashException;
  6. use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundByIdException;
  7. use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundException;
  8. use Shopware\Core\Checkout\Customer\Exception\CustomerOptinNotCompletedException;
  9. use Shopware\Core\Checkout\Customer\Exception\CustomerRecoveryHashExpiredException;
  10. use Shopware\Core\Checkout\Customer\Exception\InvalidImitateCustomerTokenException;
  11. use Shopware\Core\Checkout\Customer\Exception\PasswordPoliciesUpdatedException;
  12. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractImitateCustomerRoute;
  13. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLoginRoute;
  14. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
  15. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractResetPasswordRoute;
  16. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractSendPasswordRecoveryMailRoute;
  17. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  18. use Shopware\Core\Framework\Log\Package;
  19. use Shopware\Core\Framework\RateLimiter\Exception\RateLimitExceededException;
  20. use Shopware\Core\Framework\Routing\RoutingException;
  21. use Shopware\Core\Framework\Validation\DataBag\DataBag;
  22. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  23. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  24. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  25. use Shopware\Storefront\Checkout\Cart\SalesChannel\StorefrontCartFacade;
  26. use Shopware\Storefront\Framework\Routing\RequestTransformer;
  27. use Shopware\Storefront\Page\Account\Login\AccountGuestLoginPageLoadedHook;
  28. use Shopware\Storefront\Page\Account\Login\AccountLoginPageLoadedHook;
  29. use Shopware\Storefront\Page\Account\Login\AccountLoginPageLoader;
  30. use Shopware\Storefront\Page\Account\RecoverPassword\AccountRecoverPasswordPageLoadedHook;
  31. use Shopware\Storefront\Page\Account\RecoverPassword\AccountRecoverPasswordPageLoader;
  32. use Symfony\Component\HttpFoundation\Request;
  33. use Symfony\Component\HttpFoundation\Response;
  34. use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
  35. use Symfony\Component\Routing\Attribute\Route;
  36. /**
  37.  * @internal
  38.  * Do not use direct or indirect repository calls in a controller. Always use a store-api route to get or put data
  39.  */
  40. #[Route(defaults: ['_routeScope' => ['storefront']])]
  41. #[Package('storefront')]
  42. class AuthController extends StorefrontController
  43. {
  44.     /**
  45.      * @internal
  46.      */
  47.     public function __construct(
  48.         private readonly AccountLoginPageLoader $loginPageLoader,
  49.         private readonly AbstractSendPasswordRecoveryMailRoute $sendPasswordRecoveryMailRoute,
  50.         private readonly AbstractResetPasswordRoute $resetPasswordRoute,
  51.         private readonly AbstractLoginRoute $loginRoute,
  52.         private readonly AbstractLogoutRoute $logoutRoute,
  53.         private readonly AbstractImitateCustomerRoute $imitateCustomerRoute,
  54.         private readonly StorefrontCartFacade $cartFacade,
  55.         private readonly AccountRecoverPasswordPageLoader $recoverPasswordPageLoader
  56.     ) {
  57.     }
  58.     #[Route(path'/account/login'name'frontend.account.login.page'defaults: ['_noStore' => true], methods: ['GET'])]
  59.     public function loginPage(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  60.     {
  61.         /** @var string $redirect */
  62.         $redirect $request->get('redirectTo''frontend.account.home.page');
  63.         $customer $context->getCustomer();
  64.         if ($customer !== null && $customer->getGuest() === false) {
  65.             $request->request->set('redirectTo'$redirect);
  66.             return $this->createActionResponse($request);
  67.         }
  68.         $page $this->loginPageLoader->load($request$context);
  69.         $this->hook(new AccountLoginPageLoadedHook($page$context));
  70.         return $this->renderStorefront('@Storefront/storefront/page/account/register/index.html.twig', [
  71.             'redirectTo' => $redirect,
  72.             'redirectParameters' => $request->get('redirectParameters'json_encode([])),
  73.             'errorRoute' => $request->attributes->get('_route'),
  74.             'page' => $page,
  75.             'loginError' => (bool) $request->get('loginError'),
  76.             'waitTime' => $request->get('waitTime'),
  77.             'errorSnippet' => $request->get('errorSnippet'),
  78.             'data' => $data,
  79.         ]);
  80.     }
  81.     #[Route(path'/account/guest/login'name'frontend.account.guest.login.page'defaults: ['_noStore' => true], methods: ['GET'])]
  82.     public function guestLoginPage(Request $requestSalesChannelContext $context): Response
  83.     {
  84.         /** @var string|null $redirect */
  85.         $redirect $request->get('redirectTo');
  86.         if (!$redirect) {
  87.             // page was probably called directly
  88.             $this->addFlash(self::DANGER$this->trans('account.orderGuestLoginWrongCredentials'));
  89.             return $this->redirectToRoute('frontend.account.login.page');
  90.         }
  91.         $customer $context->getCustomer();
  92.         if ($customer !== null) {
  93.             $request->request->set('redirectTo'$redirect);
  94.             return $this->createActionResponse($request);
  95.         }
  96.         $waitTime = (int) $request->get('waitTime');
  97.         if ($waitTime) {
  98.             $this->addFlash(self::INFO$this->trans('account.loginThrottled', ['%seconds%' => $waitTime]));
  99.         }
  100.         if ((bool) $request->get('loginError')) {
  101.             $this->addFlash(self::DANGER$this->trans('account.orderGuestLoginWrongCredentials'));
  102.         }
  103.         $page $this->loginPageLoader->load($request$context);
  104.         $this->hook(new AccountGuestLoginPageLoadedHook($page$context));
  105.         return $this->renderStorefront('@Storefront/storefront/page/account/guest-auth.html.twig', [
  106.             'redirectTo' => $redirect,
  107.             'redirectParameters' => $request->get('redirectParameters'json_encode([])),
  108.             'page' => $page,
  109.         ]);
  110.     }
  111.     #[Route(path'/account/logout'name'frontend.account.logout.page'methods: ['GET'])]
  112.     public function logout(Request $requestSalesChannelContext $contextRequestDataBag $dataBag): Response
  113.     {
  114.         if ($context->getCustomer() === null) {
  115.             return $this->redirectToRoute('frontend.account.login.page');
  116.         }
  117.         try {
  118.             $this->logoutRoute->logout($context$dataBag);
  119.             $this->addFlash(self::SUCCESS$this->trans('account.logoutSucceeded'));
  120.             $parameters = [];
  121.         } catch (ConstraintViolationException $formViolations) {
  122.             $parameters = ['formViolations' => $formViolations];
  123.         }
  124.         return $this->redirectToRoute('frontend.account.login.page'$parameters);
  125.     }
  126.     #[Route(path'/account/login'name'frontend.account.login'defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
  127.     public function login(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  128.     {
  129.         $customer $context->getCustomer();
  130.         if ($customer !== null && $customer->getGuest() === false) {
  131.             return $this->createActionResponse($request);
  132.         }
  133.         try {
  134.             $token $this->loginRoute->login($data$context)->getToken();
  135.             $cartBeforeNewContext $this->cartFacade->get($token$context);
  136.             if (!empty($token)) {
  137.                 $this->addCartErrors($cartBeforeNewContext);
  138.                 return $this->createActionResponse($request);
  139.             }
  140.         } catch (CustomerOptinNotCompletedException $e) {
  141.             $errorSnippet $e->getSnippetKey();
  142.         } catch (CustomerAuthThrottledException $e) {
  143.             $waitTime $e->getWaitTime();
  144.             // @deprecated tag:v6.7.0 - Remove catch for UnauthorizedHttpException
  145.         } catch (BadCredentialsException|CustomerNotFoundException|UnauthorizedHttpException) {
  146.         } catch (PasswordPoliciesUpdatedException $e) {
  147.             $this->addFlash(self::WARNING$this->trans('account.passwordPoliciesUpdated'));
  148.             return $this->forwardToRoute('frontend.account.recover.page');
  149.         } finally {
  150.             $data->set('password'null);
  151.         }
  152.         return $this->forwardToRoute(
  153.             'frontend.account.login.page',
  154.             [
  155.                 'loginError' => true,
  156.                 'errorSnippet' => $errorSnippet ?? null,
  157.                 'waitTime' => $waitTime ?? null,
  158.             ]
  159.         );
  160.     }
  161.     #[Route(path'/account/recover'name'frontend.account.recover.page'methods: ['GET'])]
  162.     public function recoverAccountForm(Request $requestSalesChannelContext $context): Response
  163.     {
  164.         $page $this->loginPageLoader->load($request$context);
  165.         return $this->renderStorefront('@Storefront/storefront/page/account/profile/recover-password.html.twig', [
  166.             'page' => $page,
  167.         ]);
  168.     }
  169.     #[Route(path'/account/recover'name'frontend.account.recover.request'methods: ['POST'])]
  170.     public function generateAccountRecovery(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  171.     {
  172.         try {
  173.             $mailData $data->get('email');
  174.             if (!$mailData instanceof DataBag) {
  175.                 throw RoutingException::invalidRequestParameter('email');
  176.             }
  177.             $mailData->set('storefrontUrl'$request->attributes->get(RequestTransformer::STOREFRONT_URL));
  178.             $this->sendPasswordRecoveryMailRoute->sendRecoveryMail(
  179.                 $mailData->toRequestDataBag(),
  180.                 $context,
  181.                 false
  182.             );
  183.             $this->addFlash(self::SUCCESS$this->trans('account.recoveryMailSend'));
  184.         } catch (CustomerNotFoundException $e) {
  185.             $this->addFlash(self::SUCCESS$this->trans('account.recoveryMailSend'));
  186.         } catch (InconsistentCriteriaIdsException $e) {
  187.             $this->addFlash(self::DANGER$this->trans('error.message-default'));
  188.         } catch (RateLimitExceededException $e) {
  189.             $this->addFlash(self::INFO$this->trans('error.rateLimitExceeded', ['%seconds%' => $e->getWaitTime()]));
  190.         } catch (ConstraintViolationException $formViolations) {
  191.             return $this->forwardToRoute(
  192.                 'frontend.account.recover.page',
  193.                 ['formViolations' => $formViolations]
  194.             );
  195.         }
  196.         return $this->redirectToRoute('frontend.account.recover.page');
  197.     }
  198.     #[Route(path'/account/recover/password'name'frontend.account.recover.password.page'methods: ['GET'])]
  199.     public function resetPasswordForm(Request $requestSalesChannelContext $context): Response
  200.     {
  201.         /** @var ?string $hash */
  202.         $hash $request->get('hash');
  203.         if (!$hash || !\is_string($hash)) {
  204.             $this->addFlash(self::DANGER$this->trans('account.passwordHashNotFound'));
  205.             return $this->redirectToRoute('frontend.account.recover.request');
  206.         }
  207.         try {
  208.             $page $this->recoverPasswordPageLoader->load($request$context$hash);
  209.         } catch (ConstraintViolationException|CustomerNotFoundByHashException) {
  210.             $this->addFlash(self::DANGER$this->trans('account.passwordHashNotFound'));
  211.             return $this->redirectToRoute('frontend.account.recover.request');
  212.         }
  213.         $this->hook(new AccountRecoverPasswordPageLoadedHook($page$context));
  214.         if ($page->getHash() === null || $page->isHashExpired()) {
  215.             $this->addFlash(self::DANGER$this->trans('account.passwordHashNotFound'));
  216.             return $this->redirectToRoute('frontend.account.recover.request');
  217.         }
  218.         return $this->renderStorefront('@Storefront/storefront/page/account/profile/reset-password.html.twig', [
  219.             'page' => $page,
  220.             'formViolations' => $request->get('formViolations'),
  221.         ]);
  222.     }
  223.     #[Route(path'/account/recover/password'name'frontend.account.recover.password.reset'methods: ['POST'])]
  224.     public function resetPassword(RequestDataBag $dataSalesChannelContext $context): Response
  225.     {
  226.         $passwordData $data->get('password');
  227.         if (!$passwordData instanceof DataBag) {
  228.             throw RoutingException::invalidRequestParameter('password');
  229.         }
  230.         $hash $passwordData->get('hash');
  231.         try {
  232.             $this->resetPasswordRoute->resetPassword($passwordData->toRequestDataBag(), $context);
  233.             $this->addFlash(self::SUCCESS$this->trans('account.passwordChangeSuccess'));
  234.         } catch (ConstraintViolationException $formViolations) {
  235.             if ($formViolations->getViolations('newPassword')->count() === 1) {
  236.                 $this->addFlash(self::DANGER$this->trans('account.passwordNotIdentical'));
  237.             } else {
  238.                 $this->addFlash(self::DANGER$this->trans('account.passwordChangeNoSuccess'));
  239.             }
  240.             return $this->forwardToRoute(
  241.                 'frontend.account.recover.password.page',
  242.                 ['hash' => $hash'formViolations' => $formViolations'passwordFormViolation' => true]
  243.             );
  244.         } catch (CustomerNotFoundByHashException) {
  245.             $this->addFlash(self::DANGER$this->trans('account.passwordChangeNoSuccess'));
  246.             return $this->forwardToRoute('frontend.account.recover.request');
  247.         } catch (CustomerRecoveryHashExpiredException) {
  248.             $this->addFlash(self::DANGER$this->trans('account.passwordHashExpired'));
  249.             return $this->forwardToRoute('frontend.account.recover.request');
  250.         }
  251.         return $this->redirectToRoute('frontend.account.profile.page');
  252.     }
  253.     #[Route(path'/account/login/imitate-customer'name'frontend.account.login.imitate-customer'methods: ['POST'])]
  254.     public function imitateCustomerLogin(RequestDataBag $dataSalesChannelContext $context): Response
  255.     {
  256.         try {
  257.             $this->imitateCustomerRoute->imitateCustomerLogin($data$context);
  258.             return $this->redirectToRoute('frontend.account.home.page');
  259.         } catch (InvalidImitateCustomerTokenException|CustomerNotFoundByIdException) {
  260.             return $this->forwardToRoute(
  261.                 'frontend.account.login.page',
  262.                 [
  263.                     'loginError' => true,
  264.                 ]
  265.             );
  266.         }
  267.     }
  268. }