add limitation errors to api

This commit is contained in:
mrilyew 2025-06-09 17:29:18 +03:00
parent b180a28b46
commit 2333924089
11 changed files with 62 additions and 7 deletions

View file

@ -98,6 +98,10 @@ final class Friends extends VKAPIRequestHandler
switch ($user->getSubscriptionStatus($this->getUser())) { switch ($user->getSubscriptionStatus($this->getUser())) {
case 0: case 0:
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "friends.outgoing_sub")) {
$this->failTooOften();
}
$user->toggleSubscription($this->getUser()); $user->toggleSubscription($this->getUser());
return 1; return 1;

View file

@ -61,6 +61,10 @@ final class Gifts extends VKAPIRequestHandler
$this->fail(-105, "Commerce is disabled on this instance"); $this->fail(-105, "Commerce is disabled on this instance");
} }
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "gifts.send", false)) {
$this->failTooOften();
}
$user = (new UsersRepo())->get((int) $user_ids); # FAKE прогноз погоды (в данном случае user_ids) $user = (new UsersRepo())->get((int) $user_ids); # FAKE прогноз погоды (в данном случае user_ids)
if (!$user || $user->isDeleted()) { if (!$user || $user->isDeleted()) {

View file

@ -312,6 +312,10 @@ final class Groups extends VKAPIRequestHandler
$isMember = !is_null($this->getUser()) ? (int) $club->getSubscriptionStatus($this->getUser()) : 0; $isMember = !is_null($this->getUser()) ? (int) $club->getSubscriptionStatus($this->getUser()) : 0;
if ($isMember == 0) { if ($isMember == 0) {
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "groups.sub")) {
$this->failTooOften();
}
$club->toggleSubscription($this->getUser()); $club->toggleSubscription($this->getUser());
} }

View file

@ -25,6 +25,11 @@ abstract class VKAPIRequestHandler
throw new APIErrorException($message, $code); throw new APIErrorException($message, $code);
} }
protected function failTooOften(): never
{
$this->fail(9, "Rate limited");
}
protected function getUser(): ?User protected function getUser(): ?User
{ {
return $this->user; return $this->user;

View file

@ -620,6 +620,10 @@ final class Wall extends VKAPIRequestHandler
return (object) ["post_id" => $post->getVirtualId()]; return (object) ["post_id" => $post->getVirtualId()];
} }
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "wall.post", false)) {
$this->failTooOften();
}
$anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"];
if ($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) { if ($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) {
$manager = $wallOwner->getManager($this->getUser()); $manager = $wallOwner->getManager($this->getUser());
@ -723,7 +727,7 @@ final class Wall extends VKAPIRequestHandler
} }
if ($owner_id > 0 && $owner_id !== $this->getUser()->getId()) { if ($owner_id > 0 && $owner_id !== $this->getUser()->getId()) {
(new WallPostNotification($wallOwner, $post, $this->getUser()->getId()))->emit(); (new WallPostNotification($wallOwner, $post, $this->getUser()))->emit();
} }
\openvk\Web\Util\EventRateLimiter::i()->writeEvent("wall.post", $this->getUser(), $wallOwner); \openvk\Web\Util\EventRateLimiter::i()->writeEvent("wall.post", $this->getUser(), $wallOwner);

View file

@ -106,6 +106,10 @@ final class GiftsPresenter extends OpenVKPresenter
return; return;
} }
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "gifts.send", false)) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
$comment = empty($c = $this->postParam("comment")) ? null : $c; $comment = empty($c = $this->postParam("comment")) ? null : $c;
$notification = new GiftNotification($user, $this->user->identity, $gift, $comment); $notification = new GiftNotification($user, $this->user->identity, $gift, $comment);
$notification->emit(); $notification->emit();

View file

