mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-13 13:35:54 +00:00
wip
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
])));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
40
app/Server/Http/Controllers/Admin/SaveSettingsController.php
Normal file
40
app/Server/Http/Controllers/Admin/SaveSettingsController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Server\Http\Controllers\Admin;
|
||||
|
||||
use App\Contracts\ConnectionManager;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Server\Configuration;
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Http\Request;
|
||||
use Ratchet\ConnectionInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use function GuzzleHttp\Psr7\str;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
|
||||
class SaveSettingsController extends AdminController
|
||||
{
|
||||
/** @var ConnectionManager */
|
||||
protected $connectionManager;
|
||||
|
||||
/** @var Configuration */
|
||||
protected $configuration;
|
||||
|
||||
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
||||
{
|
||||
$this->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'
|
||||
])));
|
||||
}
|
||||
}
|
||||
40
app/Server/Http/Controllers/Admin/ShowSettingsController.php
Normal file
40
app/Server/Http/Controllers/Admin/ShowSettingsController.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Server\Http\Controllers\Admin;
|
||||
|
||||
use App\Contracts\ConnectionManager;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Server\Configuration;
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use Clue\React\SQLite\Result;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Http\Request;
|
||||
use Ratchet\ConnectionInterface;
|
||||
use Twig\Environment;
|
||||
use Twig\Loader\ArrayLoader;
|
||||
use function GuzzleHttp\Psr7\str;
|
||||
use function GuzzleHttp\Psr7\stream_for;
|
||||
|
||||
class ShowSettingsController extends AdminController
|
||||
{
|
||||
/** @var ConnectionManager */
|
||||
protected $connectionManager;
|
||||
|
||||
/** @var Configuration */
|
||||
protected $configuration;
|
||||
|
||||
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
||||
{
|
||||
$this->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,
|
||||
]))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
50
composer.lock
generated
50
composer.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,15 +14,21 @@
|
||||
</div>
|
||||
<div class="hidden sm:-my-px sm:ml-6 sm:flex">
|
||||
<a href="/users"
|
||||
class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">
|
||||
class="
|
||||
{% if request.is('users*') %} border-indigo-500 focus:border-indigo-700 text-gray-900 {% else %} border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300{% endif %}
|
||||
inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out">
|
||||
Users
|
||||
</a>
|
||||
<a href="/sites"
|
||||
class="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-indigo-500 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out">
|
||||
class="
|
||||
{% if request.is('sites') %} border-indigo-500 focus:border-indigo-700 text-gray-900 {% else %} border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300{% endif %}
|
||||
ml-8 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out">
|
||||
Shared sites
|
||||
</a>
|
||||
<a href="#"
|
||||
class="ml-8 inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out">
|
||||
<a href="/settings"
|
||||
class="
|
||||
{% if request.is('settings') %} border-indigo-500 focus:border-indigo-700 text-gray-900 {% else %} border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300{% endif %}
|
||||
ml-8 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium leading-5 focus:outline-none transition duration-150 ease-in-out">
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
|
||||
61
resources/views/server/settings/index.twig
Normal file
61
resources/views/server/settings/index.twig
Normal file
@@ -0,0 +1,61 @@
|
||||
{% extends "app" %}
|
||||
{% block title %}Settings{% endblock %}
|
||||
{% block content %}
|
||||
<div class="flex flex-col py-8">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<div>
|
||||
<div class="">
|
||||
<div class="">
|
||||
<div role="group" aria-labelledby="label-email">
|
||||
<div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-baseline">
|
||||
<div>
|
||||
<div class="text-base leading-6 font-medium text-gray-900 sm:text-sm sm:leading-5 sm:text-gray-700" id="label-email">
|
||||
Authentication
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:col-span-2">
|
||||
<div class="max-w-lg">
|
||||
<div class="relative flex items-start">
|
||||
<div class="absolute flex items-center h-5">
|
||||
<input id="authentication"
|
||||
type="checkbox"
|
||||
name="validate_auth_tokens"
|
||||
value="1"
|
||||
{% if configuration.validate_auth_tokens %} checked="checked" {% endif %}
|
||||
class="form-checkbox h-4 w-4 text-indigo-600 transition duration-150 ease-in-out" />
|
||||
</div>
|
||||
<div class="pl-7 text-sm leading-5">
|
||||
<label for="authentication" class="font-medium text-gray-700">Require authentication tokens</label>
|
||||
<p class="text-gray-500">Only allow connection from clients with valid authentication tokens</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 border-t border-gray-200 pt-5">
|
||||
<div class="flex justify-end">
|
||||
<span class="inline-flex rounded-md shadow-sm">
|
||||
<button type="button"
|
||||
class="py-2 px-4 border border-gray-300 rounded-md text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition duration-150 ease-in-out">
|
||||
Cancel
|
||||
</button>
|
||||
</span>
|
||||
<span class="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<button type="submit"
|
||||
class="inline-flex justify-center py-2 px-4 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition duration-150 ease-in-out">
|
||||
Save
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -2,11 +2,15 @@
|
||||
|
||||
namespace Tests\Feature\Server;
|
||||
|
||||
use App\Contracts\ConnectionManager;
|
||||
use App\Http\Server;
|
||||
use App\Server\Factory;
|
||||
use Clue\React\Buzz\Browser;
|
||||
use Clue\React\Buzz\Message\ResponseException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Ratchet\Server\IoConnection;
|
||||
use React\Socket\Connector;
|
||||
use Tests\Feature\TestCase;
|
||||
|
||||
@@ -49,10 +53,6 @@ class AdminTest extends TestCase
|
||||
/** @test */
|
||||
public function it_accepts_valid_credentials()
|
||||
{
|
||||
$this->app['config']['expose.admin.users'] = [
|
||||
'username' => 'secret',
|
||||
];
|
||||
|
||||
/** @var ResponseInterface $response */
|
||||
$response = $this->await($this->browser->get('http://127.0.0.1:8080', [
|
||||
'Host' => 'expose.localhost',
|
||||
@@ -61,8 +61,67 @@ class AdminTest extends TestCase
|
||||
$this->assertSame(200, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_allows_saving_settings()
|
||||
{
|
||||
$this->app['config']['expose.admin.validate_auth_tokens'] = false;
|
||||
|
||||
/** @var ResponseInterface $response */
|
||||
$this->await($this->browser->post('http://127.0.0.1:8080/settings', [
|
||||
'Host' => 'expose.localhost',
|
||||
'Authorization' => base64_encode("username:secret"),
|
||||
'Content-Type' => 'application/json'
|
||||
], json_encode([
|
||||
'validate_auth_tokens' => true,
|
||||
])));
|
||||
|
||||
$this->assertTrue(config('expose.admin.validate_auth_tokens'));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_create_users()
|
||||
{
|
||||
$this->await($this->browser->post('http://127.0.0.1:8080/users', [
|
||||
'Host' => 'expose.localhost',
|
||||
'Authorization' => base64_encode("username:secret"),
|
||||
'Content-Type' => 'application/json'
|
||||
], json_encode([
|
||||
'name' => 'Marcel',
|
||||
])));
|
||||
|
||||
$this->assertDatabaseHasResults('SELECT * FROM users WHERE name = "Marcel"');
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_can_list_all_currently_connected_sites()
|
||||
{
|
||||
/** @var ConnectionManager $connectionManager */
|
||||
$connectionManager = app(ConnectionManager::class);
|
||||
|
||||
$connection = \Mockery::mock(IoConnection::class);
|
||||
$connectionManager->storeConnection('some-host.text', 'fixed-subdomain', $connection);
|
||||
|
||||
/** @var Response $response */
|
||||
$response = $this->await($this->browser->get('http://127.0.0.1:8080/sites', [
|
||||
'Host' => 'expose.localhost',
|
||||
'Authorization' => base64_encode("username:secret"),
|
||||
'Content-Type' => 'application/json'
|
||||
]));
|
||||
|
||||
$body = $response->getBody()->getContents();
|
||||
|
||||
$this->assertTrue(Str::contains($body, 'some-host.text'));
|
||||
$this->assertTrue(Str::contains($body, 'fixed-subdomain'));
|
||||
}
|
||||
|
||||
protected function startServer()
|
||||
{
|
||||
$this->app['config']['expose.admin.database'] = ':memory:';
|
||||
|
||||
$this->app['config']['expose.admin.users'] = [
|
||||
'username' => 'secret',
|
||||
];
|
||||
|
||||
$this->serverFactory = new Factory();
|
||||
|
||||
$this->serverFactory->setLoop($this->loop)
|
||||
|
||||
103
tests/Feature/Server/TunnelTest.php
Normal file
103
tests/Feature/Server/TunnelTest.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature\Server;
|
||||
|
||||
use App\Client\Client;
|
||||
use App\Contracts\ConnectionManager;
|
||||
use App\Server\Factory;
|
||||
use Clue\React\Buzz\Browser;
|
||||
use Clue\React\Buzz\Message\ResponseException;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ratchet\Client\WebSocket;
|
||||
use Ratchet\ConnectionInterface;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\Http\Server;
|
||||
use Tests\Feature\TestCase;
|
||||
use function Ratchet\Client\connect;
|
||||
|
||||
class TunnelTest extends TestCase
|
||||
{
|
||||
/** @var Browser */
|
||||
protected $browser;
|
||||
|
||||
/** @var Factory */
|
||||
protected $serverFactory;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->browser = new Browser($this->loop);
|
||||
|
||||
$this->startServer();
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_returns_404_for_non_existing_clients()
|
||||
{
|
||||
$this->expectException(ResponseException::class);
|
||||
$this->expectExceptionMessage(404);
|
||||
|
||||
$response = $this->await($this->browser->get('http://127.0.0.1:8080/', [
|
||||
'Host' => 'tunnel.localhost'
|
||||
]));
|
||||
}
|
||||
|
||||
/** @test */
|
||||
public function it_sends_incoming_requests_to_the_connected_client()
|
||||
{
|
||||
$this->createTestHttpServer();
|
||||
|
||||
/**
|
||||
* We create an expose client that connects to our server and shares
|
||||
* the created test HTTP server
|
||||
*/
|
||||
$client = $this->createClient();
|
||||
$this->await($client->connectToServer('127.0.0.1:8085', 'tunnel'));
|
||||
|
||||
/**
|
||||
* Once the client is connected, we perform a GET request on the
|
||||
* created tunnel.
|
||||
*/
|
||||
$response = $this->await($this->browser->get('http://127.0.0.1:8080/', [
|
||||
'Host' => 'tunnel.localhost'
|
||||
]));
|
||||
|
||||
$this->assertSame('Hello World!', $response->getBody()->getContents());
|
||||
}
|
||||
|
||||
|
||||
protected function startServer()
|
||||
{
|
||||
$this->app['config']['expose.admin.database'] = ':memory:';
|
||||
|
||||
$this->serverFactory = new Factory();
|
||||
|
||||
$this->serverFactory->setLoop($this->loop)
|
||||
->setHost('127.0.0.1')
|
||||
->setHostname('localhost')
|
||||
->createServer();
|
||||
}
|
||||
|
||||
protected function createClient()
|
||||
{
|
||||
(new \App\Client\Factory())
|
||||
->setLoop($this->loop)
|
||||
->setHost('127.0.0.1')
|
||||
->setPort(8080)
|
||||
->createClient();
|
||||
|
||||
return app(Client::class);
|
||||
}
|
||||
|
||||
protected function createTestHttpServer()
|
||||
{
|
||||
$server = new Server(function (ServerRequestInterface $request) {
|
||||
return new Response(200, ['Content-Type' => 'text/plain'], "Hello World!");
|
||||
});
|
||||
|
||||
$socket = new \React\Socket\Server(8085, $this->loop);
|
||||
$server->listen($socket);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Illuminate\Support\Str;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use React\EventLoop\Factory;
|
||||
use React\EventLoop\LoopInterface;
|
||||
use React\EventLoop\StreamSelectLoop;
|
||||
@@ -33,4 +37,13 @@ abstract class TestCase extends \Tests\TestCase
|
||||
{
|
||||
return await($promise, $loop ?? $this->loop, $timeout ?? static::AWAIT_TIMEOUT);
|
||||
}
|
||||
|
||||
protected function assertDatabaseHasResults($query)
|
||||
{
|
||||
$database = app(DatabaseInterface::class);
|
||||
|
||||
$result = $this->await($database->query($query));
|
||||
|
||||
$this->assertGreaterThanOrEqual(1, count($result->rows));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
use LaravelZero\Framework\Testing\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
|
||||
Reference in New Issue
Block a user