src/EventSubscriber/ApiRateLimitSubscriber.php line 35

Open in your IDE?
  1. <?php
  2. //----------------------------------------------------------------------
  3. // src/EventSubscriber/ApiRateLimitSubscriber.php
  4. //----------------------------------------------------------------------
  5. namespace App\EventSubscriber;
  6. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  7. use Symfony\Component\HttpKernel\Event\RequestEvent;
  8. use Symfony\Component\HttpKernel\KernelEvents;
  9. use Symfony\Component\RateLimiter\RateLimiterFactory;
  10. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  11. use App\Entity\APIRest\AccessAPI;
  12. use App\Services\APIRest\Tools\APIResponseTools;
  13. use App\Services\LogTools;
  14. use App\Utils\APIError;
  15. class ApiRateLimitSubscriber implements EventSubscriberInterface
  16. {
  17.     public function __construct(
  18.         private RateLimiterFactory $apiByIpLimiter,
  19.         private RateLimiterFactory $apiByUserLimiter,
  20.         private TokenStorageInterface $tokenStorage,
  21.         private APIResponseTools $apiResponseTools,
  22.         private LogTools $logTools,
  23.     ) {}
  24.     public static function getSubscribedEvents(): array
  25.     {
  26.         return [
  27.             KernelEvents::REQUEST => ['onKernelRequest', -10],
  28.         ];
  29.     }
  30.     public function onKernelRequest(RequestEvent $event): void
  31.     {
  32.         if (!$event->isMainRequest()) 
  33.         {
  34.             return;
  35.         }
  36.         $request $event->getRequest();
  37.         $route $request->attributes->get('_route''');
  38.         // Only apply to API REST routes
  39.         if (!str_starts_with($route'icod_api_rest_')) 
  40.         {
  41.             return;
  42.         }
  43.         // Exclude login and lost password routes
  44.         if (in_array($route, ['icod_api_rest_authentication_login''icod_api_rest_lost_password'])) 
  45.         {
  46.             return;
  47.         }
  48.         $ip $request->getClientIp() ?? 'unknown';
  49.         // Rate limit by IP
  50.         $ipLimiter $this->apiByIpLimiter->create($ip);
  51.         $ipLimit $ipLimiter->consume(1);
  52.         if (!$ipLimit->isAccepted()) 
  53.         {
  54.             $token $this->tokenStorage->getToken();
  55.             $accessApi null;
  56.             if ($token !== null && ($tokenUser $token->getUser()) instanceof AccessAPI)
  57.             {
  58.                 $accessApi $tokenUser;
  59.             }
  60.             $maxRequests method_exists($ipLimit'getLimit') ? $ipLimit->getLimit() : '';
  61.             $this->logTools->apiRestRateLimitLog([
  62.                 'scope'        => 'IP',
  63.                 'ip'           => $ip,
  64.                 'accessApi'    => $accessApi,
  65.                 'route'        => $route,
  66.                 'max_requests' => $maxRequests,
  67.             ]);
  68.             $apiError = new APIError(APIError::RATE_LIMIT_EXCEEDED);
  69.             $event->setResponse($this->apiResponseTools->tooManyRequestsResponse($apiError));
  70.             return;
  71.         }
  72.         // Rate limit by authenticated user
  73.         $token $this->tokenStorage->getToken();
  74.         if ($token !== null && ($user $token->getUser()) instanceof AccessAPI
  75.         {
  76.             $userLimiter $this->apiByUserLimiter->create('user_' $user->getId());
  77.             $userLimit $userLimiter->consume(1);
  78.             if (!$userLimit->isAccepted()) 
  79.             {
  80.                 $maxRequests method_exists($userLimit'getLimit') ? $userLimit->getLimit() : '';
  81.                 $this->logTools->apiRestRateLimitLog([
  82.                     'scope'        => 'USER',
  83.                     'ip'           => $ip,
  84.                     'accessApi'    => $user,
  85.                     'route'        => $route,
  86.                     'max_requests' => $maxRequests,
  87.                 ]);
  88.                 $apiError = new APIError(APIError::RATE_LIMIT_EXCEEDED);
  89.                 $event->setResponse($this->apiResponseTools->tooManyRequestsResponse($apiError));
  90.                 return;
  91.             }
  92.         }
  93.     }
  94. }