@ -63,6 +63,10 @@ final class GroupPresenter extends OpenVKPresenter
if ($_SERVER["REQUEST_METHOD"] === "POST") { if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!empty($this->postParam("name")) && mb_strlen(trim($this->postParam("name"))) > 0) { if (!empty($this->postParam("name")) && mb_strlen(trim($this->postParam("name"))) > 0) {
if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->user->identity, "groups.create")) {
$this->flashFail("err", tr("error"), tr("limit_exceed_exception"));
}
$club = new Club(); $club = new Club();
$club->setName($this->postParam("name")); $club->setName($this->postParam("name"));
$club->setAbout(empty($this->postParam("about")) ? null : $this->postParam("about")); $club->setAbout(empty($this->postParam("about")) ? null : $this->postParam("about"));

View file

@ -38,20 +38,41 @@ class EventRateLimiter
$this->availableFields = array_keys($this->config['list']); $this->availableFields = array_keys($this->config['list']);
} }
public function tryToLimit(?User $user): bool /*
Checks count of actions for last x seconds, returns true if limit has exceed
x is OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["eventsLimit"]["restrictionTime"]
*/
public function tryToLimit(?User $user, string $event_type, bool $distinct = true): bool
{ {
if (!$this->config['enable']) { if (!$this->config['enable']) {
return false; return false;
} }
if ($user->isAdmin()) { if (!($e = eventdb())) {
return false; return false;
} }
return true; if ($this->config['ignoreForAdmins'] && $user->isAdmin()) {
return false;
}
$limitForThisEvent = $this->config['list'][$event_type];
$compareTime = time() - $this->config['restrictionTime'];
$query = "SELECT COUNT(".($distinct ? "DISTINCT(`receiverId`)" : "*").") as `cnt` FROM `user-events` WHERE `initiatorId` = ? AND `eventType` = ? AND `eventTime` > ?";
$result = $e->getConnection()->query($query, ...[$user->getId(), $event_type, $compareTime]);
$count = $result->fetch()->cnt;
#bdump($count); exit();
return $count > $limitForThisEvent;
} }
public function writeEvent(string $event_name, User $initiator, ?RowModel $reciever = null): bool /*
Writes new event to `openvk-eventdb`.`user-events`
*/
public function writeEvent(string $event_type, User $initiator, ?RowModel $reciever = null): bool
{ {
if (!$this->config['enable']) { if (!$this->config['enable']) {
return false; return false;
@ -65,7 +86,7 @@ class EventRateLimiter
'initiatorId' => $initiator->getId(), 'initiatorId' => $initiator->getId(),
'initiatorIp' => null, 'initiatorIp' => null,
'receiverId' => null, 'receiverId' => null,
'eventType' => $event_name, 'eventType' => $event_type,
'eventTime' => time() 'eventTime' => time()
]; ];

View file

@ -1657,6 +1657,8 @@
"error_geolocation" = "Error while trying to pin geolocation"; "error_geolocation" = "Error while trying to pin geolocation";
"error_no_geotag" = "There is no geo-tag pinned in this post"; "error_no_geotag" = "There is no geo-tag pinned in this post";
"limit_exceed_exception" = "You're doing this action too often. Try again later.";
/* Admin actions */ /* Admin actions */
"login_as" = "Login as $1"; "login_as" = "Login as $1";

View file

@ -1561,6 +1561,8 @@
"error_geolocation" = "Ошибка при прикреплении геометки"; "error_geolocation" = "Ошибка при прикреплении геометки";
"error_no_geotag" = "У поста не указана гео-метка"; "error_no_geotag" = "У поста не указана гео-метка";
"limit_exceed_exception" = "Вы совершаете это действие слишком часто. Повторите позже.";
/* Admin actions */ /* Admin actions */
"login_as" = "Войти как $1"; "login_as" = "Войти как $1";

View file

@ -43,12 +43,13 @@ openvk:
autoban: true autoban: true
eventsLimit: eventsLimit:
enable: true enable: true
ignoreForAdmins: true
restrictionTime: 86400 restrictionTime: 86400
list: list:
groups.create: 5 groups.create: 5
groups.sub: 50 groups.sub: 50
friends.outgoing_sub: 25 friends.outgoing_sub: 25
wall.post: 500 wall.post: 5000
gifts.send: 20 gifts.send: 20
blacklists: blacklists:
limit: 100 limit: 100