Added ban management endpoints for the chat.

This commit is contained in:
flash 2021-03-02 19:40:17 +00:00
parent b7ff4f6a6e
commit 71e905c9a8
4 changed files with 138 additions and 4 deletions

View file

@ -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

View file

@ -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();

View file

@ -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);
}

View file

@ -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 {