Redis + RateLimiting
This commit is contained in:
@@ -5,6 +5,7 @@ namespace Core\API;
|
||||
use Core\Driver\Logger\Logger;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\DatabaseEntity\TwoFactorToken;
|
||||
use Core\Objects\RateLimiting;
|
||||
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
use PhpMqtt\Client\MqttClient;
|
||||
|
||||
@@ -23,6 +24,7 @@ abstract class Request {
|
||||
protected bool $isDisabled;
|
||||
protected bool $apiKeyAllowed;
|
||||
protected bool $csrfTokenRequired;
|
||||
protected ?RateLimiting $rateLimiting;
|
||||
|
||||
private array $defaultParams;
|
||||
private array $allowedMethods;
|
||||
@@ -47,6 +49,7 @@ abstract class Request {
|
||||
$this->apiKeyAllowed = true;
|
||||
$this->allowedMethods = array("GET", "POST");
|
||||
$this->csrfTokenRequired = true;
|
||||
$this->rateLimiting = null;
|
||||
}
|
||||
|
||||
public function getAPIName(): string {
|
||||
@@ -192,7 +195,26 @@ abstract class Request {
|
||||
$this->result['logoutIn'] = $session->getExpiresSeconds();
|
||||
}
|
||||
|
||||
if ($this->isDisabled) {
|
||||
$this->lastError = "This function is currently disabled.";
|
||||
http_response_code(503);
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
if ($sql === null || !$sql->isConnected()) {
|
||||
$this->lastError = $sql ? $sql->getLastError() : "Database not connected yet.";
|
||||
http_response_code(503);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->externalCall) {
|
||||
if (!$this->isPublic) {
|
||||
$this->lastError = 'This function is private.';
|
||||
http_response_code(403);
|
||||
return false;
|
||||
}
|
||||
|
||||
$values = $_REQUEST;
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && in_array("application/json", explode(";", $_SERVER["CONTENT_TYPE"] ?? ""))) {
|
||||
$jsonData = json_decode(file_get_contents('php://input'), true);
|
||||
@@ -204,21 +226,6 @@ abstract class Request {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isDisabled) {
|
||||
$this->lastError = "This function is currently disabled.";
|
||||
http_response_code(503);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->externalCall && !$this->isPublic) {
|
||||
$this->lastError = 'This function is private.';
|
||||
http_response_code(403);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->externalCall) {
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||
http_response_code(204); # No content
|
||||
@@ -292,10 +299,15 @@ abstract class Request {
|
||||
$this->parseVariableParams($values);
|
||||
}
|
||||
|
||||
$sql = $this->context->getSQL();
|
||||
if ($sql === null || !$sql->isConnected()) {
|
||||
$this->lastError = $sql ? $sql->getLastError() : "Database not connected yet.";
|
||||
return false;
|
||||
if ($this->externalCall && $this->rateLimiting) {
|
||||
$settings = $this->context->getSettings();
|
||||
if ($settings->isRateLimitingEnabled()) {
|
||||
if (!$this->rateLimiting->check($this->context, self::getEndpoint())) {
|
||||
http_response_code(429);
|
||||
$this->lastError = "Rate limit exceeded";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->success = true;
|
||||
|
||||
@@ -58,6 +58,8 @@ namespace Core\API\TFA {
|
||||
use Core\API\Parameter\StringType;
|
||||
use Core\API\TfaAPI;
|
||||
use Core\Objects\Context;
|
||||
use Core\Objects\RateLimiting;
|
||||
use Core\Objects\RateLimitRule;
|
||||
use Core\Objects\TwoFactor\AttestationObject;
|
||||
use Core\Objects\TwoFactor\AuthenticationData;
|
||||
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
@@ -214,6 +216,10 @@ namespace Core\API\TFA {
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
null,
|
||||
new RateLimitRule(5, 30, RateLimitRule::SECOND)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -347,6 +353,10 @@ namespace Core\API\TFA {
|
||||
]);
|
||||
$this->loginRequired = true;
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
null,
|
||||
new RateLimitRule(20, 60, RateLimitRule::SECOND)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
@@ -29,6 +29,7 @@ trait Pagination {
|
||||
$this->paginationCondition = $condition;
|
||||
$this->entityCount = call_user_func("$this->paginationClass::count", $sql, $condition, $joins);
|
||||
$this->pageSize = $this->getParam("count");
|
||||
$this->page = $this->getParam("page");
|
||||
if ($this->entityCount === false) {
|
||||
return $this->createError("Error fetching $this->paginationClass::count: " . $sql->getLastError());
|
||||
}
|
||||
|
||||
@@ -137,6 +137,8 @@ namespace Core\API\User {
|
||||
use Core\Driver\SQL\Condition\Compare;
|
||||
use Core\Driver\SQL\Condition\CondIn;
|
||||
use Core\Driver\SQL\Expression\JsonArrayAgg;
|
||||
use Core\Objects\RateLimiting;
|
||||
use Core\Objects\RateLimitRule;
|
||||
use Core\Objects\TwoFactor\KeyBasedTwoFactorToken;
|
||||
use ImagickException;
|
||||
use Core\Objects\Context;
|
||||
@@ -563,6 +565,9 @@ namespace Core\API\User {
|
||||
'token' => new StringType('token', 36)
|
||||
));
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(5, 1, RateLimitRule::MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -601,8 +606,6 @@ namespace Core\API\User {
|
||||
|
||||
class Login extends UserAPI {
|
||||
|
||||
private int $startedAt;
|
||||
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
'username' => new StringType('username'),
|
||||
@@ -610,13 +613,9 @@ namespace Core\API\User {
|
||||
'stayLoggedIn' => new Parameter('stayLoggedIn', Parameter::TYPE_BOOLEAN, true, false)
|
||||
));
|
||||
$this->forbidMethod("GET");
|
||||
}
|
||||
|
||||
private function wrongCredentials(): bool {
|
||||
$runtime = microtime(true) - $this->startedAt;
|
||||
$sleepTime = round(3e6 - $runtime);
|
||||
if ($sleepTime > 0) usleep($sleepTime);
|
||||
return $this->createError(L('Wrong username or password'));
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(10, 30, RateLimitRule::SECOND)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
@@ -635,7 +634,6 @@ namespace Core\API\User {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->startedAt = microtime(true);
|
||||
$this->success = false;
|
||||
$username = $this->getParam('username');
|
||||
$password = $this->getParam('password');
|
||||
@@ -648,7 +646,7 @@ namespace Core\API\User {
|
||||
|
||||
if ($user !== false) {
|
||||
if ($user === null) {
|
||||
return $this->wrongCredentials();
|
||||
return $this->createError(L('Wrong username or password'));
|
||||
} else if (!$user->isActive()) {
|
||||
return $this->createError("This user is currently disabled. Contact the server administrator, if you believe this is a mistake.");
|
||||
} else if (password_verify($password, $user->password)) {
|
||||
@@ -668,7 +666,7 @@ namespace Core\API\User {
|
||||
$this->success = true;
|
||||
}
|
||||
} else {
|
||||
return $this->wrongCredentials();
|
||||
return $this->createError(L('Wrong username or password'));
|
||||
}
|
||||
} else {
|
||||
return $this->createError("Error fetching user details: " . $sql->getLastError());
|
||||
@@ -1190,14 +1188,18 @@ namespace Core\API\User {
|
||||
class ResetPassword extends UserAPI {
|
||||
|
||||
public function __construct(Context $context, $externalCall = false) {
|
||||
parent::__construct($context, $externalCall, array(
|
||||
parent::__construct($context, $externalCall, [
|
||||
'token' => new StringType('token', 36),
|
||||
'password' => new StringType('password'),
|
||||
'confirmPassword' => new StringType('confirmPassword'),
|
||||
));
|
||||
]);
|
||||
|
||||
$this->forbidMethod("GET");
|
||||
$this->csrfTokenRequired = false;
|
||||
$this->apiKeyAllowed = false;
|
||||
$this->rateLimiting = new RateLimiting(
|
||||
new RateLimitRule(5, 1, RateLimitRule::MINUTE)
|
||||
);
|
||||
}
|
||||
|
||||
public function _execute(): bool {
|
||||
|
||||
Reference in New Issue
Block a user