Sharp Chat interop updates.
This commit is contained in:
parent
18c836a457
commit
b9b80e17d0
3 changed files with 243 additions and 107 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit 1a8344c1c31cb62726305d079384045582315f64
|
Subproject commit bce5ba77a268ecd6338d0e3520e41ff4c40cbeda
|
|
@ -13,6 +13,7 @@ use Misuzu\Users\UserSession;
|
||||||
use Misuzu\Users\UserWarning;
|
use Misuzu\Users\UserWarning;
|
||||||
use Misuzu\Users\UserNotFoundException;
|
use Misuzu\Users\UserNotFoundException;
|
||||||
use Misuzu\Users\UserWarningCreationFailedException;
|
use Misuzu\Users\UserWarningCreationFailedException;
|
||||||
|
use Misuzu\Users\UserSessionNotFoundException;
|
||||||
|
|
||||||
final class SharpChatRoutes {
|
final class SharpChatRoutes {
|
||||||
private IConfig $config;
|
private IConfig $config;
|
||||||
|
@ -31,23 +32,43 @@ final class SharpChatRoutes {
|
||||||
$this->hashKey = $hashKey;
|
$this->hashKey = $hashKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simplify default error pages
|
||||||
|
$router->use('/_sockchat', function() use($router) {
|
||||||
|
$router->addErrorHandler(400, function($response) {
|
||||||
|
$response->setContent('HTTP 400');
|
||||||
|
});
|
||||||
|
$router->addErrorHandler(403, function($response) {
|
||||||
|
$response->setContent('HTTP 403');
|
||||||
|
});
|
||||||
|
$router->addErrorHandler(404, function($response) {
|
||||||
|
$response->setContent('HTTP 404');
|
||||||
|
});
|
||||||
|
$router->addErrorHandler(500, function($response) {
|
||||||
|
$response->setContent('HTTP 500');
|
||||||
|
});
|
||||||
|
$router->addErrorHandler(503, function($response) {
|
||||||
|
$response->setContent('HTTP 503');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Public endpoints
|
// Public endpoints
|
||||||
$router->get('/_sockchat/emotes', [$this, 'emotes']);
|
$router->get('/_sockchat/emotes', [$this, 'getEmotes']);
|
||||||
$router->get('/_sockchat/login', [$this, 'login']);
|
$router->get('/_sockchat/login', [$this, 'getLogin']);
|
||||||
$router->options('/_sockchat/token', [$this, 'token']);
|
$router->options('/_sockchat/token', [$this, 'getToken']);
|
||||||
$router->get('/_sockchat/token', [$this, 'token']);
|
$router->get('/_sockchat/token', [$this, 'getToken']);
|
||||||
|
|
||||||
// Private endpoints
|
// Private endpoints
|
||||||
$router->get('/_sockchat/resolve', [$this, 'resolve']);
|
$router->get('/_sockchat/resolve', [$this, 'getResolve']);
|
||||||
$router->post('/_sockchat/bump', [$this, 'bump']);
|
$router->post('/_sockchat/bump', [$this, 'postBump']);
|
||||||
$router->post('/_sockchat/verify', [$this, 'verify']);
|
$router->post('/_sockchat/verify', [$this, 'postVerify']);
|
||||||
$router->get('/_sockchat/bans', [$this, 'bans']);
|
$router->get('/_sockchat/bans', [$this, 'getBans']);
|
||||||
$router->get('/_sockchat/bans/check', [$this, 'checkBan']);
|
$router->get('/_sockchat/bans/list', [$this, 'getBanList']);
|
||||||
$router->post('/_sockchat/bans/create', [$this, 'createBan']);
|
$router->get('/_sockchat/bans/check', [$this, 'getBanCheck']);
|
||||||
$router->delete('/_sockchat/bans/remove', [$this, 'removeBan']);
|
$router->post('/_sockchat/bans/create', [$this, 'postBanCreate']);
|
||||||
|
$router->delete('/_sockchat/bans/revoke', [$this, 'deleteBanRevoke']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function emotes($response, $request): array {
|
public static function getEmotes($response, $request): array {
|
||||||
$response->setHeader('Access-Control-Allow-Origin', '*');
|
$response->setHeader('Access-Control-Allow-Origin', '*');
|
||||||
$response->setHeader('Access-Control-Allow-Methods', 'GET');
|
$response->setHeader('Access-Control-Allow-Methods', 'GET');
|
||||||
|
|
||||||
|
@ -70,7 +91,7 @@ final class SharpChatRoutes {
|
||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function login($response, $request): void {
|
public function getLogin($response, $request): void {
|
||||||
$currentUser = User::getCurrent();
|
$currentUser = User::getCurrent();
|
||||||
$configKey = $request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal';
|
$configKey = $request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal';
|
||||||
$chatPath = $this->config->getValue($configKey, IConfig::T_STR, '/');
|
$chatPath = $this->config->getValue($configKey, IConfig::T_STR, '/');
|
||||||
|
@ -82,7 +103,7 @@ final class SharpChatRoutes {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function token($response, $request) {
|
public function getToken($response, $request) {
|
||||||
$host = $request->hasHeader('Host') ? $request->getHeaderFirstLine('Host') : '';
|
$host = $request->hasHeader('Host') ? $request->getHeaderFirstLine('Host') : '';
|
||||||
$origin = $request->hasHeader('Origin') ? $request->getHeaderFirstLine('Origin') : '';
|
$origin = $request->hasHeader('Origin') ? $request->getHeaderFirstLine('Origin') : '';
|
||||||
$originHost = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
|
$originHost = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
|
||||||
|
@ -119,7 +140,7 @@ final class SharpChatRoutes {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function resolve($response, $request): array {
|
public function getResolve($response, $request): array {
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
||||||
$method = (string)$request->getParam('m');
|
$method = (string)$request->getParam('m');
|
||||||
|
@ -153,86 +174,121 @@ final class SharpChatRoutes {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bump($response, $request) {
|
public function postBump($response, $request) {
|
||||||
if(!$request->isStringContent())
|
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||||
return 400;
|
return 400;
|
||||||
|
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
if($request->isFormContent()) {
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
$content = $request->getContent();
|
||||||
$bumpString = (string)$request->getContent();
|
|
||||||
$realHash = hash_hmac('sha256', $bumpString, $this->hashKey);
|
|
||||||
|
|
||||||
|
$bumpList = $content->getParam('u', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
|
||||||
|
if(!is_array($bumpList))
|
||||||
|
return 400;
|
||||||
|
|
||||||
|
$userTime = (int)$content->getParam('t', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$signature = "bump#{$userTime}";
|
||||||
|
|
||||||
|
foreach($bumpList as $userId => $ipAddr)
|
||||||
|
$signature .= "#{$userId}:{$ipAddr}";
|
||||||
|
} else {
|
||||||
|
$bumpString = (string)$request->getContent();
|
||||||
|
$signature = $bumpString;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
|
||||||
|
$realHash = hash_hmac('sha256', $signature, $this->hashKey);
|
||||||
if(!hash_equals($realHash, $userHash))
|
if(!hash_equals($realHash, $userHash))
|
||||||
return;
|
return 403;
|
||||||
|
|
||||||
$bumpInfo = json_decode($bumpString);
|
if(empty($bumpString)) {
|
||||||
|
if($userTime < time() - 60)
|
||||||
|
return 403;
|
||||||
|
} else {
|
||||||
|
$bumpList = [];
|
||||||
|
$bumpInfo = json_decode($bumpString);
|
||||||
|
if(empty($bumpInfo))
|
||||||
|
return;
|
||||||
|
|
||||||
if(empty($bumpInfo))
|
foreach($bumpInfo as $bumpUser)
|
||||||
return;
|
if(!empty($bumpUser->id) && !empty($bumpUser->ip))
|
||||||
|
$bumpList[$bumpUser->id] = $bumpUser->ip;
|
||||||
|
}
|
||||||
|
|
||||||
foreach($bumpInfo as $bumpUser)
|
foreach($bumpList as $userId => $ipAddr)
|
||||||
try {
|
User::byId($userId)->bumpActivity($ipAddr);
|
||||||
User::byId($bumpUser->id)->bumpActivity($bumpUser->ip);
|
|
||||||
} catch(UserNotFoundException $ex) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function verify($response, $request): array {
|
public function postVerify($response, $request) {
|
||||||
|
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||||
|
return 400;
|
||||||
|
|
||||||
if($request->isStreamContent())
|
if($request->isStreamContent())
|
||||||
$authInfo = json_decode((string)$request->getContent());
|
$authInfo = json_decode((string)$request->getContent());
|
||||||
elseif($request->isJsonContent())
|
elseif($request->isJsonContent())
|
||||||
$authInfo = $request->getContent()->getContent(); // maybe change this api lol, this looks silly
|
$authInfo = $request->getContent()->getContent(); // maybe change this api lol, this looks silly
|
||||||
else
|
elseif($request->isFormContent()) {
|
||||||
|
$content = $request->getContent();
|
||||||
|
$authMethod = (string)$content->getParam('method');
|
||||||
|
$authToken = (string)$content->getParam('token');
|
||||||
|
$ipAddress = (string)$content->getParam('ipaddr');
|
||||||
|
} else
|
||||||
return ['success' => false, 'reason' => 'request'];
|
return ['success' => false, 'reason' => 'request'];
|
||||||
|
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
|
||||||
|
|
||||||
if(strlen($userHash) !== 64)
|
if(strlen($userHash) !== 64)
|
||||||
return ['success' => false, 'reason' => 'length'];
|
return ['success' => false, 'reason' => 'length'];
|
||||||
|
|
||||||
if(!isset($authInfo->user_id, $authInfo->token, $authInfo->ip))
|
if(isset($authInfo) && !empty($authInfo->token) && !empty($authInfo->ip)) {
|
||||||
|
// user_id is discarded now
|
||||||
|
// tokens should be entirely unique anyway
|
||||||
|
|
||||||
|
$tokenParts = explode(':', $authInfo->token, 2);
|
||||||
|
if(count($tokenParts) < 2) {
|
||||||
|
$authMethod = '';
|
||||||
|
$authToken = $tokenParts[0];
|
||||||
|
} else [$authMethod, $authToken] = $tokenParts;
|
||||||
|
|
||||||
|
$ipAddress = $authInfo->ip;
|
||||||
|
$signature = "{$authInfo->user_id}#{$authInfo->token}#{$authInfo->ip}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(empty($authMethod) || empty($authToken))
|
||||||
return ['success' => false, 'reason' => 'data'];
|
return ['success' => false, 'reason' => 'data'];
|
||||||
|
|
||||||
$realHash = hash_hmac('sha256', implode('#', [$authInfo->user_id, $authInfo->token, $authInfo->ip]), $this->hashKey);
|
if(!isset($signature))
|
||||||
|
$signature = "verify#{$authMethod}#{$authToken}#{$ipAddress}";
|
||||||
|
|
||||||
|
$realHash = hash_hmac('sha256', $signature, $this->hashKey);
|
||||||
if(!hash_equals($realHash, $userHash))
|
if(!hash_equals($realHash, $userHash))
|
||||||
return ['success' => false, 'reason' => 'hash'];
|
return ['success' => false, 'reason' => 'hash'];
|
||||||
|
|
||||||
try {
|
if($authMethod === 'SESS' || $authMethod === 'Misuzu') {
|
||||||
$userInfo = User::byId($authInfo->user_id);
|
$authTokenInfo = AuthToken::unpack($authToken);
|
||||||
} catch(UserNotFoundException $ex) {
|
if($authTokenInfo->isValid())
|
||||||
return ['success' => false, 'reason' => 'user'];
|
$authToken = $authTokenInfo->getSessionToken();
|
||||||
}
|
|
||||||
|
|
||||||
$authMethod = mb_substr($authInfo->token, 0, 5);
|
|
||||||
|
|
||||||
if($authMethod === 'SESS:') {
|
|
||||||
$sessionToken = mb_substr($authInfo->token, 5);
|
|
||||||
|
|
||||||
$authToken = AuthToken::unpack($sessionToken);
|
|
||||||
if($authToken->isValid())
|
|
||||||
$sessionToken = $authToken->getSessionToken();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$sessionInfo = UserSession::byToken($sessionToken);
|
$sessionInfo = UserSession::byToken($authToken);
|
||||||
} catch(UserSessionNotFoundException $ex) {
|
} catch(UserSessionNotFoundException $ex) {
|
||||||
return ['success' => false, 'reason' => 'token'];
|
return ['success' => false, 'reason' => 'token'];
|
||||||
}
|
}
|
||||||
|
|
||||||
if($sessionInfo->getUserId() !== $userInfo->getId())
|
|
||||||
return ['success' => false, 'reason' => 'user'];
|
|
||||||
|
|
||||||
if($sessionInfo->hasExpired()) {
|
if($sessionInfo->hasExpired()) {
|
||||||
$sessionInfo->delete();
|
$sessionInfo->delete();
|
||||||
return ['success' => false, 'reason' => 'expired'];
|
return ['success' => false, 'reason' => 'expired'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$sessionInfo->bump($authInfo->ip);
|
$sessionInfo->bump($ipAddress);
|
||||||
|
|
||||||
|
$userInfo = $sessionInfo->getUser();
|
||||||
} else {
|
} else {
|
||||||
return ['success' => false, 'reason' => 'unsupported'];
|
return ['success' => false, 'reason' => 'unsupported'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$userInfo->bumpActivity($authInfo->ip);
|
if(empty($userInfo))
|
||||||
|
return ['success' => false, 'reason' => 'user'];
|
||||||
|
|
||||||
|
$userInfo->bumpActivity($ipAddress);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'success' => true,
|
'success' => true,
|
||||||
|
@ -246,7 +302,7 @@ final class SharpChatRoutes {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function bans($response, $request): array {
|
public function getBans($response, $request): array {
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
||||||
$realHash = hash_hmac('sha256', 'givemethebeans', $this->hashKey);
|
$realHash = hash_hmac('sha256', 'givemethebeans', $this->hashKey);
|
||||||
|
@ -279,50 +335,105 @@ final class SharpChatRoutes {
|
||||||
return $bans;
|
return $bans;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function checkBan($response, $request): array {
|
public function getBanList($response, $request) {
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
|
||||||
$ipAddress = (string)$request->getParam('a');
|
|
||||||
$userId = (int)$request->getParam('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['user_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($response, $request): int {
|
|
||||||
if(!$request->isFormContent())
|
|
||||||
return 400;
|
return 400;
|
||||||
|
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
$userTime = (int)$request->getParam('x', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
|
$realHash = hash_hmac('sha256', "list#{$userTime}", $this->hashKey);
|
||||||
|
if(!hash_equals($realHash, $userHash) || $userTime < time() - 60)
|
||||||
|
return 403;
|
||||||
|
|
||||||
|
$warnings = UserWarning::byActive();
|
||||||
|
$bans = [];
|
||||||
|
|
||||||
|
foreach($warnings as $warning) {
|
||||||
|
if(!$warning->isBan() || $warning->hasExpired())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$isPerma = $warning->isPermanent();
|
||||||
|
$userInfo = $warning->getUser();
|
||||||
|
$bans[] = [
|
||||||
|
'is_ban' => true,
|
||||||
|
'user_id' => (string)$userInfo->getId(),
|
||||||
|
'user_name' => $userInfo->getUsername(),
|
||||||
|
'user_colour' => Colour::toMisuzu($userInfo->getColour()),
|
||||||
|
'ip_addr' => $warning->getUserRemoteAddress(),
|
||||||
|
'is_perma' => $isPerma,
|
||||||
|
'expires' => date('c', $isPerma ? 0xFFFFFFFF : $warning->getExpirationTime()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bans;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBanCheck($response, $request) {
|
||||||
|
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||||
|
return 400;
|
||||||
|
|
||||||
|
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
|
||||||
|
$userTime = (int)$request->getParam('x', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$ipAddress = (string)$request->getParam('a');
|
||||||
|
$userId = (string)$request->getParam('u');
|
||||||
|
$userIdIsName = (int)$request->getParam('n', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
|
||||||
|
$realHash = hash_hmac('sha256', "check#{$userTime}#{$userId}#{$ipAddress}#{$userIdIsName}", $this->hashKey);
|
||||||
|
if(!hash_equals($realHash, $userHash) || $userTime < time() - 60)
|
||||||
|
return 403;
|
||||||
|
|
||||||
|
if(!empty($ipAddress))
|
||||||
|
$warning = UserWarning::byRemoteAddressActive($ipAddress);
|
||||||
|
|
||||||
|
if(empty($warning) && !empty($userId)) {
|
||||||
|
if($userIdIsName)
|
||||||
|
try {
|
||||||
|
$userInfo = User::byUsername($userId);
|
||||||
|
$userId = $userInfo->getId();
|
||||||
|
} catch(UserNotFoundException $ex) {
|
||||||
|
$userId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$warning = UserWarning::byUserIdActive((int)$userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($warning === null)
|
||||||
|
return ['is_ban' => false];
|
||||||
|
|
||||||
|
$isPerma = $warning->isPermanent();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'is_ban' => true,
|
||||||
|
'user_id' => (string)$warning->getUserId(),
|
||||||
|
'ip_addr' => $warning->getUserRemoteAddress(),
|
||||||
|
'is_perma' => $isPerma,
|
||||||
|
'expires' => date('c', $isPerma ? 0xFFFFFFFF : $warning->getExpirationTime()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postBanCreate($response, $request): int {
|
||||||
|
if(!$request->hasHeader('X-SharpChat-Signature') || !$request->isFormContent())
|
||||||
|
return 400;
|
||||||
|
|
||||||
|
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
|
||||||
$content = $request->getContent();
|
$content = $request->getContent();
|
||||||
$userId = (int)$content->getParam('u', FILTER_SANITIZE_NUMBER_INT);
|
$userTime = (int)$content->getParam('t', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$modId = (int)$content->getParam('m', FILTER_SANITIZE_NUMBER_INT);
|
$userId = (string)$content->getParam('ui', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$userAddr = (string)$content->getParam('ua');
|
||||||
|
$modId = (string)$content->getParam('mi', FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$modAddr = (string)$content->getParam('ma');
|
||||||
$duration = (int)$content->getParam('d', FILTER_SANITIZE_NUMBER_INT);
|
$duration = (int)$content->getParam('d', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$isPermanent = (int)$content->getParam('p', FILTER_SANITIZE_NUMBER_INT);
|
$isPermanent = (int)$content->getParam('p', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$reason = (string)$content->getParam('r');
|
$reason = (string)$content->getParam('r');
|
||||||
|
|
||||||
$realHash = hash_hmac('sha256', "create#{$userId}#{$modId}#{$duration}#{$isPermanent}#{$reason}", $this->hashKey);
|
$signature = implode('#', [
|
||||||
if(!hash_equals($realHash, $userHash))
|
'create', $userTime, $userId, $userAddr,
|
||||||
|
$modId, $modAddr, $duration, $isPermanent, $reason,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$realHash = hash_hmac('sha256', $signature, $this->hashKey);
|
||||||
|
if(!hash_equals($realHash, $userHash) || $userTime < time() - 60)
|
||||||
return 403;
|
return 403;
|
||||||
|
|
||||||
if(empty($reason))
|
if(empty($reason))
|
||||||
|
@ -333,14 +444,21 @@ final class SharpChatRoutes {
|
||||||
elseif($duration < 1)
|
elseif($duration < 1)
|
||||||
return 400;
|
return 400;
|
||||||
|
|
||||||
|
// IPs cannot be banned on their own
|
||||||
|
// substituting with the unused Railgun account for now.
|
||||||
|
if(empty($targetId))
|
||||||
|
$targetId = 69;
|
||||||
|
if(empty($modId))
|
||||||
|
$modId = 69;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$userInfo = User::byId($userId);
|
$modInfo = User::byId((int)$modId);
|
||||||
} catch(UserNotFoundException $ex) {
|
} catch(UserNotFoundException $ex) {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$modInfo = User::byId($modId);
|
$userInfo = User::byId((int)$userId);
|
||||||
} catch(UserNotFoundException $ex) {
|
} catch(UserNotFoundException $ex) {
|
||||||
return 404;
|
return 404;
|
||||||
}
|
}
|
||||||
|
@ -351,7 +469,10 @@ final class SharpChatRoutes {
|
||||||
$modInfo,
|
$modInfo,
|
||||||
UserWarning::TYPE_BAHN,
|
UserWarning::TYPE_BAHN,
|
||||||
$duration,
|
$duration,
|
||||||
$reason
|
$reason,
|
||||||
|
null,
|
||||||
|
$userAddr,
|
||||||
|
$modAddr
|
||||||
);
|
);
|
||||||
} catch(UserWarningCreationFailedException $ex) {
|
} catch(UserWarningCreationFailedException $ex) {
|
||||||
return 500;
|
return 500;
|
||||||
|
@ -360,19 +481,22 @@ final class SharpChatRoutes {
|
||||||
return 201;
|
return 201;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function removeBan($response, $request): int {
|
public function deleteBanRevoke($response, $request): int {
|
||||||
$userHash = $request->hasHeader('X-SharpChat-Signature')
|
if(!$request->hasHeader('X-SharpChat-Signature'))
|
||||||
? $request->getHeaderFirstLine('X-SharpChat-Signature') : '';
|
return 400;
|
||||||
|
|
||||||
|
$userHash = (string)$request->getHeaderFirstLine('X-SharpChat-Signature');
|
||||||
|
$userTime = (int)$request->getParam('x', FILTER_SANITIZE_NUMBER_INT);
|
||||||
$type = (string)$request->getParam('t');
|
$type = (string)$request->getParam('t');
|
||||||
$subject = (string)$request->getParam('s');
|
$subject = (string)$request->getParam('s');
|
||||||
|
|
||||||
$realHash = hash_hmac('sha256', "remove#{$type}#{$subject}", $this->hashKey);
|
$realHash = hash_hmac('sha256', "revoke#{$userTime}#{$type}#{$subject}", $this->hashKey);
|
||||||
if(!hash_equals($realHash, $userHash))
|
if(!hash_equals($realHash, $userHash) || $userTime < time() - 60)
|
||||||
return 403;
|
return 403;
|
||||||
|
|
||||||
$warning = null;
|
$warning = null;
|
||||||
switch($type) {
|
switch($type) {
|
||||||
case 'ip':
|
case 'addr':
|
||||||
$warning = UserWarning::byRemoteAddressActive($subject);
|
$warning = UserWarning::byRemoteAddressActive($subject);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -162,7 +162,16 @@ class UserWarning {
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function create(User $user, User $issuer, int $type, int $duration, string $publicNote, ?string $privateNote = null): self {
|
public static function create(
|
||||||
|
User $user,
|
||||||
|
User $issuer,
|
||||||
|
int $type,
|
||||||
|
int $duration,
|
||||||
|
string $publicNote,
|
||||||
|
?string $privateNote = null,
|
||||||
|
?string $targetAddr = null,
|
||||||
|
?string $issuerAddr = null
|
||||||
|
): self {
|
||||||
if(!in_array($type, self::TYPES))
|
if(!in_array($type, self::TYPES))
|
||||||
throw new InvalidArgumentException('Type was invalid.');
|
throw new InvalidArgumentException('Type was invalid.');
|
||||||
|
|
||||||
|
@ -175,13 +184,16 @@ class UserWarning {
|
||||||
$duration = -1;
|
$duration = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$targetAddr ??= $user->getLastRemoteAddress();
|
||||||
|
$issuerAddr ??= $issuer->getLastRemoteAddress();
|
||||||
|
|
||||||
$warningId = DB::prepare(
|
$warningId = DB::prepare(
|
||||||
'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `user_ip`, `issuer_id`, `issuer_ip`, `warning_created`, `warning_duration`, `warning_type`, `warning_note`, `warning_note_private`)'
|
'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`user_id`, `user_ip`, `issuer_id`, `issuer_ip`, `warning_created`, `warning_duration`, `warning_type`, `warning_note`, `warning_note_private`)'
|
||||||
. ' VALUES (:user, INET6_ATON(:user_addr), :issuer, INET6_ATON(:issuer_addr), NOW(), IF(:set_duration, NOW() + INTERVAL :duration SECOND, NULL), :type, :public_note, :private_note)'
|
. ' VALUES (:user, INET6_ATON(:user_addr), :issuer, INET6_ATON(:issuer_addr), NOW(), IF(:set_duration, NOW() + INTERVAL :duration SECOND, NULL), :type, :public_note, :private_note)'
|
||||||
) ->bind('user', $user->getId())
|
) ->bind('user', $user->getId())
|
||||||
->bind('user_addr', $user->getLastRemoteAddress())
|
->bind('user_addr', $targetAddr)
|
||||||
->bind('issuer', $issuer->getId())
|
->bind('issuer', $issuer->getId())
|
||||||
->bind('issuer_addr', $issuer->getLastRemoteAddress())
|
->bind('issuer_addr', $issuerAddr)
|
||||||
->bind('set_duration', $duration > 0 ? 1 : 0)
|
->bind('set_duration', $duration > 0 ? 1 : 0)
|
||||||
->bind('duration', $duration)
|
->bind('duration', $duration)
|
||||||
->bind('type', $type)
|
->bind('type', $type)
|
||||||
|
|
Loading…
Reference in a new issue