From 37e44c0f478bc2996abb8a72691bff41fe76f570 Mon Sep 17 00:00:00 2001 From: Siebe Vanden Eynden Date: Wed, 13 Mar 2024 13:37:39 +0100 Subject: [PATCH] Add key and proper request for not tunnel domains --- .gitignore | 1 + app/Server/Factory.php | 7 +- .../Controllers/ValidateTunnelController.php | 67 ++++++++++++++++--- config/expose.php | 4 ++ 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 075b932..4decc3f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ expose.php database/expose.db .expose.php .env +db.db diff --git a/app/Server/Factory.php b/app/Server/Factory.php index f3e0fee..af4191c 100644 --- a/app/Server/Factory.php +++ b/app/Server/Factory.php @@ -135,7 +135,7 @@ protected function addControlConnectionRoute(): WsServer protected function addAdminRoutes() { - $adminCondition = 'request.headers.get("Host") matches "/^'.config('expose.admin.subdomain').'\\\\./i"'; + $adminCondition = 'request.headers.get("Host") matches "/^' . config('expose.admin.subdomain') . '\\\\./i"'; $this->router->get('/', RedirectToUsersController::class, $adminCondition); $this->router->get('/users', ListUsersController::class, $adminCondition); @@ -173,6 +173,9 @@ protected function addAdminRoutes() protected function addValidateTunnel() { + $localCondition = 'request.headers.get("Host") matches "/^' . $this->host . ':' . $this->port . '$/i"'; + +// $this->router->get('/validate-tunnel', ValidateTunnelController::class, $localCondition); $this->router->get('/validate-tunnel', ValidateTunnelController::class); return $this; @@ -285,7 +288,7 @@ protected function bindDatabase() app()->singleton(DatabaseInterface::class, function () { $factory = new \Clue\React\SQLite\Factory($this->loop); - $options = ['worker_command' => Phar::running(false) ? Phar::running(false).' --sqlite-worker' : null]; + $options = ['worker_command' => Phar::running(false) ? Phar::running(false) . ' --sqlite-worker' : null]; return $factory->openLazy( config('expose.admin.database', ':memory:'), diff --git a/app/Server/Http/Controllers/ValidateTunnelController.php b/app/Server/Http/Controllers/ValidateTunnelController.php index d29ed91..83c35e5 100644 --- a/app/Server/Http/Controllers/ValidateTunnelController.php +++ b/app/Server/Http/Controllers/ValidateTunnelController.php @@ -4,33 +4,65 @@ use App\Contracts\ConnectionManager; use App\Http\Controllers\Controller; +use App\Server\Configuration; use App\Server\Connections\ControlConnection; use Illuminate\Http\Request; -use Illuminate\Support\Collection; use Ratchet\ConnectionInterface; class ValidateTunnelController extends Controller { private ConnectionManager $connectionManager; + private Configuration $configuration; - - public function __construct(ConnectionManager $connectionManager) - { + public function __construct( + Configuration $configuration, + ConnectionManager $connectionManager, + ) { $this->connectionManager = $connectionManager; + $this->configuration = $configuration; } public function handle(Request $request, ConnectionInterface $httpConnection) { + $key = $request->get('key'); + + // Only allow requests with the correct key + if ($key !== $this->getAuthorizedKey()) { + $httpConnection->send( + respond_json(['exists' => false], 401), + ); + $httpConnection->close(); + + return; + } + $domain = $request->get('domain'); if ($domain === null) { $httpConnection->send( respond_json(['exists' => false, 'error' => 'invalid_domain'], 404), ); + $httpConnection->close(); return; } - /** @var Collection $sites */ + // If the domain is the same as the hostname, then it requested the main domain + $hostname = $this->configuration->hostname(); + if ($hostname === $domain) { + $this->isSuccessful($httpConnection); + + return; + } + + // Also allow the admin dashboard + $adminSubdomain = config('expose.admin.subdomain'); + if ($domain === $adminSubdomain . '.' . $hostname) { + $this->isSuccessful($httpConnection); + + return; + } + + // Check if the domain is a tunnel $sites = collect($this->connectionManager->getConnections()) ->filter(function ($site) use ($domain) { $isControlConnection = get_class($site) === ControlConnection::class; @@ -38,12 +70,12 @@ public function handle(Request $request, ConnectionInterface $httpConnection) return false; } - $fqdn = sprintf( '%s.%s', $site->subdomain, $site->serverHost, ); + return $fqdn === $domain; }) ->map(function (ControlConnection $site) { @@ -54,10 +86,25 @@ public function handle(Request $request, ConnectionInterface $httpConnection) ); }); - $response = $sites->count() === 0 - ? respond_json(['exists' => false, 'error' => 'no_tunnel_found'], 404) - : respond_json(['exists' => true, 'sites' => $sites->toArray()]); + if ($sites->count() > 0) { + $this->isSuccessful($httpConnection); + + return; + } + + + $httpConnection->send(respond_json(['exists' => false, 'error' => 'no_tunnel_found'], 404)); + $httpConnection->close(); + } - $httpConnection->send($response); + private function isSuccessful(ConnectionInterface $connection): void + { + $connection->send(respond_json(['exists' => true])); + $connection->close(); + } + + private function getAuthorizedKey(): string + { + return config('expose.validate_tunnel.authorized_key'); } } diff --git a/config/expose.php b/config/expose.php index 8ca4f57..4249fcc 100644 --- a/config/expose.php +++ b/config/expose.php @@ -390,4 +390,8 @@ 'repository' => \App\Server\StatisticsRepository\DatabaseStatisticsRepository::class, ], ], + + 'validate_tunnel' => [ + 'authorized_key' => 'asHzMGp4y4fYmNzWAUmgsZZbcjSM5e', + ], ];