Removed code related to managing server whitelists.
This commit is contained in:
parent
69799c6422
commit
9e95366336
12 changed files with 61 additions and 763 deletions
10
database/2023_08_23_233728_remove_whitelist_tables.php
Normal file
10
database/2023_08_23_233728_remove_whitelist_tables.php
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
use Index\Data\IDbConnection;
|
||||||
|
use Index\Data\Migration\IDbMigration;
|
||||||
|
|
||||||
|
final class RemoveWhitelistTables_20230823_233728 implements IDbMigration {
|
||||||
|
public function migrate(IDbConnection $conn): void {
|
||||||
|
$conn->execute('DROP TABLE whitelist');
|
||||||
|
$conn->execute('DROP TABLE whitelist_2022');
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,5 +31,3 @@ try {
|
||||||
}
|
}
|
||||||
|
|
||||||
$db->execute('SET SESSION time_zone = "+00:00", sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"');
|
$db->execute('SET SESSION time_zone = "+00:00", sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION"');
|
||||||
|
|
||||||
$remote = new RemoteV2($config['remotev2_url'], $config['remotev2_secret']);
|
|
||||||
|
|
|
@ -1,294 +0,0 @@
|
||||||
<?php
|
|
||||||
// this is the source for the script that RemoteV2 interacts with
|
|
||||||
|
|
||||||
define('SRV_REQ_SEC', 'secret key goes here');
|
|
||||||
define('SRV_REQ_LIFE', 60);
|
|
||||||
define('SRV_DIR_FMT', '/srv/minecraft/%s');
|
|
||||||
|
|
||||||
function base64url_encode(string $input): string {
|
|
||||||
return rtrim(strtr(base64_encode($input), '+/', '-_'), '=');
|
|
||||||
}
|
|
||||||
|
|
||||||
function base64url_decode(string $input): string {
|
|
||||||
return base64_decode(str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT));
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_open(int $port) {
|
|
||||||
$conn = fsockopen('localhost', $port, $code, $message, 2);
|
|
||||||
return $conn;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_close($conn) {
|
|
||||||
if(is_resource($conn))
|
|
||||||
fclose($conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_recv($conn): array {
|
|
||||||
if(!is_resource($conn))
|
|
||||||
return ['error' => ':rcon:conn'];
|
|
||||||
|
|
||||||
extract(unpack('Vlength', fread($conn, 4)));
|
|
||||||
if($length < 10)
|
|
||||||
return ['error' => ':rcon:length'];
|
|
||||||
|
|
||||||
extract(unpack('VreqId/Vopcode', fread($conn, 8)));
|
|
||||||
$body = substr(fread($conn, $length - 8), 0, -2);
|
|
||||||
|
|
||||||
return compact('reqId', 'opcode', 'body');
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_send($conn, int $opcode, string $text): int {
|
|
||||||
if(!is_resource($conn))
|
|
||||||
return -1;
|
|
||||||
$length = 10 + strlen($text);
|
|
||||||
$reqId = random_int(1, 0x7FFFFFFF);
|
|
||||||
fwrite($conn, pack('VVV', $length, $reqId, $opcode) . $text . "\0\0");
|
|
||||||
return $reqId;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_send_large($conn, int $opcode, string $text): array {
|
|
||||||
if(!is_resource($conn))
|
|
||||||
return ['error' => ':rcon:conn'];
|
|
||||||
|
|
||||||
$reqId = rcon_send($conn, $opcode, $text);
|
|
||||||
if($reqId < 1) return ['error' => ':rcon:request'];
|
|
||||||
|
|
||||||
$trailer = rcon_send($conn, 2, 'time query gametime');
|
|
||||||
if($trailer < 1) return ['error' => ':rcon:trailer'];
|
|
||||||
|
|
||||||
$opcode = 0;
|
|
||||||
$body = '';
|
|
||||||
|
|
||||||
for(;;) {
|
|
||||||
$resp = rcon_recv($conn);
|
|
||||||
if(!empty($resp['error']))
|
|
||||||
return $resp;
|
|
||||||
|
|
||||||
if($resp['reqId'] === $trailer) break;
|
|
||||||
if($resp['reqId'] !== $reqId) continue;
|
|
||||||
|
|
||||||
if($resp['opcode'] !== 0)
|
|
||||||
return ['error' => ':rcon:opcode'];
|
|
||||||
|
|
||||||
$body .= $resp['body'];
|
|
||||||
}
|
|
||||||
|
|
||||||
return compact('reqId', 'trailer', 'opcode', 'body');
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_open_props(array $props) {
|
|
||||||
if(empty($props['rcon.port']))
|
|
||||||
die('{"error":":conf:rcon-port"}');
|
|
||||||
if(empty($props['rcon.password']))
|
|
||||||
die('{"error":":conf:rcon-passwd"}');
|
|
||||||
|
|
||||||
return rcon_open($props['rcon.port']);
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_auth_props($conn, array $props): void {
|
|
||||||
if(empty($props['rcon.password']))
|
|
||||||
die('{"error":":conf:rcon-passwd"}');
|
|
||||||
|
|
||||||
rcon_send($conn, 3, $props['rcon.password']);
|
|
||||||
$resp = rcon_recv($conn);
|
|
||||||
if(!empty($resp['error']))
|
|
||||||
die(json_encode(['error' => $resp['error']]));
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_get_whitelist($conn): array {
|
|
||||||
$resp = rcon_send_large($conn, 2, 'whitelist list');
|
|
||||||
if(!empty($resp['error'])) return $resp;
|
|
||||||
|
|
||||||
$halfs = explode(':', $resp['body'], 2);
|
|
||||||
if(empty($halfs[1])) return [];
|
|
||||||
|
|
||||||
$names = explode(',', $halfs[1]);
|
|
||||||
foreach($names as &$name)
|
|
||||||
$name = trim($name);
|
|
||||||
|
|
||||||
return array_values(array_filter($names));
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_add_whitelist($conn, array $userNames): array {
|
|
||||||
$results = [];
|
|
||||||
|
|
||||||
foreach($userNames as $name) {
|
|
||||||
rcon_send($conn, 2, 'whitelist add ' . $name); // todo: sanitise username
|
|
||||||
$resp = rcon_recv($conn);
|
|
||||||
|
|
||||||
$results[$name] = [
|
|
||||||
'success' => str_starts_with($resp['body'], 'Added '),
|
|
||||||
'message' => $resp['body'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
rcon_send($conn, 2, 'whitelist reload');
|
|
||||||
rcon_recv($conn); // discard reload message
|
|
||||||
|
|
||||||
return compact('results');
|
|
||||||
}
|
|
||||||
|
|
||||||
function rcon_remove_whitelist($conn, array $userNames): array {
|
|
||||||
$results = [];
|
|
||||||
|
|
||||||
foreach($userNames as $name) {
|
|
||||||
rcon_send($conn, 2, 'whitelist remove ' . $name); // todo: sanitise username
|
|
||||||
$resp = rcon_recv($conn);
|
|
||||||
|
|
||||||
$results[$name] = [
|
|
||||||
'success' => str_starts_with($resp['body'], 'Removed '),
|
|
||||||
'message' => $resp['body'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
rcon_send($conn, 2, 'whitelist reload');
|
|
||||||
rcon_recv($conn); // discard reload message
|
|
||||||
|
|
||||||
return compact('results');
|
|
||||||
}
|
|
||||||
|
|
||||||
function read_server_props(string $serverPath, string $fileName = 'server.properties'): array {
|
|
||||||
$props = [];
|
|
||||||
|
|
||||||
$path = realpath($serverPath . '/' . $fileName);
|
|
||||||
if(is_file($path)) {
|
|
||||||
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
|
||||||
foreach($lines as $line) {
|
|
||||||
if(empty($line) || str_starts_with($line, '#'))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
$parts = explode('=', $line, 2);
|
|
||||||
if(count($parts) == 2) {
|
|
||||||
$value = $parts[1];
|
|
||||||
if($value === 'false' || $value === 'true')
|
|
||||||
$value = $value === 'true';
|
|
||||||
elseif(ctype_digit($value))
|
|
||||||
$value = (int)$value;
|
|
||||||
elseif(is_numeric($value))
|
|
||||||
$value = (float)$value;
|
|
||||||
|
|
||||||
$props[$parts[0]] = $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $props;
|
|
||||||
}
|
|
||||||
|
|
||||||
function create_request_hash(int $time = -1, ?array $params = null, ?string $method = null, ?string $path = null): string {
|
|
||||||
if($time < 0)
|
|
||||||
$time = time();
|
|
||||||
if($params === null)
|
|
||||||
$params = $_REQUEST ?? [];
|
|
||||||
if($method === null)
|
|
||||||
$method = $GLOBALS['reqMethod'] ?? '';
|
|
||||||
if($path === null)
|
|
||||||
$path = $GLOBALS['reqPath'] ?? '';
|
|
||||||
|
|
||||||
ksort($params);
|
|
||||||
$compare = [];
|
|
||||||
|
|
||||||
$stringify = null; // use() gets sad if it's not defined yet
|
|
||||||
$stringify = function(array $arr, string $prefix = '') use(&$compare, &$stringify) {
|
|
||||||
foreach($arr as $name => $value) {
|
|
||||||
if(is_array($value))
|
|
||||||
$stringify($value, $name . ';');
|
|
||||||
else
|
|
||||||
$compare[] = "{$name}:{$value}";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$stringify($params);
|
|
||||||
$input = "{$time}%{$method} {$path}%" . implode('#', $compare);
|
|
||||||
|
|
||||||
return hash_hmac('sha256', $input, SRV_REQ_SEC, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
function verify_request_hash(): bool {
|
|
||||||
$realTime = time();
|
|
||||||
$lifeTimeHalf = (int)ceil(SRV_REQ_LIFE / 2);
|
|
||||||
|
|
||||||
$userHash = base64url_decode((string)filter_input(INPUT_SERVER, 'HTTP_X_MINCE_SIGNATURE'));
|
|
||||||
$userTime = (int)filter_input(INPUT_SERVER, 'HTTP_X_MINCE_TIMESTAMP', FILTER_SANITIZE_NUMBER_INT);
|
|
||||||
if(strlen($userHash) !== 32 || $userTime < ($realTime - $lifeTimeHalf) || $userTime > ($realTime + $lifeTimeHalf))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return hash_equals(create_request_hash($userTime), $userHash);
|
|
||||||
}
|
|
||||||
|
|
||||||
function get_server_path(string $serverId): string {
|
|
||||||
if(empty($serverId) || !ctype_alnum($serverId))
|
|
||||||
die('{"error":":req:id"}');
|
|
||||||
|
|
||||||
$path = sprintf(SRV_DIR_FMT, $serverId);
|
|
||||||
if(!is_dir($path))
|
|
||||||
die('{"error":":req:server"}');
|
|
||||||
|
|
||||||
return $path;
|
|
||||||
}
|
|
||||||
|
|
||||||
$reqMethod = (string)filter_input(INPUT_SERVER, 'REQUEST_METHOD');
|
|
||||||
$reqPath = '/' . trim(parse_url((string)filter_input(INPUT_SERVER, 'REQUEST_URI'), PHP_URL_PATH), '/');
|
|
||||||
|
|
||||||
header('Content-Type: application/json; charset=utf-8');
|
|
||||||
|
|
||||||
if(!verify_request_hash())
|
|
||||||
die('{"error":":request:verification"}');
|
|
||||||
|
|
||||||
if($reqMethod === 'GET' && $reqPath === '/') {
|
|
||||||
$dirs = glob(__DIR__ . '/../*');
|
|
||||||
$servers = [];
|
|
||||||
|
|
||||||
foreach($dirs as $dir)
|
|
||||||
if(is_file($dir . '/server.properties') && !is_file($dir . '/.dead'))
|
|
||||||
$servers[] = basename($dir);
|
|
||||||
|
|
||||||
echo json_encode(compact('servers'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($reqMethod === 'GET' && $reqPath === '/whitelist') {
|
|
||||||
$sPath = get_server_path((string)filter_input(INPUT_GET, 'server'));
|
|
||||||
$sProps = read_server_props($sPath);
|
|
||||||
$rcon = rcon_open_props($sProps);
|
|
||||||
|
|
||||||
try {
|
|
||||||
rcon_auth_props($rcon, $sProps);
|
|
||||||
echo json_encode(['list' => rcon_get_whitelist($rcon)]);
|
|
||||||
} finally {
|
|
||||||
rcon_close($rcon);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($reqMethod === 'POST' && $reqPath === '/whitelist') {
|
|
||||||
$wNames = json_decode((string)filter_input(INPUT_POST, 'names'));
|
|
||||||
$sPath = get_server_path((string)filter_input(INPUT_POST, 'server'));
|
|
||||||
$sProps = read_server_props($sPath);
|
|
||||||
$rcon = rcon_open_props($sProps);
|
|
||||||
|
|
||||||
try {
|
|
||||||
rcon_auth_props($rcon, $sProps);
|
|
||||||
echo json_encode(rcon_add_whitelist($rcon, $wNames));
|
|
||||||
} finally {
|
|
||||||
rcon_close($rcon);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($reqMethod === 'DELETE' && $reqPath === '/whitelist') {
|
|
||||||
$wNames = json_decode((string)filter_input(INPUT_GET, 'names'));
|
|
||||||
$sPath = get_server_path((string)filter_input(INPUT_GET, 'server'));
|
|
||||||
$sProps = read_server_props($sPath);
|
|
||||||
$rcon = rcon_open_props($sProps);
|
|
||||||
|
|
||||||
try {
|
|
||||||
rcon_auth_props($rcon, $sProps);
|
|
||||||
echo json_encode(rcon_remove_whitelist($rcon, $wNames));
|
|
||||||
} finally {
|
|
||||||
rcon_close($rcon);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo '{"error":":request:notfound"}';
|
|
|
@ -8,7 +8,7 @@ require_once __DIR__ . '/../mince.php';
|
||||||
|
|
||||||
// replace this with id.flashii.net shit
|
// replace this with id.flashii.net shit
|
||||||
$authToken = (string)filter_input(INPUT_COOKIE, 'msz_auth');
|
$authToken = (string)filter_input(INPUT_COOKIE, 'msz_auth');
|
||||||
$authInfo = ChatAuth::attempt($db, $config['chat_endpoint'], $config['chat_secret'], $authToken);
|
$authInfo = ChatAuth::attempt($config['chat_endpoint'], $config['chat_secret'], $authToken);
|
||||||
|
|
||||||
$users = new Users($db);
|
$users = new Users($db);
|
||||||
if($authInfo->success) {
|
if($authInfo->success) {
|
||||||
|
@ -29,7 +29,6 @@ $templating->addVars([
|
||||||
'title' => 'Flashii Minecraft Servers',
|
'title' => 'Flashii Minecraft Servers',
|
||||||
],
|
],
|
||||||
'is_authed' => $userInfo !== null,
|
'is_authed' => $userInfo !== null,
|
||||||
'auth' => $authInfo,
|
|
||||||
'user' => $userInfo,
|
'user' => $userInfo,
|
||||||
'csrfp' => $csrfp->createToken(),
|
'csrfp' => $csrfp->createToken(),
|
||||||
]);
|
]);
|
||||||
|
@ -58,7 +57,6 @@ $router->setDefaultErrorHandler(function($response, $request, $code, $text) use
|
||||||
(new HomeRoutes(new Servers($db), $templating, $authInfo, $config['login_url']))->register($router);
|
(new HomeRoutes(new Servers($db), $templating, $authInfo, $config['login_url']))->register($router);
|
||||||
(new ClientsRoutes($templating, $accountLinks, $authorisations, $verifications, $csrfp, $authInfo))->register($router);
|
(new ClientsRoutes($templating, $accountLinks, $authorisations, $verifications, $csrfp, $authInfo))->register($router);
|
||||||
(new SkinsRoutes($templating, $accountLinks, new Skins($db), new Capes($db), $csrfp, $authInfo, $config['skins_base_url']))->register($router);
|
(new SkinsRoutes($templating, $accountLinks, new Skins($db), new Capes($db), $csrfp, $authInfo, $config['skins_base_url']))->register($router);
|
||||||
(new WhitelistRoutes(new Whitelist($db), $csrfp, $authInfo))->register($router);
|
|
||||||
|
|
||||||
MojangInterop::registerRoutes($router);
|
MojangInterop::registerRoutes($router);
|
||||||
|
|
||||||
|
|
|
@ -103,8 +103,7 @@ h1, h2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-btn-green,
|
.form-btn-green,
|
||||||
.acclink input[type="submit"],
|
.acclink input[type="submit"] {
|
||||||
.whitelist input[type="submit"] {
|
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -117,20 +116,16 @@ h1, h2 {
|
||||||
.form-btn-green:hover,
|
.form-btn-green:hover,
|
||||||
.form-btn-green:focus,
|
.form-btn-green:focus,
|
||||||
.acclink input[type="submit"]:hover,
|
.acclink input[type="submit"]:hover,
|
||||||
.acclink input[type="submit"]:focus,
|
.acclink input[type="submit"]:focus {
|
||||||
.whitelist input[type="submit"]:hover,
|
|
||||||
.whitelist input[type="submit"]:focus {
|
|
||||||
background-color: #272;
|
background-color: #272;
|
||||||
}
|
}
|
||||||
.form-btn-green:active,
|
.form-btn-green:active,
|
||||||
.acclink input[type="submit"]:active,
|
.acclink input[type="submit"]:active {
|
||||||
.whitelist input[type="submit"]:active {
|
|
||||||
background-color: #232;
|
background-color: #232;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-btn-red,
|
.form-btn-red,
|
||||||
.accunlink input[type="submit"],
|
.accunlink input[type="submit"] {
|
||||||
.unwhitelist input[type="submit"] {
|
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
margin: 5px;
|
margin: 5px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
@ -143,14 +138,11 @@ h1, h2 {
|
||||||
.form-btn-red:hover,
|
.form-btn-red:hover,
|
||||||
.form-btn-red:focus,
|
.form-btn-red:focus,
|
||||||
.accunlink input[type="submit"]:hover,
|
.accunlink input[type="submit"]:hover,
|
||||||
.accunlink input[type="submit"]:focus,
|
.accunlink input[type="submit"]:focus {
|
||||||
.unwhitelist input[type="submit"]:hover,
|
|
||||||
.unwhitelist input[type="submit"]:focus {
|
|
||||||
background-color: #722;
|
background-color: #722;
|
||||||
}
|
}
|
||||||
.form-btn-red:active,
|
.form-btn-red:active,
|
||||||
.accunlink input[type="submit"]:active,
|
.accunlink input[type="submit"]:active {
|
||||||
.unwhitelist input[type="submit"]:active {
|
|
||||||
background-color: #322;
|
background-color: #322;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
namespace Mince;
|
namespace Mince;
|
||||||
|
|
||||||
use stdClass;
|
use stdClass;
|
||||||
use Index\Data\IDbConnection;
|
|
||||||
|
|
||||||
final class ChatAuth {
|
final class ChatAuth {
|
||||||
public static function attempt(IDbConnection $db, string $endPoint, string $secret, string $cookie): object {
|
public static function attempt(string $endPoint, string $secret, string $cookie): object {
|
||||||
if(!empty($cookie)) {
|
if(!empty($cookie)) {
|
||||||
$method = 'Misuzu';
|
$method = 'Misuzu';
|
||||||
$signature = sprintf('verify#%s#%s#%s', $method, $cookie, $_SERVER['REMOTE_ADDR']);
|
$signature = sprintf('verify#%s#%s#%s', $method, $cookie, $_SERVER['REMOTE_ADDR']);
|
||||||
|
@ -48,21 +47,6 @@ final class ChatAuth {
|
||||||
$userInfo->rank = 0;
|
$userInfo->rank = 0;
|
||||||
$userInfo->hierarchy = 0;
|
$userInfo->hierarchy = 0;
|
||||||
$userInfo->perms = 0;
|
$userInfo->perms = 0;
|
||||||
$userInfo->mc_username = null;
|
|
||||||
$userInfo->mc_whitelisted = 0;
|
|
||||||
} else {
|
|
||||||
$getWhitelist = $db->prepare('SELECT `minecraft_username`, UNIX_TIMESTAMP(`whitelist_added`) AS `whitelist_added` FROM `whitelist_2022` WHERE `flashii_id` = ?');
|
|
||||||
$getWhitelist->addParameter(1, $userInfo->user_id);
|
|
||||||
$getWhitelist->execute();
|
|
||||||
$whitelist = $getWhitelist->getResult();
|
|
||||||
|
|
||||||
if($whitelist->next()) {
|
|
||||||
$userInfo->mc_username = $whitelist->getString(0);
|
|
||||||
$userInfo->mc_whitelisted = $whitelist->getInteger(1);
|
|
||||||
} else {
|
|
||||||
$userInfo->mc_username = null;
|
|
||||||
$userInfo->mc_whitelisted = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $userInfo;
|
return $userInfo;
|
||||||
|
|
|
@ -22,44 +22,8 @@ class HomeRoutes {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getIndex($response, $request) {
|
public function getIndex($response, $request) {
|
||||||
$name = (string)$request->getParam('name');
|
|
||||||
$error = (string)$request->getParam('error');
|
|
||||||
|
|
||||||
if(!empty($error) && ctype_lower($error)) {
|
|
||||||
$errors = [
|
|
||||||
'request' => ['Invalid request type.', 'Try to reload the page and try again.'],
|
|
||||||
'verify' => ['Request verification failed.', 'Try to reload the page and try again.'],
|
|
||||||
'itainthappenin' => ['Haha', 'No'],
|
|
||||||
'short' => ['Invalid username', 'The provided name is too short.'],
|
|
||||||
'long' => ['Invalid username', 'The provided name is too long.'],
|
|
||||||
'invalid' => ['Invalid username', 'The provided name contains invalid characters.'],
|
|
||||||
'conflict' => ['Username conflict', 'This username is already whitelisted with someone, contact flashwave if this is unexpected.'],
|
|
||||||
'connect' => ['Failed to connect to the server', 'The server is probably offline, pope flashwave if this is not expected.'],
|
|
||||||
'not-listed' => ['You have not been whitelisted yet', 'Add yourself to the whitelist before trying to remove yourself from it.'],
|
|
||||||
];
|
|
||||||
|
|
||||||
if(array_key_exists($error, $errors)) {
|
|
||||||
$errTitle = $errors[$error][0];
|
|
||||||
$errBody = $errors[$error][1];
|
|
||||||
} else {
|
|
||||||
$errTitle = 'Unexpected response from server';
|
|
||||||
$errBody = $error;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->templating->addVars([
|
|
||||||
'error' => [
|
|
||||||
'title' => $errTitle,
|
|
||||||
'body' => $errBody,
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if($this->userInfo->mc_whitelisted > 0)
|
|
||||||
$this->templating->setVar('whitelist_pending', floor($this->userInfo->mc_whitelisted / 300) === floor(time() / 300));
|
|
||||||
|
|
||||||
return $this->templating->render('index', [
|
return $this->templating->render('index', [
|
||||||
'servers' => $this->servers->getServers(deleted: false),
|
'servers' => $this->servers->getServers(deleted: false),
|
||||||
'wladdform_username' => $name,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
131
src/RemoteV2.php
131
src/RemoteV2.php
|
@ -1,131 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Mince;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
use Index\Serialisation\UriBase64;
|
|
||||||
|
|
||||||
class RemoteV2 {
|
|
||||||
public function __construct(
|
|
||||||
private string $endPoint,
|
|
||||||
private string $secretKey
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getInfo(): object {
|
|
||||||
return $this->getRequest('GET', '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getWhitelist(string $serverId): object {
|
|
||||||
return $this->getRequest('GET', '/whitelist', ['server' => $serverId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addToWhitelist(string $serverId, array $userNames): object {
|
|
||||||
return $this->postRequest('POST', '/whitelist', [
|
|
||||||
'server' => $serverId,
|
|
||||||
'names' => json_encode($userNames),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeFromWhitelist(string $serverId, array $userNames): object {
|
|
||||||
return $this->getRequest('DELETE', '/whitelist', [
|
|
||||||
'server' => $serverId,
|
|
||||||
'names' => json_encode($userNames),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function createSignature(string $method, string $path, array $params, int $time = -1): string {
|
|
||||||
if($time < 0)
|
|
||||||
$time = time();
|
|
||||||
|
|
||||||
ksort($params);
|
|
||||||
$compare = [];
|
|
||||||
|
|
||||||
// other sides supports arrays, not gonna bother here
|
|
||||||
foreach($params as $name => $value)
|
|
||||||
$compare[] = "{$name}:{$value}";
|
|
||||||
|
|
||||||
$input = "{$time}%{$method} {$path}%" . implode('#', $compare);
|
|
||||||
|
|
||||||
return UriBase64::encode(
|
|
||||||
hash_hmac('sha256', $input, $this->secretKey, true)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRequest(string $method, string $path, array $params = []): mixed {
|
|
||||||
$time = time();
|
|
||||||
$sign = $this->createSignature($method, $path, $params, $time);
|
|
||||||
|
|
||||||
$url = $this->endPoint . $path;
|
|
||||||
if(!empty($params))
|
|
||||||
$url .= '?' . http_build_query($params);
|
|
||||||
|
|
||||||
$request = curl_init($url);
|
|
||||||
curl_setopt_array($request, [
|
|
||||||
CURLOPT_AUTOREFERER => false,
|
|
||||||
CURLOPT_FAILONERROR => false,
|
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_HEADER => false,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_TCP_FASTOPEN => true,
|
|
||||||
CURLOPT_CONNECTTIMEOUT => 2,
|
|
||||||
CURLOPT_MAXREDIRS => 2,
|
|
||||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
|
||||||
CURLOPT_TIMEOUT => 5,
|
|
||||||
CURLOPT_CUSTOMREQUEST => $method,
|
|
||||||
CURLOPT_USERAGENT => 'mc.flashii.net',
|
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
"X-Mince-Signature: {$sign}",
|
|
||||||
"X-Mince-Timestamp: {$time}",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$response = curl_exec($request);
|
|
||||||
curl_close($request);
|
|
||||||
|
|
||||||
if(empty($response))
|
|
||||||
throw new RuntimeException('Empty response.');
|
|
||||||
|
|
||||||
$response = json_decode($response);
|
|
||||||
if(!empty($response->error))
|
|
||||||
throw new RuntimeException($response->error);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postRequest(string $method, string $path, array $params): mixed {
|
|
||||||
$time = time();
|
|
||||||
$sign = $this->createSignature($method, $path, $params, $time);
|
|
||||||
$url = $this->endPoint . $path;
|
|
||||||
|
|
||||||
$request = curl_init($url);
|
|
||||||
curl_setopt_array($request, [
|
|
||||||
CURLOPT_AUTOREFERER => false,
|
|
||||||
CURLOPT_FAILONERROR => false,
|
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_HEADER => false,
|
|
||||||
CURLOPT_POST => true,
|
|
||||||
CURLOPT_POSTFIELDS => $params,
|
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
|
||||||
CURLOPT_TCP_FASTOPEN => true,
|
|
||||||
CURLOPT_CONNECTTIMEOUT => 2,
|
|
||||||
CURLOPT_MAXREDIRS => 2,
|
|
||||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
|
|
||||||
CURLOPT_TIMEOUT => 5,
|
|
||||||
CURLOPT_CUSTOMREQUEST => $method,
|
|
||||||
CURLOPT_USERAGENT => 'mc.flashii.net',
|
|
||||||
CURLOPT_HTTPHEADER => [
|
|
||||||
"X-Mince-Signature: {$sign}",
|
|
||||||
"X-Mince-Timestamp: {$time}",
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$response = curl_exec($request);
|
|
||||||
curl_close($request);
|
|
||||||
|
|
||||||
if(empty($response))
|
|
||||||
throw new RuntimeException('Empty response.');
|
|
||||||
|
|
||||||
$response = json_decode($response);
|
|
||||||
if(!empty($response->error))
|
|
||||||
throw new RuntimeException($response->error);
|
|
||||||
|
|
||||||
return $response;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Mince;
|
|
||||||
|
|
||||||
use Index\Data\IDbConnection;
|
|
||||||
use Index\Data\DbType;
|
|
||||||
|
|
||||||
class Whitelist {
|
|
||||||
public function __construct(
|
|
||||||
private IDbConnection $dbConn
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getNames(): array {
|
|
||||||
$names = [];
|
|
||||||
|
|
||||||
$getNames = $this->dbConn->query('SELECT minecraft_username FROM whitelist_2022');
|
|
||||||
while($getNames->next())
|
|
||||||
$names[] = $getNames->getString(0);
|
|
||||||
|
|
||||||
return $names;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function add(object $userInfo, string $userName): string {
|
|
||||||
$length = strlen($userName);
|
|
||||||
if($length < 3)
|
|
||||||
return 'short';
|
|
||||||
if($length > 16)
|
|
||||||
return 'long';
|
|
||||||
if(!preg_match('#^([a-zA-Z0-9_]{3,16})$#', $userName))
|
|
||||||
return 'invalid';
|
|
||||||
|
|
||||||
$dupeCheck = $this->dbConn->prepare('SELECT COUNT(`flashii_id`) > 0 FROM `whitelist_2022` WHERE `minecraft_username` = ?');
|
|
||||||
$dupeCheck->addParameter(1, $userName, DbType::STRING);
|
|
||||||
$dupeCheck->execute();
|
|
||||||
$dupeResult = $dupeCheck->getResult();
|
|
||||||
|
|
||||||
if(!$dupeResult->next())
|
|
||||||
return 'dupefail';
|
|
||||||
if($dupeResult->getInteger(0))
|
|
||||||
return 'conflict';
|
|
||||||
|
|
||||||
if(!empty($userInfo->mc_whitelisted) || !empty($userInfo->mc_username)) {
|
|
||||||
$resp = $this->remove($userInfo);
|
|
||||||
if($resp !== '')
|
|
||||||
return $resp;
|
|
||||||
}
|
|
||||||
|
|
||||||
$insert = $this->dbConn->prepare('INSERT INTO `whitelist_2022` (`flashii_id`, `minecraft_username`) VALUES (?, ?)');
|
|
||||||
$insert->addParameter(1, $userInfo->user_id);
|
|
||||||
$insert->addParameter(2, $userName, DbType::STRING);
|
|
||||||
$insert->execute();
|
|
||||||
|
|
||||||
$userInfo->mc_username = $userName;
|
|
||||||
$userInfo->mc_whitelisted = time();
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove(object $userInfo): string {
|
|
||||||
if(empty($userInfo->mc_whitelisted) || empty($userInfo->mc_username))
|
|
||||||
return 'not-listed';
|
|
||||||
|
|
||||||
$delete = $this->dbConn->prepare('DELETE FROM `whitelist_2022` WHERE `flashii_id` = ?');
|
|
||||||
$delete->addParameter(1, $userInfo->user_id);
|
|
||||||
$delete->execute();
|
|
||||||
|
|
||||||
$userInfo->mc_username = null;
|
|
||||||
$userInfo->mc_whitelisted = 0;
|
|
||||||
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,61 +0,0 @@
|
||||||
<?php
|
|
||||||
namespace Mince;
|
|
||||||
|
|
||||||
use Index\Routing\IRouter;
|
|
||||||
use Index\Security\CSRFP;
|
|
||||||
|
|
||||||
class WhitelistRoutes {
|
|
||||||
public function __construct(
|
|
||||||
private Whitelist $whitelist,
|
|
||||||
private CSRFP $csrfp,
|
|
||||||
private object $userInfo
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function register(IRouter $router): void {
|
|
||||||
$router->use('/whitelist', [$this, 'verifyRequest']);
|
|
||||||
$router->post('/whitelist/add', [$this, 'postAdd']);
|
|
||||||
$router->post('/whitelist/remove', [$this, 'postRemove']);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function verifyRequest($response, $request) {
|
|
||||||
if(!$request->isFormContent()) {
|
|
||||||
$response->redirect('/?error=request');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = $request->getContent();
|
|
||||||
|
|
||||||
if(!$body->hasParam('csrfp') || !$this->csrfp->verifyToken((string)$body->getParam('csrfp'))) {
|
|
||||||
$response->redirect('/?error=verify');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postAdd($response, $request) {
|
|
||||||
if($this->userInfo->user_id == 45) {
|
|
||||||
$response->redirect('/?error=itainthappenin');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
$body = $request->getContent();
|
|
||||||
$name = (string)$body->getParam('name');
|
|
||||||
$resp = $this->whitelist->add($this->userInfo, $name);
|
|
||||||
|
|
||||||
if($resp === '')
|
|
||||||
$response->redirect('/');
|
|
||||||
else {
|
|
||||||
if($resp === 'invalid')
|
|
||||||
$name = '';
|
|
||||||
$response->redirect("/?error={$resp}&name={$name}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function postRemove($response) {
|
|
||||||
$resp = $this->whitelist->remove($this->userInfo);
|
|
||||||
|
|
||||||
if($resp === '')
|
|
||||||
$response->redirect('/');
|
|
||||||
else
|
|
||||||
$response->redirect("/?error={$resp}");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +1,6 @@
|
||||||
{% extends 'master.twig' %}
|
{% extends 'master.twig' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if error is defined %}
|
|
||||||
<div class="error">
|
|
||||||
<h2>{{ error.title }}</h2>
|
|
||||||
<p>{{ error.body|default('No further details provided.') }}</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if not is_authed %}
|
|
||||||
<div class="section">
|
|
||||||
<h2>You must be logged in to use this website!</h2>
|
|
||||||
<p>This website allows you to whitelist yourself on our Minecraft servers, for which you need to be logged in.</p>
|
|
||||||
<p>So it doesn't make sense to display the details either.</p>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
{% if auth.mc_whitelisted < 1 %}
|
|
||||||
<div class="section whitelist">
|
|
||||||
<h2>Add to Whitelist</h2>
|
|
||||||
<p>This will give you access to the server. <strong>The whitelist is being removed in favour of an authentication plugin, go to the <a href="/clients">clients page</a> for more info.</strong></p>
|
|
||||||
<form method="post" action="/whitelist/add">
|
|
||||||
<input type="hidden" name="csrfp" value="{{ csrfp }}">
|
|
||||||
<label>
|
|
||||||
<div class="label-header">Username</div>
|
|
||||||
<div class="label-input"><input type="text" name="name" value="{{ wladdform_username }}"></div>
|
|
||||||
</label>
|
|
||||||
<input type="submit" value="Add me to the Whitelist">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Servers</h2>
|
<h2>Servers</h2>
|
||||||
<table class="servers">
|
<table class="servers">
|
||||||
|
@ -62,22 +33,6 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if auth.mc_whitelisted > 0 %}
|
|
||||||
<div class="section unwhitelist">
|
|
||||||
<h2>Remove from Whitelist</h2>
|
|
||||||
<p>This will revoke your access to the server.</p>
|
|
||||||
<p>You are currently whitelisted as <b>{{ auth.mc_username }}</b> on <b>{{ auth.mc_whitelisted|date('Y-m-d H:i:s T') }}</b>.</p>
|
|
||||||
{% if whitelist_pending %}
|
|
||||||
<p><b style="font-size: 1.1em; line-height: 1.4em;">The whitelists are synchronised once every 5 minutes, you'll be able to play soon!</b></p>
|
|
||||||
<p>If you're playing a modpack, take that time to start the game up; it'll take a while.</p>
|
|
||||||
{% endif %}
|
|
||||||
<form method="post" action="/whitelist/remove">
|
|
||||||
<input type="hidden" name="csrfp" value="{{ csrfp }}">
|
|
||||||
<input type="submit" value="Remove me from the whitelist">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>Bedrock versions</h2>
|
<h2>Bedrock versions</h2>
|
||||||
<p>Through the black magic bestowed upon us by <a href="https://geysermc.org/" target="_blank" rel="noopener">GeyserMC</a> it's possible to play on the server through any of the updated Bedrock versions of Minecraft.</p>
|
<p>Through the black magic bestowed upon us by <a href="https://geysermc.org/" target="_blank" rel="noopener">GeyserMC</a> it's possible to play on the server through any of the updated Bedrock versions of Minecraft.</p>
|
||||||
|
@ -92,5 +47,4 @@
|
||||||
<p>2. Don't be an asshole.</p>
|
<p>2. Don't be an asshole.</p>
|
||||||
<p>3. Don't flood.</p>
|
<p>3. Don't flood.</p>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
45
tools/sync
45
tools/sync
|
@ -1,45 +0,0 @@
|
||||||
#!/usr/bin/env php
|
|
||||||
<?php
|
|
||||||
use Mince\Whitelist;
|
|
||||||
|
|
||||||
require_once __DIR__ . '/../mince.php';
|
|
||||||
|
|
||||||
echo 'Syncing server whitelists...' . PHP_EOL;
|
|
||||||
|
|
||||||
$rInfo = $remote->getInfo();
|
|
||||||
if(empty($rInfo->servers)) {
|
|
||||||
echo 'There are no active servers.' . PHP_EOL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo 'Fetching master list from database...' . PHP_EOL;
|
|
||||||
$myNames = (new Whitelist($db))->getNames();
|
|
||||||
|
|
||||||
foreach($rInfo->servers as $serverId) {
|
|
||||||
try {
|
|
||||||
echo "[{$serverId}] Fetching list on server..." . PHP_EOL;
|
|
||||||
$rWhitelist = $remote->getWhitelist($serverId);
|
|
||||||
$rNames = $rWhitelist->list ?? [];
|
|
||||||
|
|
||||||
echo "[{$serverId}] Filtering..." . PHP_EOL;
|
|
||||||
$addNames = array_values(array_udiff($myNames, $rNames, 'strcasecmp'));
|
|
||||||
$removeNames = array_values(array_udiff($rNames, $myNames, $addNames, 'strcasecmp'));
|
|
||||||
|
|
||||||
if(!empty($addNames)) {
|
|
||||||
echo "[{$serverId}] Adding names..." . PHP_EOL;
|
|
||||||
$addResult = $remote->addToWhitelist($serverId, $addNames);
|
|
||||||
foreach($addResult->results as $name => $result)
|
|
||||||
echo "[{$serverId}] [{$name}] {$result->message}" . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!empty($removeNames)) {
|
|
||||||
echo "[{$serverId}] Removing names..." . PHP_EOL;
|
|
||||||
$removeResult = $remote->removeFromWhitelist($serverId, $removeNames);
|
|
||||||
foreach($removeResult->results as $name => $result)
|
|
||||||
echo "[{$serverId}] [{$name}] {$result->message}" . PHP_EOL;
|
|
||||||
}
|
|
||||||
} catch(RuntimeException $ex) {
|
|
||||||
var_dump((string)$ex);
|
|
||||||
echo PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue