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

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