2020-02-06 03:35:13 +01:00
|
|
|
<?php
|
2020-10-15 00:19:31 +02:00
|
|
|
|
2021-07-19 07:53:45 +02:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
2020-02-06 03:35:13 +01:00
|
|
|
namespace App;
|
|
|
|
|
2023-06-07 08:25:57 +02:00
|
|
|
use App\Container\EnvironmentAwareTrait;
|
2023-04-28 08:47:44 +02:00
|
|
|
use App\Entity\Repository\SettingsRepository;
|
2020-02-06 03:35:13 +01:00
|
|
|
use App\Http\ServerRequest;
|
2022-12-06 15:45:43 +01:00
|
|
|
use App\Lock\LockFactory;
|
2021-06-28 17:03:21 +02:00
|
|
|
use Psr\Cache\CacheItemPoolInterface;
|
|
|
|
use Symfony\Component\Cache\Adapter\ProxyAdapter;
|
|
|
|
use Symfony\Component\RateLimiter\RateLimiterFactory;
|
|
|
|
use Symfony\Component\RateLimiter\Storage\CacheStorage;
|
2020-02-06 03:35:13 +01:00
|
|
|
|
2022-07-01 09:41:04 +02:00
|
|
|
final class RateLimit
|
2020-02-06 03:35:13 +01:00
|
|
|
{
|
2023-06-07 08:25:57 +02:00
|
|
|
use EnvironmentAwareTrait;
|
|
|
|
|
2022-07-01 09:41:04 +02:00
|
|
|
private CacheItemPoolInterface $psr6Cache;
|
2021-06-28 17:03:21 +02:00
|
|
|
|
2021-04-23 07:24:12 +02:00
|
|
|
public function __construct(
|
2022-07-01 09:41:04 +02:00
|
|
|
private readonly LockFactory $lockFactory,
|
2023-04-28 08:47:44 +02:00
|
|
|
private readonly SettingsRepository $settingsRepo,
|
2021-06-28 17:03:21 +02:00
|
|
|
CacheItemPoolInterface $cacheItemPool
|
2021-04-23 07:24:12 +02:00
|
|
|
) {
|
2021-06-28 17:03:21 +02:00
|
|
|
$this->psr6Cache = new ProxyAdapter($cacheItemPool, 'ratelimit.');
|
2020-02-06 03:35:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param ServerRequest $request
|
2021-01-23 18:03:16 +01:00
|
|
|
* @param string $groupName
|
2020-02-06 03:35:13 +01:00
|
|
|
* @param int $interval
|
2021-06-28 17:03:21 +02:00
|
|
|
* @param int $limit
|
2020-02-06 03:35:13 +01:00
|
|
|
*
|
|
|
|
* @throws Exception\RateLimitExceededException
|
|
|
|
*/
|
2021-01-23 18:03:16 +01:00
|
|
|
public function checkRequestRateLimit(
|
2020-02-06 03:35:13 +01:00
|
|
|
ServerRequest $request,
|
2021-01-23 18:03:16 +01:00
|
|
|
string $groupName,
|
2021-06-28 17:03:21 +02:00
|
|
|
int $interval = 5,
|
|
|
|
int $limit = 2
|
2021-01-23 18:03:16 +01:00
|
|
|
): void {
|
2020-12-04 09:41:55 +01:00
|
|
|
if ($this->environment->isTesting() || $this->environment->isCli()) {
|
2021-01-23 18:03:16 +01:00
|
|
|
return;
|
2020-02-06 03:35:13 +01:00
|
|
|
}
|
|
|
|
|
2023-04-28 08:47:44 +02:00
|
|
|
$ip = $this->settingsRepo->readSettings()->getIp($request);
|
|
|
|
|
|
|
|
$ipKey = str_replace([':', '.'], '_', $ip);
|
2021-06-28 17:03:21 +02:00
|
|
|
$this->checkRateLimit($groupName, $ipKey, $interval, $limit);
|
2021-01-23 18:03:16 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param string $groupName
|
2021-06-28 17:03:21 +02:00
|
|
|
* @param string $key
|
2021-01-23 18:03:16 +01:00
|
|
|
* @param int $interval
|
2021-06-28 17:03:21 +02:00
|
|
|
* @param int $limit
|
2021-01-23 18:03:16 +01:00
|
|
|
*
|
|
|
|
* @throws Exception\RateLimitExceededException
|
|
|
|
*/
|
|
|
|
public function checkRateLimit(
|
|
|
|
string $groupName,
|
2021-06-28 17:03:21 +02:00
|
|
|
string $key,
|
|
|
|
int $interval = 5,
|
|
|
|
int $limit = 2
|
2021-01-23 18:03:16 +01:00
|
|
|
): void {
|
2021-06-28 17:03:21 +02:00
|
|
|
$cacheStore = new CacheStorage($this->psr6Cache);
|
2021-01-23 18:03:16 +01:00
|
|
|
|
2021-06-28 17:03:21 +02:00
|
|
|
$config = [
|
|
|
|
'id' => 'ratelimit.' . $groupName,
|
|
|
|
'policy' => 'sliding_window',
|
|
|
|
'interval' => $interval . ' seconds',
|
|
|
|
'limit' => $limit,
|
|
|
|
];
|
2020-02-06 03:35:13 +01:00
|
|
|
|
2021-06-28 17:03:21 +02:00
|
|
|
$rateLimiterFactory = new RateLimiterFactory($config, $cacheStore, $this->lockFactory);
|
|
|
|
$rateLimiter = $rateLimiterFactory->create($key);
|
2020-02-06 03:35:13 +01:00
|
|
|
|
2022-05-17 09:51:00 +02:00
|
|
|
if (!$rateLimiter->consume()->isAccepted()) {
|
2021-06-28 17:03:21 +02:00
|
|
|
throw new Exception\RateLimitExceededException();
|
2020-02-06 03:35:13 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|