diff --git a/app/Client/Client.php b/app/Client/Client.php index d90ff4f..9ff44fa 100644 --- a/app/Client/Client.php +++ b/app/Client/Client.php @@ -6,6 +6,7 @@ use App\Client\Connections\ControlConnection; use App\Logger\CliRequestLogger; use Ratchet\Client\WebSocket; use React\EventLoop\LoopInterface; +use React\Promise\PromiseInterface; use function Ratchet\Client\connect; class Client @@ -37,8 +38,11 @@ class Client } } - protected function connectToServer(string $sharedUrl, $subdomain) + public function connectToServer(string $sharedUrl, $subdomain): PromiseInterface { + $deferred = new \React\Promise\Deferred(); + $promise = $deferred->promise(); + $token = config('expose.auth_token'); $wsProtocol = $this->configuration->port() === 443 ? "wss" : "ws"; @@ -46,7 +50,7 @@ class Client connect($wsProtocol."://{$this->configuration->host()}:{$this->configuration->port()}/expose/control?authToken={$token}", [], [ 'X-Expose-Control' => 'enabled', ], $this->loop) - ->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain) { + ->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain, $deferred) { $connection = ControlConnection::create($clientConnection); $connection->authenticate($sharedUrl, $subdomain); @@ -66,7 +70,7 @@ class Client exit(1); }); - $connection->on('authenticated', function ($data) { + $connection->on('authenticated', function ($data) use ($deferred) { $httpProtocol = $this->configuration->port() === 443 ? "https" : "http"; $host = $this->configuration->host(); @@ -77,12 +81,19 @@ class Client $this->logger->info("Connected to {$httpProtocol}://{$data->subdomain}.{$host}"); static::$subdomains[] = "$data->subdomain.{$this->configuration->host()}:{$this->configuration->port()}"; + + $deferred->resolve(); }); - }, function (\Exception $e) { + }, function (\Exception $e) use ($deferred) { $this->logger->error("Could not connect to the server."); $this->logger->error($e->getMessage()); + + $deferred->reject(); + exit(1); }); + + return $promise; } } diff --git a/app/Client/Factory.php b/app/Client/Factory.php index f2f411c..17a397d 100644 --- a/app/Client/Factory.php +++ b/app/Client/Factory.php @@ -86,12 +86,17 @@ class Factory }); } - public function createClient($sharedUrl, $subdomain = null, $auth = null) + public function createClient() { $this->bindConfiguration(); $this->bindProxyManager(); + return $this; + } + + public function share($sharedUrl, $subdomain = null) + { app(Client::class)->share($sharedUrl, $subdomain); return $this; @@ -131,7 +136,7 @@ class Factory echo("Started Dashboard on port {$dashboardPort}" . PHP_EOL); - echo('If the dashboard does not automatically open, visit: ' . $dashboardUrl . PHP_EOL); + echo('You can visit the dashboard at: ' . $dashboardUrl . PHP_EOL); }); $this->app = new App('127.0.0.1', $dashboardPort, '0.0.0.0', $this->loop); diff --git a/app/Client/Http/Controllers/DashboardController.php b/app/Client/Http/Controllers/DashboardController.php index da2064e..8986fb4 100644 --- a/app/Client/Http/Controllers/DashboardController.php +++ b/app/Client/Http/Controllers/DashboardController.php @@ -15,7 +15,7 @@ class DashboardController extends Controller public function handle(Request $request, ConnectionInterface $httpConnection) { - $httpConnection->send(respond_html($this->getView('client.dashboard', [ + $httpConnection->send(respond_html($this->getView($httpConnection, 'client.dashboard', [ 'subdomains' => Client::$subdomains, ]))); } diff --git a/app/Commands/ShareCommand.php b/app/Commands/ShareCommand.php index 010103c..f59afc7 100644 --- a/app/Commands/ShareCommand.php +++ b/app/Commands/ShareCommand.php @@ -33,7 +33,8 @@ class ShareCommand extends Command ->setHost(config('expose.host', 'localhost')) ->setPort(config('expose.port', 8080)) ->setAuth($this->option('auth')) - ->createClient($this->argument('host'), explode(',', $this->option('subdomain'))) + ->createClient() + ->share($this->argument('host'), explode(',', $this->option('subdomain'))) ->createHttpServer() ->run(); } diff --git a/app/Http/Controllers/Concerns/LoadsViews.php b/app/Http/Controllers/Concerns/LoadsViews.php index 5665708..1906ff4 100644 --- a/app/Http/Controllers/Concerns/LoadsViews.php +++ b/app/Http/Controllers/Concerns/LoadsViews.php @@ -2,13 +2,14 @@ namespace App\Http\Controllers\Concerns; +use Ratchet\ConnectionInterface; use Twig\Environment; use Twig\Loader\ArrayLoader; use function GuzzleHttp\Psr7\stream_for; trait LoadsViews { - protected function getView(string $view, array $data = []) + protected function getView(ConnectionInterface $connection, string $view, array $data = []) { $templatePath = implode(DIRECTORY_SEPARATOR, explode('.', $view)); @@ -19,6 +20,10 @@ trait LoadsViews ]) ); + $data = array_merge($data, [ + 'request' => $connection->laravelRequest ?? null, + ]); + return stream_for($twig->render('template', $data)); } } diff --git a/app/Http/Controllers/Concerns/ParsesIncomingRequest.php b/app/Http/Controllers/Concerns/ParsesIncomingRequest.php index 5342806..6159f72 100644 --- a/app/Http/Controllers/Concerns/ParsesIncomingRequest.php +++ b/app/Http/Controllers/Concerns/ParsesIncomingRequest.php @@ -26,10 +26,10 @@ trait ParsesIncomingRequest protected function checkContentLength(ConnectionInterface $connection) { if (strlen($connection->requestBuffer) === $connection->contentLength) { - $laravelRequest = $this->createLaravelRequest($connection); + $connection->laravelRequest = $this->createLaravelRequest($connection); - if ($this->shouldHandleRequest($laravelRequest, $connection)) { - $this->handle($laravelRequest, $connection); + if ($this->shouldHandleRequest($connection->laravelRequest, $connection)) { + $this->handle($connection->laravelRequest, $connection); } if (!$this->keepConnectionOpen) { diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index d45be23..7e157bb 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -12,7 +12,8 @@ use function GuzzleHttp\Psr7\parse_request; abstract class Controller implements HttpServerInterface { - use LoadsViews, ParsesIncomingRequest; + use LoadsViews; + use ParsesIncomingRequest; protected $keepConnectionOpen = false; @@ -29,6 +30,7 @@ abstract class Controller implements HttpServerInterface public function onClose(ConnectionInterface $connection) { + unset($connection->laravelRequest); unset($connection->requestBuffer); unset($connection->contentLength); unset($connection->request); diff --git a/app/Server/Configuration.php b/app/Server/Configuration.php index e033a6f..6009d30 100644 --- a/app/Server/Configuration.php +++ b/app/Server/Configuration.php @@ -26,4 +26,15 @@ class Configuration { return $this->port; } + + public function __isset($key) + { + return property_exists($this, $key) || ! is_null(config('expose.admin.'.$key)); + } + + public function __get($key) + { + dump(config('expose.admin')); + return $this->$key ?? config('expose.admin.'.$key); + } } diff --git a/app/Server/Factory.php b/app/Server/Factory.php index 81f7a95..b6c63e1 100644 --- a/app/Server/Factory.php +++ b/app/Server/Factory.php @@ -11,6 +11,8 @@ use App\Server\Http\Controllers\Admin\ListSitesController; use App\Server\Http\Controllers\Admin\ListUsersController; use App\Server\Http\Controllers\Admin\LoginController; use App\Server\Http\Controllers\Admin\RedirectToUsersController; +use App\Server\Http\Controllers\Admin\SaveSettingsController; +use App\Server\Http\Controllers\Admin\ShowSettingsController; use App\Server\Http\Controllers\Admin\StoreUsersController; use App\Server\Http\Controllers\Admin\VerifyLoginController; use App\Server\Http\Controllers\ControlMessageController; @@ -114,6 +116,8 @@ class Factory $this->router->get('/', RedirectToUsersController::class, $adminCondition); $this->router->get('/users', ListUsersController::class, $adminCondition); + $this->router->get('/settings', ShowSettingsController::class, $adminCondition); + $this->router->post('/settings', SaveSettingsController::class, $adminCondition); $this->router->post('/users', StoreUsersController::class, $adminCondition); $this->router->delete('/users/delete/{id}', DeleteUsersController::class, $adminCondition); $this->router->get('/sites', ListSitesController::class, $adminCondition); @@ -183,7 +187,7 @@ class Factory { app()->singleton(DatabaseInterface::class, function() { $factory = new \Clue\React\SQLite\Factory($this->loop); - return $factory->openLazy(base_path('database/expose.db')); + return $factory->openLazy(config('expose.admin.database', ':memory:')); }); return $this; @@ -210,7 +214,7 @@ class Factory public function validateAuthTokens(bool $validate) { - config()->set('expose.validate_auth_tokens', $validate); + config()->set('expose.admin.validate_auth_tokens', $validate); return $this; } diff --git a/app/Server/Http/Controllers/Admin/ListSitesController.php b/app/Server/Http/Controllers/Admin/ListSitesController.php index 4447266..6cabe7f 100644 --- a/app/Server/Http/Controllers/Admin/ListSitesController.php +++ b/app/Server/Http/Controllers/Admin/ListSitesController.php @@ -31,7 +31,7 @@ class ListSitesController extends AdminController public function handle(Request $request, ConnectionInterface $httpConnection) { try { - $sites = $this->getView('server.sites.index', [ + $sites = $this->getView($httpConnection, 'server.sites.index', [ 'scheme' => $this->configuration->port() === 443 ? 'https' : 'http', 'configuration' => $this->configuration, 'sites' => $this->connectionManager->getConnections() diff --git a/app/Server/Http/Controllers/Admin/ListUsersController.php b/app/Server/Http/Controllers/Admin/ListUsersController.php index c2a490b..b171053 100644 --- a/app/Server/Http/Controllers/Admin/ListUsersController.php +++ b/app/Server/Http/Controllers/Admin/ListUsersController.php @@ -29,7 +29,7 @@ class ListUsersController extends AdminController { $this->database->query('SELECT * FROM users ORDER by created_at DESC')->then(function (Result $result) use ($httpConnection) { $httpConnection->send( - respond_html($this->getView('server.users.index', ['users' => $result->rows])) + respond_html($this->getView($httpConnection, 'server.users.index', ['users' => $result->rows])) ); $httpConnection->close(); diff --git a/app/Server/Http/Controllers/Admin/SaveSettingsController.php b/app/Server/Http/Controllers/Admin/SaveSettingsController.php new file mode 100644 index 0000000..a46dc8f --- /dev/null +++ b/app/Server/Http/Controllers/Admin/SaveSettingsController.php @@ -0,0 +1,40 @@ +connectionManager = $connectionManager; + $this->configuration = $configuration; + } + + public function handle(Request $request, ConnectionInterface $httpConnection) + { + config()->set('expose.admin.validate_auth_tokens', $request->has('validate_auth_tokens')); + + $httpConnection->send(str(new Response(301, [ + 'Location' => '/settings' + ]))); + } +} diff --git a/app/Server/Http/Controllers/Admin/ShowSettingsController.php b/app/Server/Http/Controllers/Admin/ShowSettingsController.php new file mode 100644 index 0000000..6e9741f --- /dev/null +++ b/app/Server/Http/Controllers/Admin/ShowSettingsController.php @@ -0,0 +1,40 @@ +connectionManager = $connectionManager; + $this->configuration = $configuration; + } + + public function handle(Request $request, ConnectionInterface $httpConnection) + { + $httpConnection->send( + respond_html($this->getView($httpConnection, 'server.settings.index', [ + 'configuration' => $this->configuration, + ])) + ); + } +} diff --git a/app/Server/Http/Controllers/ControlMessageController.php b/app/Server/Http/Controllers/ControlMessageController.php index 27e41dc..141f02e 100644 --- a/app/Server/Http/Controllers/ControlMessageController.php +++ b/app/Server/Http/Controllers/ControlMessageController.php @@ -74,7 +74,7 @@ class ControlMessageController implements MessageComponentInterface protected function authenticate(ConnectionInterface $connection, $data) { - if (config('expose.validate_auth_tokens') === true) { + if (config('expose.admin.validate_auth_tokens') === true) { $this->verifyAuthToken($connection); } diff --git a/app/Server/Http/Controllers/TunnelMessageController.php b/app/Server/Http/Controllers/TunnelMessageController.php index 03fcd35..65c33e0 100644 --- a/app/Server/Http/Controllers/TunnelMessageController.php +++ b/app/Server/Http/Controllers/TunnelMessageController.php @@ -44,7 +44,7 @@ class TunnelMessageController extends Controller if (is_null($controlConnection)) { $httpConnection->send( - respond_html($this->getView('server.errors.404', ['subdomain' => $subdomain])) + respond_html($this->getView($httpConnection, 'server.errors.404', ['subdomain' => $subdomain]), 404) ); $httpConnection->close(); return; diff --git a/composer.json b/composer.json index b2873cd..e71b2fa 100644 --- a/composer.json +++ b/composer.json @@ -42,6 +42,7 @@ "nyholm/psr7": "^1.2", "phpunit/phpunit": "^8.5", "ratchet/pawl": "^0.3.4", + "react/http": "^0.8.6", "react/socket": "^1.4", "riverline/multipart-parser": "^2.0", "symfony/expression-language": "^5.0", diff --git a/composer.lock b/composer.lock index 0c53ea3..7787032 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "17ed4fb1b80fc6efe2594c3bfbbf3c7f", + "content-hash": "801c0ab8f694ff370f2eefea409a7fcc", "packages": [], "packages-dev": [ { @@ -4470,6 +4470,54 @@ ], "time": "2020-01-01T18:39:52+00:00" }, + { + "name": "react/http", + "version": "v0.8.6", + "source": { + "type": "git", + "url": "https://github.com/reactphp/http.git", + "reference": "248202e57195d06a4375f6d2f5c5b9ff9da3ea9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/http/zipball/248202e57195d06a4375f6d2f5c5b9ff9da3ea9e", + "reference": "248202e57195d06a4375f6d2f5c5b9ff9da3ea9e", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/promise": "^2.3 || ^1.2.1", + "react/promise-stream": "^1.1", + "react/socket": "^1.0 || ^0.8.3", + "react/stream": "^1.0 || ^0.7.1", + "ringcentral/psr7": "^1.2" + }, + "require-dev": { + "clue/block-react": "^1.1", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Http\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Event-driven, streaming plaintext HTTP and secure HTTPS server for ReactPHP", + "keywords": [ + "event-driven", + "http", + "https", + "reactphp", + "server", + "streaming" + ], + "time": "2020-01-12T13:15:06+00:00" + }, { "name": "react/http-client", "version": "v0.5.10", diff --git a/config/expose.php b/config/expose.php index dd39380..fd4a515 100644 --- a/config/expose.php +++ b/config/expose.php @@ -2,11 +2,15 @@ return [ 'host' => 'expose.dev', - 'port' => 8080, + 'port' => 443, 'auth_token' => '', 'admin' => [ + 'database' => base_path('database/expose.db'), + + 'validate_auth_tokens' => false, + /* |-------------------------------------------------------------------------- | Subdomain diff --git a/resources/views/server/layouts/app.twig b/resources/views/server/layouts/app.twig index 3707775..9dfdef8 100644 --- a/resources/views/server/layouts/app.twig +++ b/resources/views/server/layouts/app.twig @@ -14,15 +14,21 @@