Updated to use URL registry.

This commit is contained in:
flash 2024-10-05 15:25:27 +00:00
parent f0ddb6727e
commit da130e5855
10 changed files with 89 additions and 51 deletions

View file

@ -4,6 +4,7 @@ namespace Mince;
use Index\CsrfToken;
use Index\Http\Routing\HttpRouter;
use Index\Templating\TplEnvironment;
use Index\Urls\ArrayUrlRegistry;
require_once __DIR__ . '/../mince.php';
@ -36,13 +37,25 @@ $authorisations->prune();
$verifications = new Verifications($db);
$verifications->prune();
$urls = new ArrayUrlRegistry;
$templating->addFunction('url', $urls->format(...));
$router = new HttpRouter(errorHandler: new RouterErrorHandler($templating));
$router->use('/', function($response, $request) { $response->setPoweredBy('Mince'); });
$router->register(new RpcRoutes($users, $accountLinks, $authorisations, $verifications, $cfg->getString('rpc:secret'), $cfg->getString('urls:clients')));
$router->register(new HomeRoutes($templating, new Servers($db), $authInfo, $cfg->getString('site:login')));
$router->register(new ClientsRoutes($templating, $accountLinks, $authorisations, $verifications, $csrfp, $authInfo));
$router->register(new SkinsRoutes($templating, $accountLinks, new Skins($db), new Capes($db), $csrfp, $authInfo, $cfg->getString('urls:skins_base')));
$homeRoutes = new HomeRoutes($templating, $urls, new Servers($db), $authInfo, $cfg->getString('site:login'));
$router->register($homeRoutes);
$urls->register($homeRoutes);
$clientRoutes = new ClientsRoutes($templating, $urls, $accountLinks, $authorisations, $verifications, $csrfp, $authInfo);
$router->register($clientRoutes);
$urls->register($clientRoutes);
$skinsRoutes = new SkinsRoutes($templating, $urls, $accountLinks, new Skins($db), new Capes($db), $csrfp, $authInfo, $cfg->getString('urls:skins_base'));
$router->register($skinsRoutes);
$urls->register($skinsRoutes);
MojangInterop::registerRoutes($router);

View file

