vendor/shopware/core/Checkout/Customer/SalesChannel/LoginRoute.php line 88

Open in your IDE?
  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Checkout\Customer\SalesChannel;
  3. use Shopware\Core\Checkout\Customer\CustomerEntity;
  4. use Shopware\Core\Checkout\Customer\Event\CustomerBeforeLoginEvent;
  5. use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
  6. use Shopware\Core\Checkout\Customer\Exception\BadCredentialsException;
  7. use Shopware\Core\Checkout\Customer\Exception\CustomerAuthThrottledException;
  8. use Shopware\Core\Checkout\Customer\Exception\CustomerNotFoundException;
  9. use Shopware\Core\Checkout\Customer\Exception\InactiveCustomerException;
  10. use Shopware\Core\Checkout\Customer\Password\LegacyPasswordVerifier;
  11. use Shopware\Core\Framework\Context;
  12. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  13. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  14. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  15. use Shopware\Core\Framework\Log\Package;
  16. use Shopware\Core\Framework\Plugin\Exception\DecorationPatternException;
  17. use Shopware\Core\Framework\RateLimiter\Exception\RateLimitExceededException;
  18. use Shopware\Core\Framework\RateLimiter\RateLimiter;
  19. use Shopware\Core\Framework\Routing\Annotation\ContextTokenRequired;
  20. use Shopware\Core\Framework\Routing\Annotation\RouteScope;
  21. use Shopware\Core\Framework\Routing\Annotation\Since;
  22. use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
  23. use Shopware\Core\System\SalesChannel\Context\CartRestorer;
  24. use Shopware\Core\System\SalesChannel\ContextTokenResponse;
  25. use Shopware\Core\System\SalesChannel\SalesChannelContext;
  26. use Symfony\Component\HttpFoundation\RequestStack;
  27. use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;
  28. use Symfony\Component\Routing\Annotation\Route;
  29. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  30. /**
  31.  * @Route(defaults={"_routeScope"={"store-api"}, "_contextTokenRequired"=true})
  32.  */
  33. #[Package('customer-order')]
  34. class LoginRoute extends AbstractLoginRoute
  35. {
  36.     private EventDispatcherInterface $eventDispatcher;
  37.     private EntityRepositoryInterface $customerRepository;
  38.     private LegacyPasswordVerifier $legacyPasswordVerifier;
  39.     private CartRestorer $restorer;
  40.     private RequestStack $requestStack;
  41.     private RateLimiter $rateLimiter;
  42.     /**
  43.      * @internal
  44.      */
  45.     public function __construct(
  46.         EventDispatcherInterface $eventDispatcher,
  47.         EntityRepositoryInterface $customerRepository,
  48.         LegacyPasswordVerifier $legacyPasswordVerifier,
  49.         CartRestorer $restorer,
  50.         RequestStack $requestStack,
  51.         RateLimiter $rateLimiter
  52.     ) {
  53.         $this->eventDispatcher $eventDispatcher;
  54.         $this->customerRepository $customerRepository;
  55.         $this->legacyPasswordVerifier $legacyPasswordVerifier;
  56.         $this->restorer $restorer;
  57.         $this->requestStack $requestStack;
  58.         $this->rateLimiter $rateLimiter;
  59.     }
  60.     public function getDecorated(): AbstractLoginRoute
  61.     {
  62.         throw new DecorationPatternException(self::class);
  63.     }
  64.     /**
  65.      * @Since("6.2.0.0")
  66.      * @Route(path="/store-api/account/login", name="store-api.account.login", methods={"POST"})
  67.      */
  68.     public function login(RequestDataBag $dataSalesChannelContext $context): ContextTokenResponse
  69.     {
  70.         $email $data->get('email'$data->get('username'));
  71.         if (empty($email) || empty($data->get('password'))) {
  72.             throw new BadCredentialsException();
  73.         }
  74.         $event = new CustomerBeforeLoginEvent($context$email);
  75.         $this->eventDispatcher->dispatch($event);
  76.         if ($this->requestStack->getMainRequest() !== null) {
  77.             $cacheKey strtolower($email) . '-' $this->requestStack->getMainRequest()->getClientIp();
  78.             try {
  79.                 $this->rateLimiter->ensureAccepted(RateLimiter::LOGIN_ROUTE$cacheKey);
  80.             } catch (RateLimitExceededException $exception) {
  81.                 throw new CustomerAuthThrottledException($exception->getWaitTime(), $exception);
  82.             }
  83.         }
  84.         try {
  85.             $customer $this->getCustomerByLogin(
  86.                 $email,
  87.                 $data->get('password'),
  88.                 $context
  89.             );
  90.         } catch (CustomerNotFoundException BadCredentialsException $exception) {
  91.             throw new UnauthorizedHttpException('json'$exception->getMessage());
  92.         }
  93.         if (isset($cacheKey)) {
  94.             $this->rateLimiter->reset(RateLimiter::LOGIN_ROUTE$cacheKey);
  95.         }
  96.         if (!$customer->getActive()) {
  97.             throw new InactiveCustomerException($customer->getId());
  98.         }
  99.         $context $this->restorer->restore($customer->getId(), $context);
  100.         $newToken $context->getToken();
  101.         $this->customerRepository->update([
  102.             [
  103.                 'id' => $customer->getId(),
  104.                 'lastLogin' => new \DateTimeImmutable(),
  105.                 'languageId' => $context->getLanguageId(),
  106.             ],
  107.         ], $context->getContext());
  108.         $event = new CustomerLoginEvent($context$customer$newToken);
  109.         $this->eventDispatcher->dispatch($event);
  110.         return new ContextTokenResponse($newToken);
  111.     }
  112.     private function getCustomerByLogin(string $emailstring $passwordSalesChannelContext $context): CustomerEntity
  113.     {
  114.         $customer $this->getCustomerByEmail($email$context);
  115.         if ($customer->hasLegacyPassword()) {
  116.             if (!$this->legacyPasswordVerifier->verify($password$customer)) {
  117.                 throw new BadCredentialsException();
  118.             }
  119.             $this->updatePasswordHash($password$customer$context->getContext());
  120.             return $customer;
  121.         }
  122.         if (!password_verify($password$customer->getPassword() ?? '')) {
  123.             throw new BadCredentialsException();
  124.         }
  125.         return $customer;
  126.     }
  127.     private function getCustomerByEmail(string $emailSalesChannelContext $context): CustomerEntity
  128.     {
  129.         $criteria = new Criteria();
  130.         $criteria->setTitle('login-route');
  131.         $criteria->addFilter(new EqualsFilter('customer.email'$email));
  132.         $result $this->customerRepository->search($criteria$context->getContext());
  133.         $result $result->filter(static function (CustomerEntity $customer) use ($context) {
  134.             $isConfirmed = !$customer->getDoubleOptInRegistration() || $customer->getDoubleOptInConfirmDate();
  135.             // Skip guest and not active users
  136.             if ($customer->getGuest() || (!$customer->getActive() && $isConfirmed)) {
  137.                 return null;
  138.             }
  139.             // If not bound, we still need to consider it
  140.             if ($customer->getBoundSalesChannelId() === null) {
  141.                 return true;
  142.             }
  143.             // It is bound, but not to the current one. Skip it
  144.             if ($customer->getBoundSalesChannelId() !== $context->getSalesChannel()->getId()) {
  145.                 return null;
  146.             }
  147.             return true;
  148.         });
  149.         if ($result->count() !== 1) {
  150.             throw new BadCredentialsException();
  151.         }
  152.         return $result->first();
  153.     }
  154.     private function updatePasswordHash(string $passwordCustomerEntity $customerContext $context): void
  155.     {
  156.         $this->customerRepository->update([
  157.             [
  158.                 'id' => $customer->getId(),
  159.                 'password' => $password,
  160.                 'legacyPassword' => null,
  161.                 'legacyEncoder' => null,
  162.             ],
  163.         ], $context);
  164.     }
  165. }