34 lines
1.2 KiB
PHP
34 lines
1.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/bootstrap.php';
|
|
|
|
function app_client_ip(): string
|
|
{
|
|
return (string)($_SERVER['REMOTE_ADDR'] ?? 'unknown');
|
|
}
|
|
|
|
/**
|
|
* Records an attempt in the given bucket and reports whether the caller is
|
|
* still within the allowed rate. Call before doing the sensitive work (auth
|
|
* check, mail dispatch, account creation); if this returns false, show a
|
|
* generic "too many attempts" message without processing the request, so a
|
|
* bucket also can't be used to enumerate valid accounts by timing.
|
|
*/
|
|
function app_rate_limit_check(PDO $pdo, string $bucket, int $maxAttempts, int $windowSeconds): bool
|
|
{
|
|
$pdo->prepare('INSERT INTO rate_limit_attempts (bucket) VALUES (?)')->execute([$bucket]);
|
|
|
|
// Globaler Maximalhorizont: unabhängig davon, ob derselbe Bucket je wieder
|
|
// benutzt wird, bleibt keine IP-basierte Rate-Limit-Zeile länger als 24h.
|
|
$pdo->exec('DELETE FROM rate_limit_attempts WHERE created_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)');
|
|
|
|
$stmt = $pdo->prepare(
|
|
'SELECT COUNT(*) FROM rate_limit_attempts WHERE bucket = ? AND created_at >= DATE_SUB(NOW(), INTERVAL ? SECOND)'
|
|
);
|
|
$stmt->execute([$bucket, $windowSeconds]);
|
|
|
|
return (int)$stmt->fetchColumn() <= $maxAttempts;
|
|
}
|