Removed code related to managing server whitelists.

This commit is contained in:
flash 2023-08-23 23:40:15 +00:00
parent 69799c6422
commit 9e95366336
12 changed files with 61 additions and 763 deletions

View 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');
}
}

View file

@ -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"');
$remote = new RemoteV2($config['remotev2_url'], $config['remotev2_secret']);

View file

@ -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"}';

View file

@ -8,7 +8,7 @@ require_once __DIR__ . '/../mince.php';
// replace this with id.flashii.net shit
$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);
if($authInfo->success) {
@ -29,7 +29,6 @@ $templating->addVars([
'title' => 'Flashii Minecraft Servers',
],
'is_authed' => $userInfo !== null,
'auth' => $authInfo,
'user' => $userInfo,
'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 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 WhitelistRoutes(new Whitelist($db), $csrfp, $authInfo))->register($router);
MojangInterop::registerRoutes($router);

View file

@ -103,8 +103,7 @@ h1, h2 {
}
.form-btn-green,
.acclink input[type="submit"],
.whitelist input[type="submit"] {
.acclink input[type="submit"] {
font-size: 1.5em;
margin: 5px;
border-radius: 4px;
@ -117,20 +116,16 @@ h1, h2 {
.form-btn-green:hover,
.form-btn-green:focus,
.acclink input[type="submit"]:hover,
.acclink input[type="submit"]:focus,
.whitelist input[type="submit"]:hover,
.whitelist input[type="submit"]:focus {
.acclink input[type="submit"]:focus {
background-color: #272;
}
.form-btn-green:active,
.acclink input[type="submit"]:active,
.whitelist input[type="submit"]:active {
.acclink input[type="submit"]:active {
background-color: #232;
}
.form-btn-red,
.accunlink input[type="submit"],
.unwhitelist input[type="submit"] {
.accunlink input[type="submit"] {
font-size: 1.5em;
margin: 5px;
border-radius: 4px;
@ -143,14 +138,11 @@ h1, h2 {
.form-btn-red:hover,
.form-btn-red:focus,
.accunlink input[type="submit"]:hover,
.accunlink input[type="submit"]:focus,
.unwhitelist input[type="submit"]:hover,
.unwhitelist input[type="submit"]:focus {
.accunlink input[type="submit"]:focus {
background-color: #722;
}
.form-btn-red:active,
.accunlink input[type="submit"]:active,
.unwhitelist input[type="submit"]:active {
.accunlink input[type="submit"]:active {
background-color: #322;
}

View file

@ -2,10 +2,9 @@
namespace Mince;
use stdClass;
use Index\Data\IDbConnection;
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)) {
$method = 'Misuzu';
$signature = sprintf('verify#%s#%s#%s', $method, $cookie, $_SERVER['REMOTE_ADDR']);
@ -48,21 +47,6 @@ final class ChatAuth {
$userInfo->rank = 0;
$userInfo->hierarchy = 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;

View file

@ -22,44 +22,8 @@ class HomeRoutes {
}
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', [
'servers' => $this->servers->getServers(deleted: false),
'wladdform_username' => $name,
]);
}

View file

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

View file

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

View file

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

View file

@ -1,96 +1,50 @@
{% extends 'master.twig' %}
{% 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">
<h2>Servers</h2>
<table class="servers">
<thead>
<tr>
<th class="col-name">Name</th>
<th class="col-address">Address</th>
<th class="col-java">Version</th>
{# <th class="col-address">Bedrock address</th> #}
{# <th class="col-bedrock">Bedrock version</th> #}
<th class="col-details">Details</th>
</tr>
</thead>
<tbody>
{% if servers is empty %}
<tr><td class="col-java" colspan="4">There are currently no active servers, check back later!</td></tr>
{% else %}
{% for server in servers %}
<tr id="s{{ server.id }}">
<td class="col-name">{{ server.name }}</td>
<td class="col-address">{% if server.hasJavaAddress %}<code>{{ server.javaAddress }}</code>{% else %}N/A{% endif %}</td>
<td class="col-java">{% if server.hasJavaVersion %}<code>{{ server.javaVersion }}</code>{% else %}N/A{% endif %}</td>
{# <td class="col-address">{% if server.hasBedrockAddress %}<code>{{ server.bedrockAddress }}</code>{% else %}N/A{% endif %}</td> #}
{# <td class="col-bedrock">{% if server.hasBedrockVersion %}<code>{{ server.bedrockVersion }}</code>{% else %}N/A{% endif %}</td> #}
<td class="col-details">{{ server.details|raw }}</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</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>
<div class="section">
<h2>Servers</h2>
<table class="servers">
<thead>
<tr>
<th class="col-name">Name</th>
<th class="col-address">Address</th>
<th class="col-java">Version</th>
{# <th class="col-address">Bedrock address</th> #}
{# <th class="col-bedrock">Bedrock version</th> #}
<th class="col-details">Details</th>
</tr>
</thead>
<tbody>
{% if servers is empty %}
<tr><td class="col-java" colspan="4">There are currently no active servers, check back later!</td></tr>
{% else %}
{% for server in servers %}
<tr id="s{{ server.id }}">
<td class="col-name">{{ server.name }}</td>
<td class="col-address">{% if server.hasJavaAddress %}<code>{{ server.javaAddress }}</code>{% else %}N/A{% endif %}</td>
<td class="col-java">{% if server.hasJavaVersion %}<code>{{ server.javaVersion }}</code>{% else %}N/A{% endif %}</td>
{# <td class="col-address">{% if server.hasBedrockAddress %}<code>{{ server.bedrockAddress }}</code>{% else %}N/A{% endif %}</td> #}
{# <td class="col-bedrock">{% if server.hasBedrockVersion %}<code>{{ server.bedrockVersion }}</code>{% else %}N/A{% endif %}</td> #}
<td class="col-details">{{ server.details|raw }}</td>
</tr>
{% endfor %}
{% 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 %}
</tbody>
</table>
</div>
<div class="section">
<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>This should allow you to play on the server from a phone, a tablet or a console, provided you also have an account for the original version of the game.</p>
<p>You will need to link your Minecraft and Bedrock accounts, you can do this by connecting to <code>link.geysermc.org</code> in both versions of the game and following the on-screen instructions.</p>
<p>Do note that this only works for servers where both a Java and Bedrock version number is listed!</p>
</div>
<div class="section">
<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>This should allow you to play on the server from a phone, a tablet or a console, provided you also have an account for the original version of the game.</p>
<p>You will need to link your Minecraft and Bedrock accounts, you can do this by connecting to <code>link.geysermc.org</code> in both versions of the game and following the on-screen instructions.</p>
<p>Do note that this only works for servers where both a Java and Bedrock version number is listed!</p>
</div>
<div class="section">
<h2>Rules</h2>
<p>1. Observe <a href="//fii.moe/rules">Global Rules</a>.</p>
<p>2. Don't be an asshole.</p>
<p>3. Don't flood.</p>
</div>
{% endif %}
<div class="section">
<h2>Rules</h2>
<p>1. Observe <a href="//fii.moe/rules">Global Rules</a>.</p>
<p>2. Don't be an asshole.</p>
<p>3. Don't flood.</p>
</div>
{% endblock %}

View file

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