feat: add captcha-backed forum security hardening
This commit is contained in:
70
app/Http/Middleware/ForumRateLimitMiddleware.php
Normal file
70
app/Http/Middleware/ForumRateLimitMiddleware.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use cPad\Plugins\Forum\Services\Security\BotProtectionService;
|
||||
use Illuminate\Http\Exceptions\ThrottleRequestsException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Routing\Middleware\ThrottleRequests;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForumRateLimitMiddleware
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ThrottleRequests $throttleRequests,
|
||||
private readonly BotProtectionService $botProtectionService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function handle(Request $request, Closure $next): Response
|
||||
{
|
||||
$routeName = (string) optional($request->route())->getName();
|
||||
$limiterName = match ($routeName) {
|
||||
'forum.topic.store' => 'forum-thread-create',
|
||||
default => 'forum-post-write',
|
||||
};
|
||||
|
||||
try {
|
||||
return $this->throttleRequests->handle($request, $next, $limiterName);
|
||||
} catch (ThrottleRequestsException $exception) {
|
||||
$maxAttempts = (int) ($exception->getHeaders()['X-RateLimit-Limit'] ?? 0);
|
||||
|
||||
$this->botProtectionService->recordRateLimitViolation(
|
||||
$request,
|
||||
$this->resolveActionName($routeName),
|
||||
[
|
||||
'limiter' => $limiterName,
|
||||
'bucket' => $this->resolveBucket($limiterName, $maxAttempts),
|
||||
'max_attempts' => $maxAttempts,
|
||||
'retry_after' => (int) ($exception->getHeaders()['Retry-After'] ?? 0),
|
||||
'reason' => sprintf('Forum write rate limit exceeded on %s.', $routeName !== '' ? $routeName : 'unknown route'),
|
||||
],
|
||||
);
|
||||
|
||||
throw $exception;
|
||||
}
|
||||
}
|
||||
|
||||
private function resolveActionName(string $routeName): string
|
||||
{
|
||||
return match ($routeName) {
|
||||
'forum.topic.store' => 'forum_topic_create',
|
||||
'forum.post.update' => 'forum_post_update',
|
||||
default => 'forum_reply_create',
|
||||
};
|
||||
}
|
||||
|
||||
private function resolveBucket(string $limiterName, int $maxAttempts): string
|
||||
{
|
||||
return $maxAttempts <= $this->minuteLimitThreshold($limiterName) ? 'minute' : 'hour';
|
||||
}
|
||||
|
||||
private function minuteLimitThreshold(string $limiterName): int
|
||||
{
|
||||
return match ($limiterName) {
|
||||
'forum-thread-create', 'forum-post-write' => 3,
|
||||
default => PHP_INT_MAX,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user