121 lines
3.7 KiB
PHP
121 lines
3.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Limiteert het aantal acties per identifier binnen een tijdvenster.
|
|
*
|
|
* Houdt per identifier (zoals een IP of gebruikersnaam) bij hoeveel pogingen
|
|
* binnen een geconfigureerd tijdvenster zijn gedaan. Oudere pogingen vallen
|
|
* buiten het venster en worden genegeerd. De teller wordt opgeslagen in een
|
|
* CacheInterface-implementatie, standaard FileCache.
|
|
*
|
|
* @since 2.6.4
|
|
*/
|
|
class RateLimiter {
|
|
/**
|
|
* Maximum aantal toegestane pogingen binnen het tijdvenster.
|
|
*
|
|
* @since 2.6.4
|
|
* @var int
|
|
*/
|
|
private int $maxAttempts;
|
|
|
|
/**
|
|
* Lengte van het tijdvenster in seconden.
|
|
*
|
|
* @since 2.6.4
|
|
* @var int
|
|
*/
|
|
private int $timeWindow;
|
|
|
|
/**
|
|
* Cache-implementatie waarin de pogingen worden bijgehouden.
|
|
*
|
|
* @since 2.6.4
|
|
* @var CacheInterface
|
|
*/
|
|
private CacheInterface $cache;
|
|
|
|
/**
|
|
* Maak een nieuwe RateLimiter aan.
|
|
*
|
|
* @since 2.6.4
|
|
*
|
|
* @param int $maxAttempts Maximum aantal pogingen binnen het tijdvenster. Default 10.
|
|
* @param int $timeWindow Lengte van het tijdvenster in seconden. Default 60.
|
|
* @param CacheInterface|null $cache Optioneel een eigen cache-implementatie; bij null wordt FileCache gebruikt.
|
|
*/
|
|
public function __construct(int $maxAttempts = 10, int $timeWindow = 60, ?CacheInterface $cache = null) {
|
|
$this->maxAttempts = $maxAttempts;
|
|
$this->timeWindow = $timeWindow;
|
|
$this->cache = $cache ?? new FileCache();
|
|
}
|
|
|
|
/**
|
|
* Bepaal of een nieuwe poging voor de identifier is toegestaan.
|
|
*
|
|
* Verwijder eerst pogingen die buiten het tijdvenster vallen. Wanneer het
|
|
* aantal resterende pogingen onder het maximum ligt wordt de huidige
|
|
* poging geregistreerd en true teruggegeven; anders false.
|
|
*
|
|
* @since 2.6.4
|
|
*
|
|
* @param string $identifier Unieke sleutel voor de te beperken actor (bijv. IP of gebruikersnaam).
|
|
* @return bool True wanneer de poging is toegestaan, false wanneer het limiet is bereikt.
|
|
*/
|
|
public function isAllowed(string $identifier): bool {
|
|
$key = 'ratelimit_' . md5($identifier);
|
|
$attempts = $this->cache->get($key) ?? [];
|
|
|
|
// Clean old attempts
|
|
$now = time();
|
|
$windowStart = $now - $this->timeWindow;
|
|
$attempts = array_filter($attempts, fn($time) => $time > $windowStart);
|
|
|
|
$attemptCount = count($attempts);
|
|
|
|
if ($attemptCount >= $this->maxAttempts) {
|
|
return false;
|
|
}
|
|
|
|
$attempts[] = $now;
|
|
$this->cache->set($key, $attempts, $this->timeWindow);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Geef het aantal nog resterende pogingen voor de identifier.
|
|
*
|
|
* Telt alleen pogingen die binnen het tijdvenster vallen. Het resultaat
|
|
* is nooit negatief.
|
|
*
|
|
* @since 2.6.4
|
|
*
|
|
* @param string $identifier Unieke sleutel voor de te beperken actor.
|
|
* @return int Aantal resterende pogingen; minimaal 0.
|
|
*/
|
|
public function getRemainingAttempts(string $identifier): int {
|
|
$key = 'ratelimit_' . md5($identifier);
|
|
$attempts = $this->cache->get($key) ?? [];
|
|
|
|
// Clean old attempts
|
|
$now = time();
|
|
$windowStart = $now - $this->timeWindow;
|
|
$attempts = array_filter($attempts, fn($time) => $time > $windowStart);
|
|
|
|
return max(0, $this->maxAttempts - count($attempts));
|
|
}
|
|
|
|
/**
|
|
* Wis alle geregistreerde pogingen voor een identifier.
|
|
*
|
|
* @since 2.6.4
|
|
*
|
|
* @param string $identifier Unieke sleutel voor de te beperken actor.
|
|
* @return void
|
|
*/
|
|
public function reset(string $identifier): void {
|
|
$key = 'ratelimit_' . md5($identifier);
|
|
$this->cache->delete($key);
|
|
}
|
|
} |