Brute-force, error and request rate limiting
This is a library for rate-limiting both brute-force attempts (like invalid credentials) and ordinary requests.
composer require maba/gentle-force
<?php
use Maba\GentleForce\RateLimit\UsageRateLimit;
use Maba\GentleForce\RateLimitProvider;
use Maba\GentleForce\Throttler;
use Maba\GentleForce\Exception\RateLimitReachedException;
$rateLimitProvider = new RateLimitProvider();
$rateLimitProvider->registerRateLimits('credentials_error', [
// allow 3 errors per hour; 2 additional errors if no errors were made during last hour
(new UsageRateLimit(3, 3600))->setBucketedUsages(2),
// allow 10 errors per day
new UsageRateLimit(10, 3600 * 24),
]);
$rateLimitProvider->registerRateLimits('api_request', [
// - allow 10 requests each minute;
// - user can "save up" hour of usage if not using API.
// This means up to 610 requests at once, after that - 10 requests per minute,
// which could again save-up up to 610.
(new UsageRateLimit(10, 60))->setBucketedPeriod(3600),
]);
$throttler = new Throttler(new \Predis\Client([
'host' => '127.0.0.1',
]), $rateLimitProvider);
// rate limiting:
try {
$result = $throttler->checkAndIncrease('api_request', $_SERVER['REMOTE_ADDR']);
header('Requests-Available', $result->getUsagesAvailable());
} catch (RateLimitReachedException $exception) {
header('Wait-For', $exception->getWaitForInSeconds(), 429);
return;
}
// brute-force limiting:
try {
// we must increase error count in-advance before even checking credentials
// this avoids race-conditions with lots of requests
$credentialsResult = $throttler->checkAndIncrease('credentials_error', $_POST['username']);
} catch (RateLimitReachedException $exception) {
echo sprintf('Too much password tries for user. Please try after %s seconds', $exception->getWaitForInSeconds());
return;
}
$credentialsValid = checkCredentials($_POST['username'], $_POST['password']);
if ($credentialsValid) {
// as we've increased error count in advance, we need to decrease it if everything went fine
$credentialsResult->decrease();
// log user into system
}
Actually, there are quite many of them.
Unfortunately, as some provide additional features (like different storage methods: file, memcached etc.), none were found with these criteria:
Some of reviewed alternatives: RateLimitInterface, rate-limiter, LosRateLimit, Rate-limit, rate-limit, php-ratelimiter, tokenbucket, brute-force, LoginGateBundle, tresholds-governor, throttle, PeerjUserSecurityBundle, php-ratelimiter, RateLimitBundle, CybBotDetectBunble, CCDNUserSecurityBundle, limit-number-calls-bundle, rate-limiter-php, flaps, token-bucket
This library follows semantic versioning.
See Symfony BC rules for basic information about what can be changed and what not in the API.
Functional tests require Redis and several PHP extensions for forking, so that behaviour on high traffic could be tested. So, generally, it's easier to run them in docker.
composer update
cd docker
docker-compose up -d
docker exec -it gentle_force_test_php vendor/bin/phpunit
docker-compose down
Feel free to create issues and give pull requests.
You can fix any code style issues using this command:
vendor/bin/php-cs-fixer fix --config=.php_cs