152 lines
5.1 KiB
PHP
152 lines
5.1 KiB
PHP
<?php
|
|
|
|
namespace App\Filters;
|
|
|
|
use App\Libraries\RateLimiterService;
|
|
use CodeIgniter\Filters\FilterInterface;
|
|
use CodeIgniter\HTTP\RequestInterface;
|
|
use CodeIgniter\HTTP\ResponseInterface;
|
|
|
|
use App\Filters\Cors;
|
|
/**
|
|
* AuthApiFilter
|
|
*
|
|
* Applied to API routes that do NOT use JWT — e.g. verifyMobileNumber, verifyOTP.
|
|
* Identity is extracted from request params: 'email' or 'mobile_number'.
|
|
*
|
|
* Performs:
|
|
* - IP-level throttle + progressive block check (via fingerprint)
|
|
* - User-level block check (if identity present in params)
|
|
*
|
|
* Usage in Routes.php:
|
|
* $routes->post('api/auth/verify-otp', 'AuthController::verifyOtp', ['filter' => 'authApiRateLimit']);
|
|
*
|
|
* Register in app/Config/Filters.php:
|
|
* 'AuthApiRateLimitFilter' => \App\Filters\AuthApiRateLimitFilter::class
|
|
*/
|
|
class AuthApiRateLimitFilter implements FilterInterface
|
|
{
|
|
protected RateLimiterService $limiter;
|
|
protected Cors $corsFilter;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->limiter = new RateLimiterService();
|
|
$this->corsFilter = new Cors();
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// BEFORE — runs before the controller
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function before(RequestInterface $request, $arguments = null)
|
|
{
|
|
$fingerprint = generateFingerprint(exclude_ua: true);
|
|
|
|
|
|
// 1. IP-level check
|
|
$ipResult = $this->limiter->checkIp($fingerprint, 'authApi');
|
|
if ($ipResult) {
|
|
return $this->jsonResponse($request, $ipResult);
|
|
}
|
|
|
|
// 2. User-level block check (identity may not be present yet on first hit)
|
|
$identity = $this->resolveIdentity($request);
|
|
if ($identity) {
|
|
$userResult = $this->limiter->checkUser($identity);
|
|
if ($userResult) {
|
|
return $this->jsonResponse($request, $userResult);
|
|
}
|
|
}
|
|
|
|
// Store resolved identity in request for use in after()
|
|
if ($identity) {
|
|
$request->setGlobal('rateLimitIdentity', $identity);
|
|
}
|
|
$request->setGlobal('rateLimitFingerprint', $fingerprint);
|
|
|
|
return null; // pass through
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// AFTER — runs after the controller; records failures on bad responses
|
|
// -------------------------------------------------------------------------
|
|
|
|
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
|
|
{
|
|
// Only act on failed responses (4xx from auth failures)
|
|
$statusCode = $response->getStatusCode();
|
|
if ($statusCode < 400 || $statusCode === 429 || $statusCode === 403 || $statusCode === 451) {
|
|
return; // 2xx/3xx = success; 429/403/451 already handled
|
|
}
|
|
|
|
$fingerprint = $request->getGlobal('rateLimitFingerprint') ?? generateFingerprint(exclude_ua: true);
|
|
|
|
$identity = $request->getGlobal('rateLimitIdentity')
|
|
?? $this->resolveIdentity($request);
|
|
|
|
// Record failure at IP level
|
|
$this->limiter->recordIpFailure($fingerprint);
|
|
|
|
// Record failure at user level
|
|
if ($identity) {
|
|
$this->limiter->recordUserFailure($identity, 'authApi');
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// HELPERS
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Extract identity from POST body or GET params.
|
|
* Looks for 'email' or 'mobile_number'.
|
|
*/
|
|
protected function resolveIdentity(RequestInterface $request): ?string
|
|
{
|
|
// Try POST body first
|
|
|
|
$request_data = $request->getPost() ?: $request->getJSON(true) ?? [];
|
|
// print_r($request_data); die;
|
|
|
|
$email = $request_data['email'] ?? $request_data['email_id'] ?? null;
|
|
$mobile = $request_data['mobile_number'] ?? null;
|
|
|
|
// Fallback to GET params
|
|
if (! $email && ! $mobile) {
|
|
$email = $request->getGet('email') ?? $request->getGet('email_id');
|
|
$mobile = $request->getGet('mobile_number');
|
|
}
|
|
|
|
if ($email) {
|
|
return strtolower(trim($email));
|
|
}
|
|
|
|
if ($mobile) {
|
|
return trim($mobile);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Build and return a JSON response for blocked/throttled requests.
|
|
*/
|
|
protected function jsonResponse(RequestInterface $request, array $result): ResponseInterface
|
|
{
|
|
$response = service('response');
|
|
$response->setStatusCode($result['status']);
|
|
$response->setContentType('application/json');
|
|
$response->setBody(json_encode([
|
|
'success' => false,
|
|
'error' => [
|
|
'code' => strtoupper('RATE_LIMIT_' . $result['level']),
|
|
'message' => $result['message'],
|
|
'type' => $result['type'] ?? 'request',
|
|
],
|
|
]));
|
|
$this->corsFilter->after($request, $response);
|
|
return $response;
|
|
}
|
|
}
|