src/Storefront/Controller/AuthController.php line 191

  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\CustomerNotFoundException;
  7. use Shopware\Core\Checkout\Customer\Exception\CustomerOptinNotCompletedException;
  8. use Shopware\Core\Checkout\Customer\Exception\CustomerRecoveryHashExpiredException;
  9. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLoginRoute;
  10. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
  11. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractResetPasswordRoute;
  12. use Shopware\Core\Checkout\Customer\SalesChannel\AbstractSendPasswordRecoveryMailRoute;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Exception\InconsistentCriteriaIdsException;
  14. use Shopware\Core\Framework\Log\Package;
  15. use Shopware\Core\Framework\RateLimiter\Exception\RateLimitExceededException;
  16. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  17. use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
  18. use Shopware\Core\PlatformRequest;
  19. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceInterface;
  20. use Shopware\Core\System\SalesChannel\Context\SalesChannelContextServiceParameters;
  21. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  22. use Shopware\Storefront\Checkout\Cart\SalesChannel\StorefrontCartFacade;
  23. use Shopware\Storefront\Framework\Routing\RequestTransformer;
  24. use Shopware\Storefront\Page\Account\Login\AccountGuestLoginPageLoadedHook;
  25. use Shopware\Storefront\Page\Account\Login\AccountLoginPageLoadedHook;
  26. use Shopware\Storefront\Page\Account\Login\AccountLoginPageLoader;
  27. use Shopware\Storefront\Page\Account\RecoverPassword\AccountRecoverPasswordPageLoadedHook;
  28. use Shopware\Storefront\Page\Account\RecoverPassword\AccountRecoverPasswordPageLoader;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Symfony\Component\HttpFoundation\Response;
  31. use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
  32. use Symfony\Component\Routing\Annotation\Route;
  33. /**
  34.  * @internal
  35.  */
  36. #[Route(defaults: ['_routeScope' => ['storefront']])]
  37. #[Package('storefront')]
  38. class AuthController extends StorefrontController
  39. {
  40.     /**
  41.      * @internal
  42.      */
  43.     public function __construct(private readonly AccountLoginPageLoader $loginPageLoader, private readonly AbstractSendPasswordRecoveryMailRoute $sendPasswordRecoveryMailRoute, private readonly AbstractResetPasswordRoute $resetPasswordRoute, private readonly AbstractLoginRoute $loginRoute, private readonly AbstractLogoutRoute $logoutRoute, private readonly StorefrontCartFacade $cartFacade, private readonly AccountRecoverPasswordPageLoader $recoverPasswordPageLoader, private readonly SalesChannelContextServiceInterface $salesChannelContext)
  44.     {
  45.     }
  46.     #[Route(path'/account/login'name'frontend.account.login.page'defaults: ['_noStore' => true], methods: ['GET'])]
  47.     public function loginPage(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  48.     {
  49.         /** @var string $redirect */
  50.         $redirect $request->get('redirectTo''frontend.account.home.page');
  51.         $customer $context->getCustomer();
  52.         if ($customer !== null && $customer->getGuest() === false) {
  53.             $request->request->set('redirectTo'$redirect);
  54.             return $this->createActionResponse($request);
  55.         }
  56.         $page $this->loginPageLoader->load($request$context);
  57.         $this->hook(new AccountLoginPageLoadedHook($page$context));
  58.         return $this->renderStorefront('@Storefront/storefront/page/account/register/index.html.twig', [
  59.             'redirectTo' => $redirect,
  60.             'redirectParameters' => $request->get('redirectParameters'json_encode([])),
  61.             'page' => $page,
  62.             'loginError' => (bool) $request->get('loginError'),
  63.             'waitTime' => $request->get('waitTime'),
  64.             'errorSnippet' => $request->get('errorSnippet'),
  65.             'data' => $data,
  66.         ]);
  67.     }
  68.     #[Route(path'/account/guest/login'name'frontend.account.guest.login.page'defaults: ['_noStore' => true], methods: ['GET'])]
  69.     public function guestLoginPage(Request $requestSalesChannelContext $context): Response
  70.     {
  71.         /** @var string $redirect */
  72.         $redirect $request->get('redirectTo''frontend.account.home.page');
  73.         $customer $context->getCustomer();
  74.         if ($customer !== null) {
  75.             $request->request->set('redirectTo'$redirect);
  76.             return $this->createActionResponse($request);
  77.         }
  78.         $waitTime = (int) $request->get('waitTime');
  79.         if ($waitTime) {
  80.             $this->addFlash(self::INFO$this->trans('account.loginThrottled', ['%seconds%' => $waitTime]));
  81.         }
  82.         if ((bool) $request->get('loginError')) {
  83.             $this->addFlash(self::DANGER$this->trans('account.orderGuestLoginWrongCredentials'));
  84.         }
  85.         $page $this->loginPageLoader->load($request$context);
  86.         $this->hook(new AccountGuestLoginPageLoadedHook($page$context));
  87.         return $this->renderStorefront('@Storefront/storefront/page/account/guest-auth.html.twig', [
  88.             'redirectTo' => $redirect,
  89.             'redirectParameters' => $request->get('redirectParameters'json_encode([])),
  90.             'page' => $page,
  91.         ]);
  92.     }
  93.     #[Route(path'/account/logout'name'frontend.account.logout.page'methods: ['GET'])]
  94.     public function logout(Request $requestSalesChannelContext $contextRequestDataBag $dataBag): Response
  95.     {
  96.         if ($context->getCustomer() === null) {
  97.             return $this->redirectToRoute('frontend.account.login.page');
  98.         }
  99.         try {
  100.             $this->logoutRoute->logout($context$dataBag);
  101.             $this->addFlash(self::SUCCESS$this->trans('account.logoutSucceeded'));
  102.             $parameters = [];
  103.         } catch (ConstraintViolationException $formViolations) {
  104.             $parameters = ['formViolations' => $formViolations];
  105.         }
  106.         return $this->redirectToRoute('frontend.account.login.page'$parameters);
  107.     }
  108.     #[Route(path'/account/login'name'frontend.account.login'defaults: ['XmlHttpRequest' => true], methods: ['POST'])]
  109.     public function login(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  110.     {
  111.         $customer $context->getCustomer();
  112.         if ($customer !== null && $customer->getGuest() === false) {
  113.             return $this->createActionResponse($request);
  114.         }
  115.         try {
  116.             $token $this->loginRoute->login($data$context)->getToken();
  117.             $cartBeforeNewContext $this->cartFacade->get($token$context);
  118.             $newContext $this->salesChannelContext->get(
  119.                 new SalesChannelContextServiceParameters(
  120.                     $context->getSalesChannelId(),
  121.                     $token,
  122.                     $context->getLanguageIdChain()[0],
  123.                     $context->getCurrencyId(),
  124.                     $context->getDomainId(),
  125.                     $context->getContext()
  126.                 )
  127.             );
  128.             // Update the sales channel context for CacheResponseSubscriber
  129.             $request->attributes->set(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT$newContext);
  130.             if (!empty($token)) {
  131.                 $this->addCartErrors($cartBeforeNewContext);
  132.                 return $this->createActionResponse($request);
  133.             }
  134.         } catch (BadCredentialsException UnauthorizedHttpException CustomerOptinNotCompletedException CustomerAuthThrottledException $e) {
  135.             if ($e instanceof CustomerOptinNotCompletedException) {
  136.                 $errorSnippet $e->getSnippetKey();
  137.             }
  138.             if ($e instanceof CustomerAuthThrottledException) {
  139.                 $waitTime $e->getWaitTime();
  140.             }
  141.         }
  142.         $data->set('password'null);
  143.         return $this->forwardToRoute(
  144.             'frontend.account.login.page',
  145.             [
  146.                 'loginError' => true,
  147.                 'errorSnippet' => $errorSnippet ?? null,
  148.                 'waitTime' => $waitTime ?? null,
  149.             ]
  150.         );
  151.     }
  152.     #[Route(path'/account/recover'name'frontend.account.recover.page'methods: ['GET'])]
  153.     public function recoverAccountForm(Request $requestSalesChannelContext $context): Response
  154.     {
  155.         $page $this->loginPageLoader->load($request$context);
  156.         return $this->renderStorefront('@Storefront/storefront/page/account/profile/recover-password.html.twig', [
  157.             'page' => $page,
  158.         ]);
  159.     }
  160.     #[Route(path'/account/recover'name'frontend.account.recover.request'methods: ['POST'])]
  161.     public function generateAccountRecovery(Request $requestRequestDataBag $dataSalesChannelContext $context): Response
  162.     {
  163.         try {
  164.             $data->get('email')
  165.                 ->set('storefrontUrl'$request->attributes->get(RequestTransformer::STOREFRONT_URL));
  166.             $this->sendPasswordRecoveryMailRoute->sendRecoveryMail(
  167.                 $data->get('email')->toRequestDataBag(),
  168.                 $context,
  169.                 false
  170.             );
  171.             $this->addFlash(self::SUCCESS$this->trans('account.recoveryMailSend'));
  172.         } catch (CustomerNotFoundException $e) {
  173.             $this->addFlash(self::SUCCESS$this->trans('account.recoveryMailSend'));
  174.         } catch (InconsistentCriteriaIdsException $e) {
  175.             $this->addFlash(self::DANGER$this->trans('error.message-default'));
  176.         } catch (RateLimitExceededException $e) {
  177.             $this->addFlash(self::INFO$this->trans('error.rateLimitExceeded', ['%seconds%' => $e->getWaitTime()]));
  178.         }
  179.         return $this->redirectToRoute('frontend.account.recover.page');
  180.     }
  181.     #[Route(path'/account/recover/password'name'frontend.account.recover.password.page'methods: ['GET'])]
  182.     public function resetPasswordForm(Request $requestSalesChannelContext $context): Response
  183.     {
  184.         /** @var ?string $hash */
  185.         $hash $request->get('hash');
  186.         if (!$hash || !\is_string($hash)) {
  187.             $this->addFlash(self::DANGER$this->trans('account.passwordHashNotFound'));
  188.             return $this->redirectToRoute('frontend.account.recover.request');
  189.         }
  190.         try {
  191.             $page $this->recoverPasswordPageLoader->load($request$context$hash);
  192.         } catch (ConstraintViolationException) {
  193.             $this->addFlash(self::DANGER$this->trans('account.passwordHashNotFound'));
  194.             return $this->redirectToRoute('frontend.account.recover.request');
  195.         }
  196.         $this->hook(new AccountRecoverPasswordPageLoadedHook($page$context));
  197.         if ($page->getHash() === null || $page->isHashExpired()) {
  198.             $this->addFlash(self::DANGER$this->trans('account.passwordHashNotFound'));
  199.             return $this->redirectToRoute('frontend.account.recover.request');
  200.         }
  201.         return $this->renderStorefront('@Storefront/storefront/page/account/profile/reset-password.html.twig', [
  202.             'page' => $page,
  203.             'formViolations' => $request->get('formViolations'),
  204.         ]);
  205.     }
  206.     #[Route(path'/account/recover/password'name'frontend.account.recover.password.reset'methods: ['POST'])]
  207.     public function resetPassword(RequestDataBag $dataSalesChannelContext $context): Response
  208.     {
  209.         $hash $data->get('password')->get('hash');
  210.         try {
  211.             $pw $data->get('password');
  212.             $this->resetPasswordRoute->resetPassword($pw->toRequestDataBag(), $context);
  213.             $this->addFlash(self::SUCCESS$this->trans('account.passwordChangeSuccess'));
  214.         } catch (ConstraintViolationException $formViolations) {
  215.             $this->addFlash(self::DANGER$this->trans('account.passwordChangeNoSuccess'));
  216.             return $this->forwardToRoute(
  217.                 'frontend.account.recover.password.page',
  218.                 ['hash' => $hash'formViolations' => $formViolations'passwordFormViolation' => true]
  219.             );
  220.         } catch (CustomerNotFoundByHashException) {
  221.             $this->addFlash(self::DANGER$this->trans('account.passwordChangeNoSuccess'));
  222.             return $this->forwardToRoute('frontend.account.recover.request');
  223.         } catch (CustomerRecoveryHashExpiredException) {
  224.             $this->addFlash(self::DANGER$this->trans('account.passwordHashExpired'));
  225.             return $this->forwardToRoute('frontend.account.recover.request');
  226.         }
  227.         return $this->redirectToRoute('frontend.account.profile.page');
  228.     }
  229. }