@ -6,13 +6,15 @@ use RuntimeException;
use Index\CsrfToken;
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerTrait};
use Index\Templating\TplEnvironment;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceTrait};
use Ramsey\Uuid\Uuid;
class ClientsRoutes implements RouteHandler {
use RouteHandlerTrait;
class ClientsRoutes implements RouteHandler, UrlSource {
use RouteHandlerTrait, UrlSourceTrait;
public function __construct(
private TplEnvironment $templating,
private UrlRegistry $urls,
private AccountLinks $accountLinks,
private Authorisations $authorisations,
private Verifications $verifications,
@ -44,6 +46,7 @@ class ClientsRoutes implements RouteHandler {
];
#[HttpGet('/clients')]
#[UrlFormat('clients:index', '/clients', ['error' => '<error>'])]
public function getClients($response, $request) {
$template = $this->templating->load('clients/index');
@ -76,16 +79,17 @@ class ClientsRoutes implements RouteHandler {
}
#[HttpPost('/clients/link')]
#[UrlFormat('clients:link', '/clients/link')]
public function postLink($response, $request) {
if($this->accountLinks->checkHasLink($this->authInfo->user_id)) {
$response->redirect('/clients?error=link:already');
$response->redirect($this->urls->format('clients:index', ['error' => 'link:already']));
return;
}
$body = $request->getContent();
$code = (string)$body->getParam('code');
if(strlen($code) !== 10) {
$response->redirect('/clients?error=link:format');
$response->redirect($this->urls->format('clients:index', ['error' => 'link:format']));
return;
}
@ -94,7 +98,7 @@ class ClientsRoutes implements RouteHandler {
try {
$verifyInfo = $this->verifications->getVerification(code: $code);
} catch(RuntimeException $ex) {
$response->redirect('/clients?error=link:code');
$response->redirect($this->urls->format('clients:index', ['error' => 'link:code']));
return;
}
@ -102,16 +106,18 @@ class ClientsRoutes implements RouteHandler {
$this->accountLinks->createLink($this->authInfo->user_id, $verifyInfo);
$this->authorisations->createAuthorisation($verifyInfo, grant: true);
$response->redirect('/clients');
$response->redirect($this->urls->format('clients:index'));
}
#[HttpPost('/clients/unlink')]
#[UrlFormat('clients:unlink', '/clients/unlink')]
public function postUnlink($response) {
$this->accountLinks->deleteLink(userInfo: $this->authInfo->user_id);
$response->redirect('/clients');
$response->redirect($this->urls->format('clients:index'));
}
#[HttpPost('/clients/authorise')]
#[UrlFormat('clients:authorise', '/clients/authorise')]
public function postAuthorise($response, $request) {
$body = $request->getContent();
$authId = (string)$body->getParam('auth');
@ -137,10 +143,11 @@ class ClientsRoutes implements RouteHandler {
$this->authorisations->setAuthorisationGranted($authInfo);
$response->redirect('/clients');
$response->redirect($this->urls->format('clients:index'));
}
#[HttpPost('/clients/deauthorise')]
#[UrlFormat('clients:deauthorise', '/clients/deauthorise')]
public function postDeauthorise($response, $request) {
$body = $request->getContent();
$authId = (string)$body->getParam('auth');
@ -170,6 +177,6 @@ class ClientsRoutes implements RouteHandler {
$this->authorisations->deleteAuthorisations(authInfo: $authInfo);
}
$response->redirect('/clients');
$response->redirect($this->urls->format('clients:index'));
}
}

View file

@ -2,19 +2,22 @@
namespace Mince;
use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerTrait};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceTrait};
use Index\Templating\TplEnvironment;
class HomeRoutes implements RouteHandler {
use RouteHandlerTrait;
class HomeRoutes implements RouteHandler, UrlSource {
use RouteHandlerTrait, UrlSourceTrait;
public function __construct(
private TplEnvironment $templating,
private UrlRegistry $urls,
private Servers $servers,
private object $userInfo,
private string $loginUrl
) {}
#[HttpGet('/')]
#[UrlFormat('index', '/')]
public function getIndex($response, $request) {
return $this->templating->render('index', [
'servers' => iterator_to_array($this->servers->getServers(deleted: false)),
@ -22,22 +25,25 @@ class HomeRoutes implements RouteHandler {
}
#[HttpGet('/login')]
#[UrlFormat('login', '/login')]
public function getLogin($response) {
$response->redirect($this->userInfo->success ? '/' : $this->loginUrl);
$response->redirect($this->userInfo->success ? $this->urls->format('index') : $this->loginUrl);
}
#[HttpGet('/downloads')]
#[UrlFormat('downloads', '/downloads')]
public function getDownloads() {
return $this->templating->render('downloads');
}
#[HttpGet('/guide')]
#[UrlFormat('guide', '/guide')]
public function getGuide() {
return $this->templating->render('guide');
}
#[HttpGet('/index.php')]
public function getRedirect($response) {
$response->redirect('/', true);
$response->redirect($this->urls->format('index'), true);
}
}

View file

@ -9,10 +9,11 @@ use RuntimeException;
use Index\{CsrfToken,XString};
use Index\Http\Routing\{HttpGet,HttpMiddleware,HttpPost,RouteHandler,RouteHandlerTrait};
use Index\Templating\TplEnvironment;
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceTrait};
use Ramsey\Uuid\{Uuid,UuidInterface};
class SkinsRoutes implements RouteHandler {
use RouteHandlerTrait;
class SkinsRoutes implements RouteHandler, UrlSource {
use RouteHandlerTrait, UrlSourceTrait;
private const TEXTURES_DIR = '/textures';
private const TEXTURES_PATH = MCR_DIR_PUB . self::TEXTURES_DIR;
@ -21,6 +22,7 @@ class SkinsRoutes implements RouteHandler {
public function __construct(
private TplEnvironment $templating,
private UrlRegistry $urls,
private AccountLinks $accountLinks,
private Skins $skins,
private Capes $capes,
@ -61,7 +63,7 @@ class SkinsRoutes implements RouteHandler {
try {
$this->linkInfo = $this->accountLinks->getLink(userInfo: $this->authInfo->user_id);
} catch(RuntimeException $ex) {
$response->redirect('/clients');
$response->redirect($this->urls->format('clients:index'));
return true;
}
@ -88,6 +90,7 @@ class SkinsRoutes implements RouteHandler {
];
#[HttpGet('/skins')]
#[UrlFormat('skins:index', '/skins', ['error' => '<error>'])]
public function getSkins($response, $request) {
$skinInfo = $this->skins->getSkin($this->linkInfo);
$skinPath = $skinInfo === null ? null : $this->getRemotePath($skinInfo->getHash(), false);
@ -121,6 +124,7 @@ class SkinsRoutes implements RouteHandler {
}
#[HttpPost('/skins/upload-skin')]
#[UrlFormat('skins:skin:upload', '/skins/upload-skin')]
public function postUploadSkin($response, $request) {
$body = $request->getContent();
if(!$body->hasUploadedFile('texture'))
@ -130,12 +134,12 @@ class SkinsRoutes implements RouteHandler {
$model = (string)$body->getParam('model');
if(!in_array($model, Skins::MODELS)) {
$response->redirect('/skins?error=skin:model');
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:model']));
return;
}
if($texture->getSize() > 512000) {
$response->redirect('/skins?error=skin:size');
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:size']));
return;
}
@ -153,7 +157,7 @@ class SkinsRoutes implements RouteHandler {
$imagick->writeImage();
$imagick->destroy();
} catch(ImagickException $ex) {
$response->redirect('/skins?error=skin:format');
$response->redirect($this->urls->format('skins:index', ['error' => 'skin:format']));
return;
}
@ -180,10 +184,11 @@ class SkinsRoutes implements RouteHandler {
$this->deleteLocalFileMaybe($hash);
}
$response->redirect('/skins');
$response->redirect($this->urls->format('skins:index'));
}
#[HttpPost('/skins/delete-skin')]
#[UrlFormat('skins:skin:delete', '/skins/delete-skin')]
public function postDeleteSkin($response) {
$skinInfo = $this->skins->getSkin($this->linkInfo);
if($skinInfo !== null) {
@ -191,10 +196,11 @@ class SkinsRoutes implements RouteHandler {
$this->deleteLocalFileMaybe($skinInfo->getHash());
}
$response->redirect('/skins');
$response->redirect($this->urls->format('skins:index'));
}
#[HttpPost('/skins/upload-cape')]
#[UrlFormat('skins:cape:upload', '/skins/upload-cape')]
public function postUploadCape($response, $request) {
$body = $request->getContent();
if(!$body->hasUploadedFile('texture'))
@ -202,7 +208,7 @@ class SkinsRoutes implements RouteHandler {
$texture = $body->getUploadedFile('texture');
if($texture->getSize() > 256000) {
$response->redirect('/skins?error=cape:size');
$response->redirect($this->urls->format('skins:index', ['error' => 'cape:size']));
return;
}
@ -217,7 +223,7 @@ class SkinsRoutes implements RouteHandler {
$imagick->writeImage();
$imagick->destroy();
} catch(ImagickException $ex) {
$response->redirect('/skins?error=cape:format');
$response->redirect($this->urls->format('skins:index', ['error' => 'cape:format']));
return;
}
@ -243,10 +249,11 @@ class SkinsRoutes implements RouteHandler {
$this->deleteLocalFileMaybe($hash);
}
$response->redirect('/skins');
$response->redirect($this->urls->format('skins:index'));
}
#[HttpPost('/skins/delete-cape')]
#[UrlFormat('skins:cape:delete', '/skins/delete-cape')]
public function postDeleteCape($response) {
$capeInfo = $this->capes->getCape($this->linkInfo);
if($capeInfo !== null) {
@ -254,10 +261,11 @@ class SkinsRoutes implements RouteHandler {
$this->deleteLocalFileMaybe($capeInfo->getHash());
}
$response->redirect('/skins');
$response->redirect($this->urls->format('skins:index'));
}
#[HttpPost('/skins/import')]
#[UrlFormat('skins:import', '/skins/import')]
public function postImport($response, $request) {
$body = $request->getContent();
$userAgent = $request->getHeaderLine('User-Agent');
@ -324,7 +332,7 @@ class SkinsRoutes implements RouteHandler {
}
}
$response->redirect('/skins');
$response->redirect($this->urls->format('skins:index'));
}
private function getProfileInfo(UuidInterface $uuid, bool $includeProfileActions) {

View file

@ -17,7 +17,7 @@
<p style="color: red;">{{ error.message }}</p>
{% endif %}
<form method="post" action="/clients/link">
<form method="post" action="{{ url('clients:link') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<label>
<div class="label-header">Link code</div>
@ -69,13 +69,13 @@
</td>
<td class="actions">
{% if client.isPending %}
<form method="post" action="/clients/authorise">
<form method="post" action="{{ url('clients:authorise') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="hidden" name="auth" value="{{ client.id }}">
<input class="action action-authorise js-authorise-button" type="submit" value="Approve" disabled onclick="return confirm('Are you sure?');">
</form>
{% endif %}
<form method="post" action="/clients/deauthorise">
<form method="post" action="{{ url('clients:deauthorise') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="hidden" name="auth" value="{{ client.id }}">
<input class="action action-deauthorise" type="submit" value="{% if client.isPending %}Deny{% else %}Deauthorise{% endif %}">
@ -92,7 +92,7 @@
<div class="section accmegadeauth">
<h2>Crowd Control</h2>
<p>Provided for those who prefer the nuclear option. Pressing the first button will deauthorise all clients, the other one will only remove pending ones.</p>
<form method="post" action="/clients/deauthorise">
<form method="post" action="{{ url('clients:deauthorise') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<button class="form-btn-red" name="auth" value="all">Deauthorise all clients</button>
<button class="form-btn-green" name="auth" value="pending">Deny pending clients</button>
@ -103,7 +103,7 @@
<h2>Linked Minecraft account</h2>
<p>This is the Minecraft account currently associated with your Flashii ID. Revoking revoke your access to the servers. <strong>If you're planning on changing your username, please keep in mind that your stats and inventory on the servers WILL NOT carry over automatically.</strong></p>
<p>Your account has been linked with <b>{{ link.name }}</b> since <b>{{ link.createdTime|date('Y-m-d H:i:s T') }}</b>.</p>
<form method="post" action="/clients/unlink">
<form method="post" action="{{ url('clients:unlink') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="submit" value="Unlink account">
</form>

View file

@ -21,7 +21,7 @@
<div class="section downloads-item">
<h2>MultiMC/PolyMC/Prism Launcher spoofed accounts file</h2>
<p>This file is used in the <a href="/guide">guide</a> to allow you to add an Offline account of your own.</p>
<p>This file is used in the <a href="{{ url('guide') }}">guide</a> to allow you to add an Offline account of your own.</p>
<ul>
<li><a href="/dl/accounts.json" download>Download accounts.json</a></li>

View file

@ -94,7 +94,7 @@
<p>Press the <strong>Add Instance</strong> button on the top left.</p>
<p>
Under <strong>Custom</strong> make sure the version the server you want to connect to is currently running is selected.
The versions of the servers are listing on the <a href="/">home page</a> along with their addresses for your convenience.
The versions of the servers are listing on the <a href="{{ url('index') }}">home page</a> along with their addresses for your convenience.
</p>
<p>If you plan on using our client side mod, select <strong>Fabric</strong> under <strong>Mod Loader</strong> and just pick whatever the latest version is, which is also what should be selected by default.</p>
<p>When you've done those things, press OK.</p>
@ -104,7 +104,7 @@
<div class="section" id="gac5">
<h3><a href="#gac5">Step 5. Installing the Flashii Extensions client side mod (Optional)</a></h3>
<p>
You can download the Flashii Extensions mod from the <a href="/downloads">downloads page</a>, the latest version of the mod is the first thing linked in the list.
You can download the Flashii Extensions mod from the <a href="{{ url('downloads') }}">downloads page</a>, the latest version of the mod is the first thing linked in the list.
Save it anywhere you like as long as you remember where you saved it.
</p>
<p>
@ -124,7 +124,7 @@
<div class="section" id="ctos">
<h2><a href="#ctos">Connecting to a server</a></h2>
<p>We're assuming you've already picked a server to play from from the <a href="/">home page</a> and have added it to your server list.</p>
<p>We're assuming you've already picked a server to play from from the <a href="{{ url('index') }}">home page</a> and have added it to your server list.</p>
</div>
<div class="section" id="ctos1">

View file

@ -35,7 +35,11 @@
<div class="section">
<h2>Authentication and Skins</h2>
<p>Because of the deprecation of Mojang/Minecraft accounts and the general distrust in Microsoft's ability to not suspend your account for no reason, our servers run in offline mode with an authentication plugin. This means you can play the game with any username you want without logging in with a Microsoft account in your launcher, so long as someone else isn't already using that name. You don't have to install any mods on your client for the authentication side of things to work, in order to be able to see skins of other players you'll need to install a mod though. You can grab the mod through the <a href="/downloads">downloads</a> page and if you're not sure what to do you can follow the <a href="/guide">guide</a> page!</p>
<p>
Because of the deprecation of Mojang/Minecraft accounts and the general distrust in Microsoft's ability to not suspend your account for no reason, our servers run in offline mode with an authentication plugin.
This means you can play the game with any username you want without logging in with a Microsoft account in your launcher, so long as someone else isn't already using that name.
You don't have to install any mods on your client for the authentication side of things to work, in order to be able to see skins of other players you'll need to install a mod though.
You can grab the mod through the <a href="{{ url('downloads') }}">downloads</a> page and if you're not sure what to do you can follow the <a href="{{ url('guide') }}">guide</a> page!</p>
</div>
{#

View file

@ -14,19 +14,19 @@
<a href="/"><img src="/assets/weblogo.png" alt="Flashii Minecraft"></a>
</div>
<div class="header-menu">
<a href="/">Home</a>
<a href="/downloads">Downloads</a>
<a href="/guide">Guide</a>
<a href="{{ url('index') }}">Home</a>
<a href="{{ url('downloads') }}">Downloads</a>
<a href="{{ url('guide') }}">Guide</a>
{% if globals.is_authed %}
<a href="/clients">Clients</a>
<a href="/skins">Skins</a>
<a href="{{ url('clients:index') }}">Clients</a>
<a href="{{ url('skins:index') }}">Skins</a>
{% endif %}
</div>
<div class="header-user">
{% if globals.is_authed %}
Logged in as <span style="color: {{ globals.user.colour }}">{{ globals.user.name }}</span>
{% else %}
<a href="/login">Log in</a>
<a href="{{ url('login') }}">Log in</a>
{% endif %}
</div>
</div>

View file

@ -5,7 +5,7 @@
{% block content %}
<div class="section skins-header">
<h1>Skins</h1>
<p>Because our servers run in offline mode with our own authentication plugin, skins are downloaded from Mojang's servers. To make up for this we also have a client side mod that substitutes the skin and cape server with our own. You can find the mod on the <a href="/downloads">downloads page</a>. Please keep your textures clean.</p>
<p>Because our servers run in offline mode with our own authentication plugin, skins are downloaded from Mojang's servers. To make up for this we also have a client side mod that substitutes the skin and cape server with our own. You can find the mod on the <a href="{{ url('downloads') }}">downloads page</a>. Please keep your textures clean.</p>
</div>
<div class="section skin">
@ -17,7 +17,7 @@
{% endif %}
<div class="skins-form">
<form method="post" action="/skins/upload-skin" enctype="multipart/form-data">
<form method="post" action="{{ url('skins:skin:upload') }}" enctype="multipart/form-data">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="file" name="texture">
<select name="model">
@ -28,7 +28,7 @@
<input type="submit" value="Upload Skin" class="skins-form-upload">
</form>
{% if skin is not null %}
<form method="post" action="/skins/delete-skin">
<form method="post" action="{{ url('skins:skin:delete') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="submit" value="Delete Skin" class="skins-form-delete">
</form>
@ -51,13 +51,13 @@
{% endif %}
<div class="skins-form">
<form method="post" action="/skins/upload-cape" enctype="multipart/form-data">
<form method="post" action="{{ url('skins:cape:upload') }}" enctype="multipart/form-data">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="file" name="texture">
<input type="submit" value="Upload Cape" class="skins-form-upload">
</form>
{% if cape is not null %}
<form method="post" action="/skins/delete-cape">
<form method="post" action="{{ url('skins:cape:delete') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<input type="submit" value="Delete Cape" class="skins-form-delete">
</form>
@ -74,7 +74,7 @@
<div class="section skinimport">
<h2>Import from Mojang account</h2>
<p>Import skin and cape textures from a Mojang Minecraft account. If you linked the same username as your actual Minecraft account, you can just press Import without anything.</p>
<form method="post" action="/skins/import">
<form method="post" action="{{ url('skins:import') }}">
<input type="hidden" name="csrfp" value="{{ globals.csrfp }}">
<label>
<div class="label-header">Minecraft Username</div>