From 2333924089906dfc71dd1907658e3a291eca5b82 Mon Sep 17 00:00:00 2001 From: mrilyew <99399973+mrilyew@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:29:18 +0300 Subject: [PATCH] add limitation errors to api --- VKAPI/Handlers/Friends.php | 4 ++++ VKAPI/Handlers/Gifts.php | 4 ++++ VKAPI/Handlers/Groups.php | 4 ++++ VKAPI/Handlers/VKAPIRequestHandler.php | 5 +++++ VKAPI/Handlers/Wall.php | 6 ++++- Web/Presenters/GiftsPresenter.php | 4 ++++ Web/Presenters/GroupPresenter.php | 4 ++++ Web/Util/EventRateLimiter.php | 31 +++++++++++++++++++++----- locales/en.strings | 2 ++ locales/ru.strings | 2 ++ openvk-example.yml | 3 ++- 11 files changed, 62 insertions(+), 7 deletions(-) diff --git a/VKAPI/Handlers/Friends.php b/VKAPI/Handlers/Friends.php index 77de7d9d..4c109e13 100644 --- a/VKAPI/Handlers/Friends.php +++ b/VKAPI/Handlers/Friends.php @@ -98,6 +98,10 @@ final class Friends extends VKAPIRequestHandler switch ($user->getSubscriptionStatus($this->getUser())) { case 0: + if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "friends.outgoing_sub")) { + $this->failTooOften(); + } + $user->toggleSubscription($this->getUser()); return 1; diff --git a/VKAPI/Handlers/Gifts.php b/VKAPI/Handlers/Gifts.php index 9ee5d222..460f11df 100644 --- a/VKAPI/Handlers/Gifts.php +++ b/VKAPI/Handlers/Gifts.php @@ -61,6 +61,10 @@ final class Gifts extends VKAPIRequestHandler $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) if (!$user || $user->isDeleted()) { diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index 8933ca5a..707dc0f4 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -312,6 +312,10 @@ final class Groups extends VKAPIRequestHandler $isMember = !is_null($this->getUser()) ? (int) $club->getSubscriptionStatus($this->getUser()) : 0; if ($isMember == 0) { + if (\openvk\Web\Util\EventRateLimiter::i()->tryToLimit($this->getUser(), "groups.sub")) { + $this->failTooOften(); + } + $club->toggleSubscription($this->getUser()); } diff --git a/VKAPI/Handlers/VKAPIRequestHandler.php b/VKAPI/Handlers/VKAPIRequestHandler.php index 4804a8dd..1f40fceb 100644 --- a/VKAPI/Handlers/VKAPIRequestHandler.php +++ b/VKAPI/Handlers/VKAPIRequestHandler.php @@ -25,6 +25,11 @@ abstract class VKAPIRequestHandler throw new APIErrorException($message, $code); } + protected function failTooOften(): never + { + $this->fail(9, "Rate limited"); + } + protected function getUser(): ?User { return $this->user; diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index a29fc754..5309f025 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -620,6 +620,10 @@ final class Wall extends VKAPIRequestHandler 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"]; if ($wallOwner instanceof Club && $from_group == 1 && $signed != 1 && $anon) { $manager = $wallOwner->getManager($this->getUser()); @@ -723,7 +727,7 @@ final class Wall extends VKAPIRequestHandler } 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); diff --git a/Web/Presenters/GiftsPresenter.php b/Web/Presenters/GiftsPresenter.php index 007be976..f945c342 100644 --- a/Web/Presenters/GiftsPresenter.php +++ b/Web/Presenters/GiftsPresenter.php @@ -106,6 +106,10 @@ final class GiftsPresenter extends OpenVKPresenter 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; $notification = new GiftNotification($user, $this->user->identity, $gift, $comment); $notification->emit(); diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 088dfc9d..57bdd8fd 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -63,6 +63,10 @@ final class GroupPresenter extends OpenVKPresenter if ($_SERVER["REQUEST_METHOD"] === "POST") { 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->setName($this->postParam("name")); $club->setAbout(empty($this->postParam("about")) ? null : $this->postParam("about")); diff --git a/Web/Util/EventRateLimiter.php b/Web/Util/EventRateLimiter.php index e39e3c61..4cce791d 100644 --- a/Web/Util/EventRateLimiter.php +++ b/Web/Util/EventRateLimiter.php @@ -38,20 +38,41 @@ class EventRateLimiter $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']) { return false; } - if ($user->isAdmin()) { + if (!($e = eventdb())) { 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']) { return false; @@ -65,7 +86,7 @@ class EventRateLimiter 'initiatorId' => $initiator->getId(), 'initiatorIp' => null, 'receiverId' => null, - 'eventType' => $event_name, + 'eventType' => $event_type, 'eventTime' => time() ]; diff --git a/locales/en.strings b/locales/en.strings index 1632b165..48576550 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -1657,6 +1657,8 @@ "error_geolocation" = "Error while trying to pin geolocation"; "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 */ "login_as" = "Login as $1"; diff --git a/locales/ru.strings b/locales/ru.strings index 6366b747..9d116554 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -1561,6 +1561,8 @@ "error_geolocation" = "Ошибка при прикреплении геометки"; "error_no_geotag" = "У поста не указана гео-метка"; +"limit_exceed_exception" = "Вы совершаете это действие слишком часто. Повторите позже."; + /* Admin actions */ "login_as" = "Войти как $1"; diff --git a/openvk-example.yml b/openvk-example.yml index 784711ba..c92d0e2f 100644 --- a/openvk-example.yml +++ b/openvk-example.yml @@ -43,12 +43,13 @@ openvk: autoban: true eventsLimit: enable: true + ignoreForAdmins: true restrictionTime: 86400 list: groups.create: 5 groups.sub: 50 friends.outgoing_sub: 25 - wall.post: 500 + wall.post: 5000 gifts.send: 20 blacklists: limit: 100