From 71e905c9a8e77ca3b626536bad461ab1a4074136 Mon Sep 17 00:00:00 2001 From: flashwave Date: Tue, 2 Mar 2021 19:40:17 +0000 Subject: [PATCH] Added ban management endpoints for the chat. --- public/index.php | 6 +- src/Http/Handlers/SockChatHandler.php | 115 +++++++++++++++++++++++++- src/Http/Routing/Route.php | 3 + src/Users/UserWarning.php | 18 +++- 4 files changed, 138 insertions(+), 4 deletions(-) diff --git a/public/index.php b/public/index.php index 96cf3d7c..af2a482e 100644 --- a/public/index.php +++ b/public/index.php @@ -53,10 +53,14 @@ Router::addRoutes( Route::create(['GET', 'POST'], '/_sockchat.php', 'phpFile', 'SockChat'), Route::group('/_sockchat', 'SockChat')->addChildren( Route::get('/emotes', 'emotes'), - Route::get('/bans', 'bans'), Route::get('/login', 'login'), Route::post('/bump', 'bump'), Route::post('/verify', 'verify'), + Route::get('/bans', 'bans')->addChildren( + Route::get('/check', 'checkBan'), + Route::post('/create', 'createBan'), + Route::delete('/remove', 'removeBan'), + ), ), // Redirects diff --git a/src/Http/Handlers/SockChatHandler.php b/src/Http/Handlers/SockChatHandler.php index 898ddd33..8b94c81d 100644 --- a/src/Http/Handlers/SockChatHandler.php +++ b/src/Http/Handlers/SockChatHandler.php @@ -17,6 +17,7 @@ use Misuzu\Users\UserChatTokenCreationFailedException; use Misuzu\Users\UserSession; use Misuzu\Users\UserSessionNotFoundException; use Misuzu\Users\UserWarning; +use Misuzu\Users\UserWarningCreationFailedException; final class SockChatHandler extends Handler { private string $hashKey = 'woomy'; @@ -132,17 +133,127 @@ final class SockChatHandler extends Handler { if(!$warning->isBan() || $warning->hasExpired()) continue; + $isPermanent = $warning->isPermanent(); $bans[] = [ 'id' => $warning->getUser()->getId(), - 'expires' => date('c', $warning->isPermanent() ? 0x7FFFFFFF : $warning->getExpirationTime()), + 'expires' => date('c', $isPermanent ? 0x7FFFFFFF : $warning->getExpirationTime()), + 'is_permanent' => $isPermanent, 'ip' => $warning->getUserRemoteAddress(), - 'username' => $warning->getUser()->getUsername() + 'username' => $warning->getUser()->getUsername(), ]; } return $bans; } + public function checkBan(HttpResponse $response, HttpRequest $request): array { + $userHash = $request->getHeaderLine('X-SharpChat-Signature'); + $ipAddress = (string)$request->getQueryParam('a', FILTER_SANITIZE_STRING); + $userId = (int)$request->getQueryParam('u', FILTER_SANITIZE_NUMBER_INT); + + $realHash = hash_hmac('sha256', "check#{$ipAddress}#{$userId}", $this->hashKey); + if(!hash_equals($realHash, $userHash)) + return []; + + $response = []; + $warning = UserWarning::byRemoteAddressActive($ipAddress) + ?? UserWarning::byUserIdActive($userId); + + if($warning !== null) { + $response['warning'] = $warning->getId(); + $response['id'] = $warning->getUserId(); + $response['ip'] = $warning->getUserRemoteAddress(); + $response['is_permanent'] = $warning->isPermanent(); + $response['expires'] = date('c', $response['is_permanent'] ? 0x7FFFFFFF : $warning->getExpirationTime()); + } else { + $response['expires'] = date('c', 0); + $response['is_permanent'] = false; + } + + return $response; + } + + public function createBan(HttpResponse $response, HttpRequest $request): int { + $userHash = $request->getHeaderLine('X-SharpChat-Signature'); + $userId = (int)$request->getBodyParam('u', FILTER_SANITIZE_NUMBER_INT); + $modId = (int)$request->getBodyParam('m', FILTER_SANITIZE_NUMBER_INT); + $duration = (int)$request->getBodyParam('d', FILTER_SANITIZE_NUMBER_INT); + $isPermanent = (int)$request->getBodyParam('p', FILTER_SANITIZE_NUMBER_INT); + $reason = (string)$request->getBodyParam('r', FILTER_SANITIZE_STRING); + + $realHash = hash_hmac('sha256', "create#{$userId}#{$modId}#{$duration}#{$isPermanent}#{$reason}", $this->hashKey); + if(!hash_equals($realHash, $userHash)) + return 403; + + if(empty($reason)) + $reason = 'Banned through chat.'; + + if($isPermanent) + $duration = -1; + elseif($duration < 1) + return 400; + + try { + $userInfo = User::byId($userId); + } catch(UserNotFoundException $ex) { + return 404; + } + + try { + $modInfo = User::byId($modId); + } catch(UserNotFoundException $ex) { + return 404; + } + + try { + UserWarning::create( + $userInfo, + $modInfo, + UserWarning::TYPE_BAHN, + $duration, + $reason + ); + } catch(UserWarningCreationFailedException $ex) { + return 500; + } + + return 201; + } + + public function removeBan(HttpResponse $response, HttpRequest $request): int { + $userHash = $request->getHeaderLine('X-SharpChat-Signature'); + $type = (string)$request->getQueryParam('t', FILTER_SANITIZE_STRING); + $subject = (string)$request->getQueryParam('s', FILTER_SANITIZE_STRING); + + $realHash = hash_hmac('sha256', "remove#{$type}#{$subject}", $this->hashKey); + if(!hash_equals($realHash, $userHash)) + return 403; + + $warning = null; + switch($type) { + case 'ip': + $warning = UserWarning::byRemoteAddressActive($subject); + break; + + case 'user': + try { + $userInfo = User::byUsername($subject); + } catch(UserNotFoundException $ex) { + return 404; + } + + $warning = UserWarning::byUserIdActive($userInfo->getId()); + break; + } + + if($warning === null) + return 404; + + $warning->delete(); + + return 204; + } + public function login(HttpResponse $response, HttpRequest $request) { $currentUser = User::getCurrent(); diff --git a/src/Http/Routing/Route.php b/src/Http/Routing/Route.php index ccccb784..c9028a8b 100644 --- a/src/Http/Routing/Route.php +++ b/src/Http/Routing/Route.php @@ -32,6 +32,9 @@ class Route implements Serializable { public static function post(string $path, ?string $method = null, ?string $class = null): self { return self::create(['POST'], $path, $method, $class); } + public static function delete(string $path, ?string $method = null, ?string $class = null): self { + return self::create(['DELETE'], $path, $method, $class); + } public static function group(string $path, ?string $class = null): self { return self::create([''], $path, null, $class); } diff --git a/src/Users/UserWarning.php b/src/Users/UserWarning.php index 346c9315..143dd207 100644 --- a/src/Users/UserWarning.php +++ b/src/Users/UserWarning.php @@ -228,13 +228,29 @@ class UserWarning { return $object; } public static function byUserActive(User $user): ?self { + return self::byUserIdActive($user->getId()); + } + public static function byUserIdActive(int $userId): ?self { + if($userId < 1) + return null; + return DB::prepare( self::byQueryBase() . ' WHERE `user_id` = :user' . ' AND `warning_type` IN (' . implode(',', self::HAS_DURATION) . ')' . ' AND (`warning_duration` IS NULL OR `warning_duration` >= NOW())' . ' ORDER BY `warning_type` DESC, `warning_duration` DESC' - ) ->bind('user', $user->getId()) + ) ->bind('user', $userId) + ->fetchObject(self::class); + } + public static function byRemoteAddressActive(string $ipAddress): ?self { + return DB::prepare( + self::byQueryBase() + . ' WHERE `user_ip` = INET6_ATON(:address)' + . ' AND `warning_type` IN (' . implode(',', self::HAS_DURATION) . ')' + . ' AND (`warning_duration` IS NULL OR `warning_duration` >= NOW())' + . ' ORDER BY `warning_type` DESC, `warning_duration` DESC' + ) ->bind('address', $ipAddress) ->fetchObject(self::class); } public static function byProfile(User $user, ?User $viewer = null): array {