135 lines
4.8 KiB
PHP
135 lines
4.8 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)
|
|
{
|
|
// $this->limiter->unblockUser('9698262411');die;
|
|
$fingerprint = generateFingerprint(exclude_ua: true);
|
|
// echo $fingerprint;die;
|
|
// 1. IP-level check
|
|
$ipResult = $this->limiter->checkIp($fingerprint, 'authApi');
|
|
if ($ipResult) {
|
|
return $this->jsonResponse($ipResult);
|
|
}
|
|
|
|
// 2. User-level block check (identity may not be present yet on first hit)
|
|
$identity = resolveIdentity($request);
|
|
// echo $identity;die;
|
|
if ($identity) {
|
|
$userResult = $this->limiter->checkUser($identity);
|
|
if ($userResult) {
|
|
return $this->jsonResponse($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)
|
|
{
|
|
|
|
// Routes where rate-limit failure should be recorded
|
|
// $allowedRoutes = [
|
|
// 'employeeRest/verifyEmployeeNumber',
|
|
// 'employeeRest/getVerifiedUserData',
|
|
// 'employeeRest/verifyEmployeeEmailId',
|
|
// 'employeeRest/verifyHrWithMobileNumber',
|
|
// 'employeeRest/verifyHrWithEmail',
|
|
// 'employeeRest/verifyHrWithEmail',
|
|
// 'employeeRest/getVerifiedHrData',
|
|
// ];
|
|
|
|
// $currentPath = service('request')->getPath();
|
|
// if (!in_array($currentPath, $allowedRoutes)) {
|
|
// return; // Don't record failures for unrelated routes
|
|
// }
|
|
|
|
// Only act on failed responses (4xx from auth failures)
|
|
$statusCode = $response->getStatusCode();
|
|
// print_r($response);die();
|
|
if ($statusCode < 400 || $statusCode === 429 || $statusCode === 403 || $statusCode === 451) {
|
|
return; // 2xx/3xx = success; 429/403/451 already handled
|
|
}
|
|
|
|
$fingerprint = $request->getVar('rateLimitFingerprint') ?? generateFingerprint(exclude_ua: true);
|
|
|
|
$identity = $request->getVar('rateLimitIdentity')
|
|
?? resolveIdentity($request);
|
|
|
|
// Record failure at IP level
|
|
$this->limiter->recordIpFailure($fingerprint);
|
|
|
|
// Record failure at user level
|
|
if ($identity) {
|
|
$this->limiter->recordUserFailure($identity, 'authApi');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build and return a JSON response for blocked/throttled requests.
|
|
*/
|
|
protected function jsonResponse(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;
|
|
}
|
|
}
|