mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-14 14:05:54 +00:00
Compare commits
5 Commits
master
...
fix-http-p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
763b45a77e | ||
|
|
f137ea298b | ||
|
|
2f457352c5 | ||
|
|
c5cdd8c352 | ||
|
|
6f72d719bf |
36
.github/workflows/run-tests.yml
vendored
36
.github/workflows/run-tests.yml
vendored
@@ -1,36 +0,0 @@
|
|||||||
name: Tests
|
|
||||||
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
os: [macos-latest, ubuntu-latest]
|
|
||||||
php: [8.0, 8.1]
|
|
||||||
stability: [prefer-stable]
|
|
||||||
|
|
||||||
name: P${{ matrix.php }} - ${{ matrix.stability }} - ${{ matrix.os }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
uses: shivammathur/setup-php@v2
|
|
||||||
with:
|
|
||||||
php-version: ${{ matrix.php }}
|
|
||||||
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
|
|
||||||
coverage: none
|
|
||||||
|
|
||||||
- name: Setup problem matchers
|
|
||||||
run: |
|
|
||||||
echo "::add-matcher::${{ runner.tool_cache }}/php.json"
|
|
||||||
echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
|
|
||||||
- name: Install dependencies
|
|
||||||
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction
|
|
||||||
|
|
||||||
- name: Execute tests
|
|
||||||
run: vendor/bin/phpunit
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM php:8.0-cli
|
FROM php:7.4-cli
|
||||||
|
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y git libzip-dev zip
|
RUN apt-get install -y git libzip-dev zip
|
||||||
@@ -20,6 +20,5 @@ ENV username=username
|
|||||||
ENV password=password
|
ENV password=password
|
||||||
ENV exposeConfigPath=/src/config/expose.php
|
ENV exposeConfigPath=/src/config/expose.php
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /usr/bin/
|
CMD sed -i "s|username|${username}|g" ${exposeConfigPath} && sed -i "s|password|${password}|g" ${exposeConfigPath} && php expose serve ${domain} --port ${port} --validateAuthTokens
|
||||||
RUN chmod 755 /usr/bin/docker-entrypoint.sh
|
ENTRYPOINT ["/src/expose"]
|
||||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -1,4 +1,4 @@
|
|||||||

|

|
||||||
|
|
||||||
# Expose
|
# Expose
|
||||||
|
|
||||||
@@ -6,17 +6,11 @@
|
|||||||
[](https://scrutinizer-ci.com/g/beyondcode/expose)
|
[](https://scrutinizer-ci.com/g/beyondcode/expose)
|
||||||
[](https://packagist.org/packages/beyondcode/expose)
|
[](https://packagist.org/packages/beyondcode/expose)
|
||||||
|
|
||||||
An open-source ngrok alternative - written in PHP.
|
A completely open-source ngrok alternative - written in pure PHP.
|
||||||
|
|
||||||
## ⭐️ Managed Expose & Expose Pro ⭐️
|
|
||||||
|
|
||||||
You can use a managed version with our proprietary platform and our free (EU) test server at the [official website](https://expose.dev). Upgrade to Expose Pro to use our global server network with your own custom domains and get high-speed tunnels all over the world.
|
|
||||||
|
|
||||||
[Create an account](https://expose.dev)
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
For installation instructions of your own server, in-depth usage and deployment details, please take a look at the [official documentation](https://expose.dev/docs).
|
For installation instructions, in-depth usage and deployment details, please take a look at the [official documentation](https://beyondco.de/docs/expose/).
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Callbacks;
|
|
||||||
|
|
||||||
use App\Server\Connections\ControlConnection;
|
|
||||||
use Clue\React\Buzz\Browser;
|
|
||||||
|
|
||||||
class WebHookConnectionCallback
|
|
||||||
{
|
|
||||||
/** @var Browser */
|
|
||||||
protected $browser;
|
|
||||||
|
|
||||||
public function __construct(Browser $browser)
|
|
||||||
{
|
|
||||||
$this->browser = $browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(ControlConnection $connection)
|
|
||||||
{
|
|
||||||
$this->browser->post(config('expose.admin.connection_callbacks.webhook.url'), [
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
'Accept' => 'application/json',
|
|
||||||
'X-Signature' => $this->generateWebhookSigningSecret($connection),
|
|
||||||
], json_encode($connection->toArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function generateWebhookSigningSecret(ControlConnection $connection)
|
|
||||||
{
|
|
||||||
return hash_hmac('sha256', $connection->client_id, config('expose.admin.connection_callbacks.webhook.secret'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,10 +31,6 @@ class Client
|
|||||||
/** @var int */
|
/** @var int */
|
||||||
protected $timeConnected = 0;
|
protected $timeConnected = 0;
|
||||||
|
|
||||||
/** @var bool */
|
|
||||||
protected $shouldExit = true;
|
|
||||||
|
|
||||||
public static $user = [];
|
|
||||||
public static $subdomains = [];
|
public static $subdomains = [];
|
||||||
|
|
||||||
public function __construct(LoopInterface $loop, Configuration $configuration, CliRequestLogger $logger)
|
public function __construct(LoopInterface $loop, Configuration $configuration, CliRequestLogger $logger)
|
||||||
@@ -44,17 +40,12 @@ class Client
|
|||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldExit($shouldExit = true)
|
public function share(string $sharedUrl, array $subdomains = [])
|
||||||
{
|
|
||||||
$this->shouldExit = $shouldExit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function share(string $sharedUrl, array $subdomains = [], $serverHost = null)
|
|
||||||
{
|
{
|
||||||
$sharedUrl = $this->prepareSharedUrl($sharedUrl);
|
$sharedUrl = $this->prepareSharedUrl($sharedUrl);
|
||||||
|
|
||||||
foreach ($subdomains as $subdomain) {
|
foreach ($subdomains as $subdomain) {
|
||||||
$this->connectToServer($sharedUrl, $subdomain, $serverHost, $this->configuration->auth());
|
$this->connectToServer($sharedUrl, $subdomain, $this->configuration->auth());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,42 +60,35 @@ class Client
|
|||||||
return $sharedUrl;
|
return $sharedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = Arr::get($parsedUrl, 'host', Arr::get($parsedUrl, 'path'));
|
$host = Arr::get($parsedUrl, 'host', Arr::get($parsedUrl, 'path', 'localhost'));
|
||||||
|
$scheme = Arr::get($parsedUrl, 'scheme', 'http');
|
||||||
|
$port = Arr::get($parsedUrl, 'port', $scheme === 'https' ? 443 : 80);
|
||||||
|
|
||||||
if (Arr::get($parsedUrl, 'scheme') === 'https') {
|
return sprintf('%s://%s:%s', $scheme, $host, $port);
|
||||||
$url .= ':443';
|
|
||||||
}
|
|
||||||
if (! is_null($port = Arr::get($parsedUrl, 'port'))) {
|
|
||||||
$url .= ":{$port}";
|
|
||||||
}
|
|
||||||
|
|
||||||
return $url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connectToServer(string $sharedUrl, $subdomain, $serverHost = null, $authToken = ''): PromiseInterface
|
public function connectToServer(string $sharedUrl, $subdomain, $authToken = ''): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
$promise = $deferred->promise();
|
$promise = $deferred->promise();
|
||||||
|
|
||||||
$exposeVersion = config('app.version');
|
|
||||||
|
|
||||||
$wsProtocol = $this->configuration->port() === 443 ? 'wss' : 'ws';
|
$wsProtocol = $this->configuration->port() === 443 ? 'wss' : 'ws';
|
||||||
|
|
||||||
connect($wsProtocol."://{$this->configuration->host()}:{$this->configuration->port()}/expose/control?authToken={$authToken}&version={$exposeVersion}", [], [
|
connect($wsProtocol."://{$this->configuration->host()}:{$this->configuration->port()}/expose/control?authToken={$authToken}", [], [
|
||||||
'X-Expose-Control' => 'enabled',
|
'X-Expose-Control' => 'enabled',
|
||||||
], $this->loop)
|
], $this->loop)
|
||||||
->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain, $serverHost, $deferred, $authToken) {
|
->then(function (WebSocket $clientConnection) use ($sharedUrl, $subdomain, $deferred, $authToken) {
|
||||||
$this->connectionRetries = 0;
|
$this->connectionRetries = 0;
|
||||||
|
|
||||||
$connection = ControlConnection::create($clientConnection);
|
$connection = ControlConnection::create($clientConnection);
|
||||||
|
|
||||||
$connection->authenticate($sharedUrl, $subdomain, $serverHost);
|
$connection->authenticate($sharedUrl, $subdomain);
|
||||||
|
|
||||||
$clientConnection->on('close', function () use ($sharedUrl, $subdomain, $serverHost, $authToken) {
|
$clientConnection->on('close', function () use ($sharedUrl, $subdomain, $authToken) {
|
||||||
$this->logger->error('Connection to server closed.');
|
$this->logger->error('Connection to server closed.');
|
||||||
|
|
||||||
$this->retryConnectionOrExit(function () use ($sharedUrl, $subdomain, $serverHost, $authToken) {
|
$this->retryConnectionOrExit(function () use ($sharedUrl, $subdomain, $authToken) {
|
||||||
$this->connectToServer($sharedUrl, $subdomain, $serverHost, $authToken);
|
$this->connectToServer($sharedUrl, $subdomain, $authToken);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,22 +102,19 @@ class Client
|
|||||||
|
|
||||||
$connection->on('authenticated', function ($data) use ($deferred, $sharedUrl) {
|
$connection->on('authenticated', function ($data) use ($deferred, $sharedUrl) {
|
||||||
$httpProtocol = $this->configuration->port() === 443 ? 'https' : 'http';
|
$httpProtocol = $this->configuration->port() === 443 ? 'https' : 'http';
|
||||||
|
$host = $this->configuration->host();
|
||||||
|
|
||||||
$httpPort = $httpProtocol === 'https' ? '' : ":{$this->configuration->port()}";
|
if ($httpProtocol !== 'https') {
|
||||||
|
$host .= ":{$this->configuration->port()}";
|
||||||
$host = $data->server_host ?? $this->configuration->host();
|
}
|
||||||
|
|
||||||
$this->configuration->setServerHost($host);
|
|
||||||
|
|
||||||
$this->logger->info($data->message);
|
$this->logger->info($data->message);
|
||||||
$this->logger->info("Shared URL:\t\t<options=bold>{$sharedUrl}</>");
|
$this->logger->info("Local-URL:\t\t{$sharedUrl}");
|
||||||
$this->logger->info("Dashboard:\t\t<options=bold>http://127.0.0.1:".config()->get('expose.dashboard_port').'</>');
|
$this->logger->info("Dashboard-URL:\t\thttp://127.0.0.1:".config()->get('expose.dashboard_port'));
|
||||||
$this->logger->info("Public HTTP:\t\t<options=bold>http://{$data->subdomain}.{$host}{$httpPort}</>");
|
$this->logger->info("Expose-URL:\t\t{$httpProtocol}://{$data->subdomain}.{$host}");
|
||||||
$this->logger->info("Public HTTPS:\t\t<options=bold>https://{$data->subdomain}.{$host}</>");
|
|
||||||
$this->logger->line('');
|
$this->logger->line('');
|
||||||
|
|
||||||
static::$subdomains[] = "{$httpProtocol}://{$data->subdomain}.{$host}";
|
static::$subdomains[] = "{$httpProtocol}://{$data->subdomain}.{$host}";
|
||||||
static::$user = $data->user ?? ['can_specify_subdomains' => 0];
|
|
||||||
|
|
||||||
$deferred->resolve($data);
|
$deferred->resolve($data);
|
||||||
});
|
});
|
||||||
@@ -160,9 +141,8 @@ class Client
|
|||||||
$promise = $deferred->promise();
|
$promise = $deferred->promise();
|
||||||
|
|
||||||
$wsProtocol = $this->configuration->port() === 443 ? 'wss' : 'ws';
|
$wsProtocol = $this->configuration->port() === 443 ? 'wss' : 'ws';
|
||||||
$exposeVersion = config('app.version');
|
|
||||||
|
|
||||||
connect($wsProtocol."://{$this->configuration->host()}:{$this->configuration->port()}/expose/control?authToken={$authToken}&version={$exposeVersion}", [], [
|
connect($wsProtocol."://{$this->configuration->host()}:{$this->configuration->port()}/expose/control?authToken={$authToken}", [], [
|
||||||
'X-Expose-Control' => 'enabled',
|
'X-Expose-Control' => 'enabled',
|
||||||
], $this->loop)
|
], $this->loop)
|
||||||
->then(function (WebSocket $clientConnection) use ($port, $deferred, $authToken) {
|
->then(function (WebSocket $clientConnection) use ($port, $deferred, $authToken) {
|
||||||
@@ -186,9 +166,9 @@ class Client
|
|||||||
$host = $this->configuration->host();
|
$host = $this->configuration->host();
|
||||||
|
|
||||||
$this->logger->info($data->message);
|
$this->logger->info($data->message);
|
||||||
$this->logger->info("Local-Port:\t\t<options=bold>{$port}</>");
|
$this->logger->info("Local-Port:\t\t{$port}");
|
||||||
$this->logger->info("Shared-Port:\t\t<options=bold>{$data->shared_port}</>");
|
$this->logger->info("Shared-Port:\t\t{$data->shared_port}");
|
||||||
$this->logger->info("Expose-URL:\t\t<options=bold>tcp://{$host}:{$data->shared_port}</>");
|
$this->logger->info("Expose-URL:\t\ttcp://{$host}:{$data->shared_port}.");
|
||||||
$this->logger->line('');
|
$this->logger->line('');
|
||||||
|
|
||||||
$deferred->resolve($data);
|
$deferred->resolve($data);
|
||||||
@@ -216,10 +196,6 @@ class Client
|
|||||||
$this->logger->info($data->message);
|
$this->logger->info($data->message);
|
||||||
});
|
});
|
||||||
|
|
||||||
$connection->on('warning', function ($data) {
|
|
||||||
$this->logger->warn($data->message);
|
|
||||||
});
|
|
||||||
|
|
||||||
$connection->on('error', function ($data) {
|
$connection->on('error', function ($data) {
|
||||||
$this->logger->error($data->message);
|
$this->logger->error($data->message);
|
||||||
});
|
});
|
||||||
@@ -250,9 +226,7 @@ class Client
|
|||||||
$deferred->reject();
|
$deferred->reject();
|
||||||
|
|
||||||
$this->loop->futureTick(function () {
|
$this->loop->futureTick(function () {
|
||||||
if ($this->shouldExit) {
|
exit(1);
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,27 +7,19 @@ class Configuration
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
protected $host;
|
protected $host;
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $serverHost;
|
|
||||||
|
|
||||||
/** @var int */
|
/** @var int */
|
||||||
protected $port;
|
protected $port;
|
||||||
|
|
||||||
/** @var string|null */
|
/** @var string|null */
|
||||||
protected $auth;
|
protected $auth;
|
||||||
|
|
||||||
/** @var string|null */
|
public function __construct(string $host, int $port, ?string $auth = null)
|
||||||
protected $basicAuth;
|
|
||||||
|
|
||||||
public function __construct(string $host, int $port, ?string $auth = null, ?string $basicAuth = null)
|
|
||||||
{
|
{
|
||||||
$this->serverHost = $this->host = $host;
|
$this->host = $host;
|
||||||
|
|
||||||
$this->port = $port;
|
$this->port = $port;
|
||||||
|
|
||||||
$this->auth = $auth;
|
$this->auth = $auth;
|
||||||
|
|
||||||
$this->basicAuth = $basicAuth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function host(): string
|
public function host(): string
|
||||||
@@ -35,26 +27,11 @@ class Configuration
|
|||||||
return $this->host;
|
return $this->host;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function serverHost(): string
|
|
||||||
{
|
|
||||||
return $this->serverHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setServerHost($serverHost)
|
|
||||||
{
|
|
||||||
$this->serverHost = $serverHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function auth(): ?string
|
public function auth(): ?string
|
||||||
{
|
{
|
||||||
return $this->auth;
|
return $this->auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function basicAuth(): ?string
|
|
||||||
{
|
|
||||||
return $this->basicAuth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function port(): int
|
public function port(): int
|
||||||
{
|
{
|
||||||
return intval($this->port);
|
return intval($this->port);
|
||||||
@@ -63,7 +40,7 @@ class Configuration
|
|||||||
public function getUrl(string $subdomain): string
|
public function getUrl(string $subdomain): string
|
||||||
{
|
{
|
||||||
$httpProtocol = $this->port() === 443 ? 'https' : 'http';
|
$httpProtocol = $this->port() === 443 ? 'https' : 'http';
|
||||||
$host = $this->serverHost();
|
$host = $this->host();
|
||||||
|
|
||||||
if ($httpProtocol !== 'https') {
|
if ($httpProtocol !== 'https') {
|
||||||
$host .= ":{$this->port()}";
|
$host .= ":{$this->port()}";
|
||||||
|
|||||||
@@ -57,14 +57,13 @@ class ControlConnection
|
|||||||
$this->proxyManager->createTcpProxy($this->clientId, $data);
|
$this->proxyManager->createTcpProxy($this->clientId, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function authenticate(string $sharedHost, string $subdomain, $serverHost = null)
|
public function authenticate(string $sharedHost, string $subdomain)
|
||||||
{
|
{
|
||||||
$this->socket->send(json_encode([
|
$this->socket->send(json_encode([
|
||||||
'event' => 'authenticate',
|
'event' => 'authenticate',
|
||||||
'data' => [
|
'data' => [
|
||||||
'type' => 'http',
|
'type' => 'http',
|
||||||
'host' => $sharedHost,
|
'host' => $sharedHost,
|
||||||
'server_host' => $serverHost,
|
|
||||||
'subdomain' => empty($subdomain) ? null : $subdomain,
|
'subdomain' => empty($subdomain) ? null : $subdomain,
|
||||||
],
|
],
|
||||||
]));
|
]));
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Exceptions;
|
|
||||||
|
|
||||||
class InvalidServerProvided extends \Exception
|
|
||||||
{
|
|
||||||
public function __construct($server)
|
|
||||||
{
|
|
||||||
$message = "No such server {$server}.";
|
|
||||||
|
|
||||||
parent::__construct($message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Client;
|
namespace App\Client;
|
||||||
|
|
||||||
use App\Client\Fileserver\Fileserver;
|
|
||||||
use App\Client\Http\Controllers\AttachDataToLogController;
|
use App\Client\Http\Controllers\AttachDataToLogController;
|
||||||
use App\Client\Http\Controllers\ClearLogsController;
|
use App\Client\Http\Controllers\ClearLogsController;
|
||||||
use App\Client\Http\Controllers\CreateTunnelController;
|
use App\Client\Http\Controllers\CreateTunnelController;
|
||||||
@@ -28,18 +27,12 @@ class Factory
|
|||||||
/** @var string */
|
/** @var string */
|
||||||
protected $auth = '';
|
protected $auth = '';
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $basicAuth;
|
|
||||||
|
|
||||||
/** @var \React\EventLoop\LoopInterface */
|
/** @var \React\EventLoop\LoopInterface */
|
||||||
protected $loop;
|
protected $loop;
|
||||||
|
|
||||||
/** @var App */
|
/** @var App */
|
||||||
protected $app;
|
protected $app;
|
||||||
|
|
||||||
/** @var Fileserver */
|
|
||||||
protected $fileserver;
|
|
||||||
|
|
||||||
/** @var RouteGenerator */
|
/** @var RouteGenerator */
|
||||||
protected $router;
|
protected $router;
|
||||||
|
|
||||||
@@ -70,13 +63,6 @@ class Factory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setBasicAuth(?string $basicAuth)
|
|
||||||
{
|
|
||||||
$this->basicAuth = $basicAuth;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLoop(LoopInterface $loop)
|
public function setLoop(LoopInterface $loop)
|
||||||
{
|
{
|
||||||
$this->loop = $loop;
|
$this->loop = $loop;
|
||||||
@@ -87,7 +73,7 @@ class Factory
|
|||||||
protected function bindConfiguration()
|
protected function bindConfiguration()
|
||||||
{
|
{
|
||||||
app()->singleton(Configuration::class, function ($app) {
|
app()->singleton(Configuration::class, function ($app) {
|
||||||
return new Configuration($this->host, $this->port, $this->auth, $this->basicAuth);
|
return new Configuration($this->host, $this->port, $this->auth);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +102,9 @@ class Factory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function share($sharedUrl, $subdomain = null, $serverHost = null)
|
public function share($sharedUrl, $subdomain = null)
|
||||||
{
|
{
|
||||||
app('expose.client')->share($sharedUrl, $subdomain, $serverHost);
|
app('expose.client')->share($sharedUrl, $subdomain);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@@ -130,15 +116,6 @@ class Factory
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shareFolder(string $folder, string $name, $subdomain = null, $serverHost = null)
|
|
||||||
{
|
|
||||||
$host = $this->createFileServer($folder, $name);
|
|
||||||
|
|
||||||
$this->share($host, $subdomain, $serverHost);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function addRoutes()
|
protected function addRoutes()
|
||||||
{
|
{
|
||||||
$this->router->get('/', DashboardController::class);
|
$this->router->get('/', DashboardController::class);
|
||||||
@@ -150,54 +127,40 @@ class Factory
|
|||||||
$this->router->post('/api/logs/{request_id}/data', AttachDataToLogController::class);
|
$this->router->post('/api/logs/{request_id}/data', AttachDataToLogController::class);
|
||||||
$this->router->get('/api/logs/clear', ClearLogsController::class);
|
$this->router->get('/api/logs/clear', ClearLogsController::class);
|
||||||
|
|
||||||
$this->app->route('/socket', new WsServer(new Socket()), ['*'], '');
|
$this->app->route('/socket', new WsServer(new Socket()), ['*']);
|
||||||
|
|
||||||
foreach ($this->router->getRoutes()->all() as $name => $route) {
|
foreach ($this->router->getRoutes()->all() as $name => $route) {
|
||||||
$this->app->routes->add($name, $route);
|
$this->app->routes->add($name, $route);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function detectNextAvailablePort($startPort = 4040): int
|
protected function detectNextFreeDashboardPort($port = 4040): int
|
||||||
{
|
{
|
||||||
while (is_resource(@fsockopen('127.0.0.1', $startPort))) {
|
while (is_resource(@fsockopen('127.0.0.1', $port))) {
|
||||||
$startPort++;
|
$port++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $startPort;
|
return $port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createHttpServer()
|
public function createHttpServer()
|
||||||
{
|
{
|
||||||
$dashboardPort = $this->detectNextAvailablePort();
|
$dashboardPort = $this->detectNextFreeDashboardPort();
|
||||||
|
|
||||||
config()->set('expose.dashboard_port', $dashboardPort);
|
config()->set('expose.dashboard_port', $dashboardPort);
|
||||||
|
|
||||||
$this->app = new App('0.0.0.0', $dashboardPort, '0.0.0.0', $this->loop);
|
$this->app = new App('127.0.0.1', $dashboardPort, '0.0.0.0', $this->loop);
|
||||||
|
|
||||||
$this->addRoutes();
|
$this->addRoutes();
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createFileServer(string $folder, string $name)
|
|
||||||
{
|
|
||||||
$port = $this->detectNextAvailablePort(8090);
|
|
||||||
|
|
||||||
$this->fileserver = new Fileserver($folder, $name, $port, '0.0.0.0', $this->loop);
|
|
||||||
|
|
||||||
return "127.0.0.1:{$port}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getApp(): App
|
public function getApp(): App
|
||||||
{
|
{
|
||||||
return $this->app;
|
return $this->app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getFileserver(): Fileserver
|
|
||||||
{
|
|
||||||
return $this->fileserver;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$this->loop->run();
|
$this->loop->run();
|
||||||
|
|||||||
@@ -1,136 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Fileserver;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Concerns\LoadsViews;
|
|
||||||
use App\Http\QueryParameters;
|
|
||||||
use GuzzleHttp\Psr7\ServerRequest;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\EventLoop\LoopInterface;
|
|
||||||
use React\Http\Message\Response;
|
|
||||||
use React\Stream\ReadableResourceStream;
|
|
||||||
use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
|
|
||||||
use Symfony\Component\Finder\Finder;
|
|
||||||
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
|
|
||||||
|
|
||||||
class ConnectionHandler
|
|
||||||
{
|
|
||||||
use LoadsViews;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $rootFolder;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $name;
|
|
||||||
|
|
||||||
/** @var LoopInterface */
|
|
||||||
protected $loop;
|
|
||||||
|
|
||||||
public function __construct(string $rootFolder, string $name, LoopInterface $loop)
|
|
||||||
{
|
|
||||||
$this->rootFolder = $rootFolder;
|
|
||||||
$this->name = $name;
|
|
||||||
$this->loop = $loop;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(ServerRequestInterface $request)
|
|
||||||
{
|
|
||||||
$request = $this->createLaravelRequest($request);
|
|
||||||
$targetPath = realpath($this->rootFolder.DIRECTORY_SEPARATOR.$request->path());
|
|
||||||
|
|
||||||
if (! $this->isValidTarget($targetPath)) {
|
|
||||||
return new Response(404);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_dir($targetPath)) {
|
|
||||||
// Directory listing
|
|
||||||
$directoryContent = Finder::create()
|
|
||||||
->depth(0)
|
|
||||||
->sort(function ($a, $b) {
|
|
||||||
return strcmp(strtolower($a->getRealpath()), strtolower($b->getRealpath()));
|
|
||||||
})
|
|
||||||
->in($targetPath);
|
|
||||||
|
|
||||||
if ($this->name !== '') {
|
|
||||||
$directoryContent->name($this->name);
|
|
||||||
}
|
|
||||||
|
|
||||||
$parentPath = explode('/', $request->path());
|
|
||||||
array_pop($parentPath);
|
|
||||||
$parentPath = implode('/', $parentPath);
|
|
||||||
|
|
||||||
return new Response(
|
|
||||||
200,
|
|
||||||
['Content-Type' => 'text/html'],
|
|
||||||
$this->getView(null, 'client.fileserver', [
|
|
||||||
'currentPath' => $request->path(),
|
|
||||||
'parentPath' => $parentPath,
|
|
||||||
'directory' => $targetPath,
|
|
||||||
'directoryContent' => $directoryContent,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_file($targetPath)) {
|
|
||||||
return new Response(
|
|
||||||
200,
|
|
||||||
['Content-Type' => mime_content_type($targetPath)],
|
|
||||||
new ReadableResourceStream(fopen($targetPath, 'r'), $this->loop)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function isValidTarget(string $targetPath): bool
|
|
||||||
{
|
|
||||||
if (! file_exists($targetPath)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->name !== '') {
|
|
||||||
$filter = new class(basename($targetPath), [$this->name]) extends FilenameFilterIterator
|
|
||||||
{
|
|
||||||
protected $filename;
|
|
||||||
|
|
||||||
public function __construct(string $filename, array $matchPatterns)
|
|
||||||
{
|
|
||||||
$this->filename = $filename;
|
|
||||||
|
|
||||||
foreach ($matchPatterns as $pattern) {
|
|
||||||
$this->matchRegexps[] = $this->toRegex($pattern);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function accept()
|
|
||||||
{
|
|
||||||
return $this->isAccepted($this->filename);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return $filter->accept();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function createLaravelRequest(ServerRequestInterface $request): Request
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
parse_str($request->getBody(), $bodyParameters);
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
$bodyParameters = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$serverRequest = (new ServerRequest(
|
|
||||||
$request->getMethod(),
|
|
||||||
$request->getUri(),
|
|
||||||
$request->getHeaders(),
|
|
||||||
$request->getBody(),
|
|
||||||
$request->getProtocolVersion(),
|
|
||||||
))
|
|
||||||
->withQueryParams(QueryParameters::create($request)->all())
|
|
||||||
->withParsedBody($bodyParameters);
|
|
||||||
|
|
||||||
return Request::createFromBase((new HttpFoundationFactory)->createRequest($serverRequest));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Fileserver;
|
|
||||||
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
use React\EventLoop\LoopInterface;
|
|
||||||
use React\Http\Server;
|
|
||||||
use React\Socket\Server as SocketServer;
|
|
||||||
|
|
||||||
class Fileserver
|
|
||||||
{
|
|
||||||
/** @var SocketServer */
|
|
||||||
protected $socket;
|
|
||||||
|
|
||||||
public function __construct($rootFolder, $name, $port, $address, LoopInterface $loop)
|
|
||||||
{
|
|
||||||
$server = new Server($loop, function (ServerRequestInterface $request) use ($rootFolder, $name, $loop) {
|
|
||||||
return (new ConnectionHandler($rootFolder, $name, $loop))->handle($request);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->socket = new SocketServer("{$address}:{$port}", $loop);
|
|
||||||
|
|
||||||
$server->listen($this->socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSocket(): SocketServer
|
|
||||||
{
|
|
||||||
return $this->socket;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,8 @@ namespace App\Client\Http\Controllers;
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Logger\RequestLogger;
|
use App\Logger\RequestLogger;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -28,11 +28,11 @@ class AttachDataToLogController extends Controller
|
|||||||
|
|
||||||
$this->requestLogger->pushLoggedRequest($loggedRequest);
|
$this->requestLogger->pushLoggedRequest($loggedRequest);
|
||||||
|
|
||||||
$httpConnection->send(Message::toString(new Response(200)));
|
$httpConnection->send(str(new Response(200)));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$httpConnection->send(Message::toString(new Response(404)));
|
$httpConnection->send(str(new Response(404)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
namespace App\Client\Http\Controllers;
|
namespace App\Client\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ class CreateTunnelController extends Controller
|
|||||||
$httpConnection->send(respond_json($data));
|
$httpConnection->send(respond_json($data));
|
||||||
$httpConnection->close();
|
$httpConnection->close();
|
||||||
}, function () use ($httpConnection) {
|
}, function () use ($httpConnection) {
|
||||||
$httpConnection->send(Message::toString(new Response(500)));
|
$httpConnection->send(str(new Response(500)));
|
||||||
$httpConnection->close();
|
$httpConnection->close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ class DashboardController extends Controller
|
|||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$httpConnection->send(respond_html($this->getView($httpConnection, 'client.dashboard', [
|
$httpConnection->send(respond_html($this->getView($httpConnection, 'client.dashboard', [
|
||||||
'user' => Client::$user,
|
|
||||||
'subdomains' => Client::$subdomains,
|
'subdomains' => Client::$subdomains,
|
||||||
'max_logs'=> config()->get('expose.max_logged_requests', 10),
|
'max_logs'=> config()->get('expose.max_logged_requests', 10),
|
||||||
])));
|
])));
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ namespace App\Client\Http\Controllers;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\WebSockets\Socket;
|
use App\WebSockets\Socket;
|
||||||
use Exception;
|
use Exception;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -23,9 +23,9 @@ class PushLogsToDashboardController extends Controller
|
|||||||
$webSocketConnection->send($request->getContent());
|
$webSocketConnection->send($request->getContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
$httpConnection->send(Message::toString(new Response(200)));
|
$httpConnection->send(str(new Response(200)));
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$httpConnection->send(Message::toString(new Response(500, [], $e->getMessage())));
|
$httpConnection->send(str(new Response(500, [], $e->getMessage())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ namespace App\Client\Http\Controllers;
|
|||||||
use App\Client\Http\HttpClient;
|
use App\Client\Http\HttpClient;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Logger\RequestLogger;
|
use App\Logger\RequestLogger;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -29,15 +29,13 @@ class ReplayLogController extends Controller
|
|||||||
$loggedRequest = $this->requestLogger->findLoggedRequest($request->get('log'));
|
$loggedRequest = $this->requestLogger->findLoggedRequest($request->get('log'));
|
||||||
|
|
||||||
if (is_null($loggedRequest)) {
|
if (is_null($loggedRequest)) {
|
||||||
$httpConnection->send(Message::toString(new Response(404)));
|
$httpConnection->send(str(new Response(404)));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$loggedRequest->refreshId();
|
$this->httpClient->performRequest($loggedRequest->getRequestData());
|
||||||
|
|
||||||
$this->httpClient->performRequest($loggedRequest->getRequest()->toString());
|
$httpConnection->send(str(new Response(200)));
|
||||||
|
|
||||||
$httpConnection->send(Message::toString(new Response(200)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,15 +6,17 @@ use App\Client\Configuration;
|
|||||||
use App\Client\Http\Modifiers\CheckBasicAuthentication;
|
use App\Client\Http\Modifiers\CheckBasicAuthentication;
|
||||||
use App\Logger\RequestLogger;
|
use App\Logger\RequestLogger;
|
||||||
use Clue\React\Buzz\Browser;
|
use Clue\React\Buzz\Browser;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use function GuzzleHttp\Psr7\parse_request;
|
use function GuzzleHttp\Psr7\parse_request;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Laminas\Http\Request;
|
use Laminas\Http\Request;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\UriInterface;
|
||||||
use Ratchet\Client\WebSocket;
|
use Ratchet\Client\WebSocket;
|
||||||
use Ratchet\RFC6455\Messaging\Frame;
|
use Ratchet\RFC6455\Messaging\Frame;
|
||||||
use React\EventLoop\LoopInterface;
|
use React\EventLoop\LoopInterface;
|
||||||
use React\Socket\Connector;
|
use React\Socket\Connector;
|
||||||
|
use React\Stream\ReadableStreamInterface;
|
||||||
|
|
||||||
class HttpClient
|
class HttpClient
|
||||||
{
|
{
|
||||||
@@ -74,7 +76,6 @@ class HttpClient
|
|||||||
protected function createConnector(): Connector
|
protected function createConnector(): Connector
|
||||||
{
|
{
|
||||||
return new Connector($this->loop, [
|
return new Connector($this->loop, [
|
||||||
'dns' => config('expose.dns', '127.0.0.1'),
|
|
||||||
'tls' => [
|
'tls' => [
|
||||||
'verify_peer' => false,
|
'verify_peer' => false,
|
||||||
'verify_peer_name' => false,
|
'verify_peer_name' => false,
|
||||||
@@ -87,25 +88,18 @@ class HttpClient
|
|||||||
(new Browser($this->loop, $this->createConnector()))
|
(new Browser($this->loop, $this->createConnector()))
|
||||||
->withFollowRedirects(false)
|
->withFollowRedirects(false)
|
||||||
->withRejectErrorResponse(false)
|
->withRejectErrorResponse(false)
|
||||||
->requestStreaming(
|
->requestStreaming($request->getMethod(), $this->getExposeUri($request), $request->getHeaders(), $request->getBody())
|
||||||
$request->getMethod(),
|
|
||||||
$request->getUri(),
|
|
||||||
$request->getHeaders(),
|
|
||||||
$request->getBody()
|
|
||||||
)
|
|
||||||
->then(function (ResponseInterface $response) use ($proxyConnection) {
|
->then(function (ResponseInterface $response) use ($proxyConnection) {
|
||||||
if (! isset($response->buffer)) {
|
if (! isset($response->buffer)) {
|
||||||
$response = $this->rewriteResponseHeaders($response);
|
$response->buffer = str($response);
|
||||||
|
|
||||||
$response->buffer = Message::toString($response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->sendChunkToServer($response->buffer, $proxyConnection);
|
$this->sendChunkToServer($response->buffer, $proxyConnection);
|
||||||
|
|
||||||
/* @var $body \React\Stream\ReadableStreamInterface */
|
/* @var $body ReadableStreamInterface */
|
||||||
$body = $response->getBody();
|
$body = $response->getBody();
|
||||||
|
|
||||||
$this->logResponse(Message::toString($response));
|
$this->logResponse(str($response));
|
||||||
|
|
||||||
$body->on('data', function ($chunk) use ($proxyConnection, $response) {
|
$body->on('data', function ($chunk) use ($proxyConnection, $response) {
|
||||||
$response->buffer .= $chunk;
|
$response->buffer .= $chunk;
|
||||||
@@ -139,24 +133,14 @@ class HttpClient
|
|||||||
return Request::fromString($data);
|
return Request::fromString($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function rewriteResponseHeaders(ResponseInterface $response)
|
private function getExposeUri(RequestInterface $request): UriInterface
|
||||||
{
|
{
|
||||||
if (! $response->hasHeader('Location')) {
|
$exposeProto = $request->getHeader('x-expose-proto')[0];
|
||||||
return $response;
|
$exposeHost = explode(':', $request->getHeader('x-expose-host')[0]);
|
||||||
}
|
|
||||||
|
|
||||||
$location = $response->getHeaderLine('Location');
|
return $request->getUri()
|
||||||
|
->withScheme($exposeProto)
|
||||||
if (! strstr($location, $this->connectionData->host)) {
|
->withHost($exposeHost[0])
|
||||||
return $response;
|
->withPort($exposeHost[1]);
|
||||||
}
|
|
||||||
|
|
||||||
$location = str_replace(
|
|
||||||
$this->connectionData->host,
|
|
||||||
$this->configuration->getUrl($this->connectionData->subdomain),
|
|
||||||
$location
|
|
||||||
);
|
|
||||||
|
|
||||||
return $response->withHeader('Location', $location);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
namespace App\Client\Http\Modifiers;
|
namespace App\Client\Http\Modifiers;
|
||||||
|
|
||||||
use App\Client\Configuration;
|
use App\Client\Configuration;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Psr\Http\Message\RequestInterface;
|
use Psr\Http\Message\RequestInterface;
|
||||||
use Ratchet\Client\WebSocket;
|
use Ratchet\Client\WebSocket;
|
||||||
@@ -29,7 +29,7 @@ class CheckBasicAuthentication
|
|||||||
|
|
||||||
if (is_null($username)) {
|
if (is_null($username)) {
|
||||||
$proxyConnection->send(
|
$proxyConnection->send(
|
||||||
Message::toString(new Response(401, [
|
str(new Response(401, [
|
||||||
'WWW-Authenticate' => 'Basic realm=Expose',
|
'WWW-Authenticate' => 'Basic realm=Expose',
|
||||||
], 'Unauthorized'))
|
], 'Unauthorized'))
|
||||||
);
|
);
|
||||||
@@ -89,7 +89,7 @@ class CheckBasicAuthentication
|
|||||||
protected function getCredentials()
|
protected function getCredentials()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$credentials = explode(':', $this->configuration->basicAuth());
|
$credentials = explode(':', $this->configuration->auth());
|
||||||
|
|
||||||
return [
|
return [
|
||||||
$credentials[0] => $credentials[1],
|
$credentials[0] => $credentials[1],
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Support;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\Node\Expr\ConstFetch;
|
|
||||||
use PhpParser\Node\Name;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
|
||||||
|
|
||||||
class ClearDomainNodeVisitor extends NodeVisitorAbstract
|
|
||||||
{
|
|
||||||
public function enterNode(Node $node)
|
|
||||||
{
|
|
||||||
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'default_domain') {
|
|
||||||
$node->value = new ConstFetch(
|
|
||||||
new Name('null')
|
|
||||||
);
|
|
||||||
|
|
||||||
return $node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Support;
|
|
||||||
|
|
||||||
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
|
|
||||||
use Symfony\Component\Console\Helper\Helper;
|
|
||||||
use Symfony\Component\Console\Output\StreamOutput;
|
|
||||||
use Symfony\Component\Console\Terminal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Pierre du Plessis <pdples@gmail.com>
|
|
||||||
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
|
|
||||||
*/
|
|
||||||
class ConsoleSectionOutput extends StreamOutput
|
|
||||||
{
|
|
||||||
private $content = [];
|
|
||||||
private $lines = 0;
|
|
||||||
private $sections;
|
|
||||||
private $terminal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param resource $stream
|
|
||||||
* @param \Symfony\Component\Console\Output\ConsoleSectionOutput[] $sections
|
|
||||||
*/
|
|
||||||
public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
|
|
||||||
{
|
|
||||||
parent::__construct($stream, $verbosity, $decorated, $formatter);
|
|
||||||
array_unshift($sections, $this);
|
|
||||||
$this->sections = &$sections;
|
|
||||||
$this->terminal = new Terminal();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears previous output for this section.
|
|
||||||
*
|
|
||||||
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
|
|
||||||
*/
|
|
||||||
public function clear(int $lines = null)
|
|
||||||
{
|
|
||||||
if (empty($this->content) || ! $this->isDecorated()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($lines) {
|
|
||||||
array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
|
|
||||||
} else {
|
|
||||||
$lines = $this->lines;
|
|
||||||
$this->content = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->lines -= $lines;
|
|
||||||
|
|
||||||
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the previous output with a new message.
|
|
||||||
*
|
|
||||||
* @param array|string $message
|
|
||||||
*/
|
|
||||||
public function overwrite($message)
|
|
||||||
{
|
|
||||||
$this->clear();
|
|
||||||
$this->writeln($message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getContent(): string
|
|
||||||
{
|
|
||||||
return implode('', $this->content);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
public function addContent(string $input)
|
|
||||||
{
|
|
||||||
foreach (explode(\PHP_EOL, $input) as $lineContent) {
|
|
||||||
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
|
|
||||||
$this->content[] = $lineContent;
|
|
||||||
$this->content[] = \PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritdoc}
|
|
||||||
*/
|
|
||||||
protected function doWrite(string $message, bool $newline)
|
|
||||||
{
|
|
||||||
if (! $this->isDecorated()) {
|
|
||||||
parent::doWrite($message, $newline);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$erasedContent = $this->popStreamContentUntilCurrentSection();
|
|
||||||
|
|
||||||
$this->addContent($message);
|
|
||||||
|
|
||||||
parent::doWrite($message, true);
|
|
||||||
parent::doWrite($erasedContent, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
|
|
||||||
* current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
|
|
||||||
*/
|
|
||||||
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
|
|
||||||
{
|
|
||||||
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
|
|
||||||
$erasedContent = [];
|
|
||||||
|
|
||||||
foreach ($this->sections as $section) {
|
|
||||||
if ($section === $this) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$numberOfLinesToClear += $section->lines;
|
|
||||||
$erasedContent[] = $section->getContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($numberOfLinesToClear > 0) {
|
|
||||||
// move cursor up n lines
|
|
||||||
parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
|
|
||||||
// erase to end of screen
|
|
||||||
parent::doWrite("\x1b[0J", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return implode('', array_reverse($erasedContent));
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getDisplayLength(string $text): int
|
|
||||||
{
|
|
||||||
$cleanedText = Helper::removeDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
|
|
||||||
$cleanedText = preg_replace('/]8;;(.*)]8;;/m', '', $cleanedText);
|
|
||||||
|
|
||||||
return Helper::width($cleanedText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Support;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\Node\Scalar\String_;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
|
||||||
|
|
||||||
class DefaultDomainNodeVisitor extends NodeVisitorAbstract
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
protected $domain;
|
|
||||||
|
|
||||||
public function __construct(string $domain)
|
|
||||||
{
|
|
||||||
$this->domain = $domain;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function leaveNode(Node $node)
|
|
||||||
{
|
|
||||||
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'default_domain') {
|
|
||||||
$node->value = new String_($this->domain);
|
|
||||||
|
|
||||||
return $node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Support;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\Node\Scalar\String_;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
|
||||||
|
|
||||||
class DefaultServerNodeVisitor extends NodeVisitorAbstract
|
|
||||||
{
|
|
||||||
/** @var string */
|
|
||||||
protected $server;
|
|
||||||
|
|
||||||
public function __construct(string $server)
|
|
||||||
{
|
|
||||||
$this->server = $server;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function enterNode(Node $node)
|
|
||||||
{
|
|
||||||
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'default_server') {
|
|
||||||
$node->value = new String_($this->server);
|
|
||||||
|
|
||||||
return $node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Support;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\Node\Expr\ConstFetch;
|
|
||||||
use PhpParser\Node\Name;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
|
||||||
|
|
||||||
class InsertDefaultDomainNodeVisitor extends NodeVisitorAbstract
|
|
||||||
{
|
|
||||||
public function leaveNode(Node $node)
|
|
||||||
{
|
|
||||||
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'auth_token') {
|
|
||||||
$defaultDomainNode = new Node\Expr\ArrayItem(
|
|
||||||
new ConstFetch(
|
|
||||||
new Name('null')
|
|
||||||
),
|
|
||||||
new Node\Scalar\String_('default_domain')
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$node,
|
|
||||||
$defaultDomainNode,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Client\Support;
|
|
||||||
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\Node\Expr\ConstFetch;
|
|
||||||
use PhpParser\Node\Name;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
|
||||||
|
|
||||||
class InsertDefaultServerNodeVisitor extends NodeVisitorAbstract
|
|
||||||
{
|
|
||||||
public function leaveNode(Node $node)
|
|
||||||
{
|
|
||||||
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'auth_token') {
|
|
||||||
$defaultServerNode = new Node\Expr\ArrayItem(
|
|
||||||
new ConstFetch(
|
|
||||||
new Name('null')
|
|
||||||
),
|
|
||||||
new Node\Scalar\String_('default_server')
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
|
||||||
$node,
|
|
||||||
$defaultServerNode,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Client\Support;
|
namespace App\Client\Support;
|
||||||
|
|
||||||
use PhpParser\Node;
|
use PhpParser\Node;
|
||||||
use PhpParser\Node\Scalar\String_;
|
|
||||||
use PhpParser\NodeVisitorAbstract;
|
use PhpParser\NodeVisitorAbstract;
|
||||||
|
|
||||||
class TokenNodeVisitor extends NodeVisitorAbstract
|
class TokenNodeVisitor extends NodeVisitorAbstract
|
||||||
@@ -19,7 +18,7 @@ class TokenNodeVisitor extends NodeVisitorAbstract
|
|||||||
public function enterNode(Node $node)
|
public function enterNode(Node $node)
|
||||||
{
|
{
|
||||||
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'auth_token') {
|
if ($node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'auth_token') {
|
||||||
$node->value = new String_($this->token);
|
$node->value->value = $this->token;
|
||||||
|
|
||||||
return $node;
|
return $node;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Commands;
|
|
||||||
|
|
||||||
use App\Client\Support\ClearDomainNodeVisitor;
|
|
||||||
use App\Client\Support\InsertDefaultDomainNodeVisitor;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\NodeFinder;
|
|
||||||
use PhpParser\NodeTraverser;
|
|
||||||
use PhpParser\NodeVisitor\CloningVisitor;
|
|
||||||
use PhpParser\Parser\Php7;
|
|
||||||
use PhpParser\PrettyPrinter\Standard;
|
|
||||||
|
|
||||||
class ClearDefaultDomainCommand extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'default-domain:clear';
|
|
||||||
|
|
||||||
protected $description = 'Clear the default domain to use with Expose.';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$this->info('Clearing the default Expose domain.');
|
|
||||||
|
|
||||||
$configFile = implode(DIRECTORY_SEPARATOR, [
|
|
||||||
$_SERVER['HOME'] ?? $_SERVER['USERPROFILE'],
|
|
||||||
'.expose',
|
|
||||||
'config.php',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (! file_exists($configFile)) {
|
|
||||||
@mkdir(dirname($configFile), 0777, true);
|
|
||||||
$updatedConfigFile = $this->modifyConfigurationFile(base_path('config/expose.php'));
|
|
||||||
} else {
|
|
||||||
$updatedConfigFile = $this->modifyConfigurationFile($configFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($configFile, $updatedConfigFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function modifyConfigurationFile(string $configFile)
|
|
||||||
{
|
|
||||||
$lexer = new Emulative([
|
|
||||||
'usedAttributes' => [
|
|
||||||
'comments',
|
|
||||||
'startLine', 'endLine',
|
|
||||||
'startTokenPos', 'endTokenPos',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$parser = new Php7($lexer);
|
|
||||||
|
|
||||||
$oldStmts = $parser->parse(file_get_contents($configFile));
|
|
||||||
$oldTokens = $lexer->getTokens();
|
|
||||||
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new CloningVisitor());
|
|
||||||
$newStmts = $nodeTraverser->traverse($oldStmts);
|
|
||||||
|
|
||||||
$nodeFinder = new NodeFinder;
|
|
||||||
|
|
||||||
$defaultDomainNode = $nodeFinder->findFirst($newStmts, function (Node $node) {
|
|
||||||
return $node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'default_domain';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is_null($defaultDomainNode)) {
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new InsertDefaultDomainNodeVisitor());
|
|
||||||
$newStmts = $nodeTraverser->traverse($newStmts);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new ClearDomainNodeVisitor());
|
|
||||||
|
|
||||||
$newStmts = $nodeTraverser->traverse($newStmts);
|
|
||||||
|
|
||||||
$prettyPrinter = new Standard();
|
|
||||||
|
|
||||||
return $prettyPrinter->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,35 +3,20 @@
|
|||||||
namespace App\Commands;
|
namespace App\Commands;
|
||||||
|
|
||||||
use App\Server\Factory;
|
use App\Server\Factory;
|
||||||
use InvalidArgumentException;
|
|
||||||
use LaravelZero\Framework\Commands\Command;
|
use LaravelZero\Framework\Commands\Command;
|
||||||
use React\EventLoop\LoopInterface;
|
use React\EventLoop\LoopInterface;
|
||||||
|
|
||||||
class ServeCommand extends Command
|
class ServeCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'serve {hostname=localhost} {host=0.0.0.0} {--validateAuthTokens} {--port=8080} {--config=}';
|
protected $signature = 'serve {hostname=localhost} {host=0.0.0.0} {--validateAuthTokens} {--port=8080}';
|
||||||
|
|
||||||
protected $description = 'Start the expose server';
|
protected $description = 'Start the expose server';
|
||||||
|
|
||||||
protected function loadConfiguration(string $configFile)
|
|
||||||
{
|
|
||||||
$configFile = realpath($configFile);
|
|
||||||
|
|
||||||
throw_if(! file_exists($configFile), new InvalidArgumentException("Invalid config file {$configFile}"));
|
|
||||||
|
|
||||||
$localConfig = require $configFile;
|
|
||||||
config()->set('expose', $localConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
/** @var LoopInterface $loop */
|
/** @var LoopInterface $loop */
|
||||||
$loop = app(LoopInterface::class);
|
$loop = app(LoopInterface::class);
|
||||||
|
|
||||||
if ($this->option('config')) {
|
|
||||||
$this->loadConfiguration($this->option('config'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$loop->futureTick(function () {
|
$loop->futureTick(function () {
|
||||||
$this->info('Expose server running on port '.$this->option('port').'.');
|
$this->info('Expose server running on port '.$this->option('port').'.');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Commands;
|
|
||||||
|
|
||||||
use App\Client\Exceptions\InvalidServerProvided;
|
|
||||||
use App\Logger\CliRequestLogger;
|
|
||||||
use Illuminate\Console\Parser;
|
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use LaravelZero\Framework\Commands\Command;
|
|
||||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
|
||||||
|
|
||||||
abstract class ServerAwareCommand extends Command
|
|
||||||
{
|
|
||||||
const DEFAULT_HOSTNAME = 'bitinflow.dev';
|
|
||||||
const DEFAULT_PORT = 443;
|
|
||||||
const DEFAULT_SERVER_ENDPOINT = 'https://expose.dev/api/servers';
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
parent::__construct();
|
|
||||||
|
|
||||||
$inheritedSignature = '{--server=} {--server-host=} {--server-port=}';
|
|
||||||
|
|
||||||
$this->getDefinition()->addOptions(Parser::parse($inheritedSignature)[2]);
|
|
||||||
|
|
||||||
$this->configureConnectionLogger();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function configureConnectionLogger()
|
|
||||||
{
|
|
||||||
app()->singleton(CliRequestLogger::class, function () {
|
|
||||||
return new CliRequestLogger(new ConsoleOutput());
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getServerHost()
|
|
||||||
{
|
|
||||||
if ($this->option('server-host')) {
|
|
||||||
return $this->option('server-host');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to find the server in the servers array.
|
|
||||||
* If no array exists at all (when upgrading from v1),
|
|
||||||
* always return bitinflow.dev.
|
|
||||||
*/
|
|
||||||
if (config('expose.servers') === null) {
|
|
||||||
return static::DEFAULT_HOSTNAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
$server = $this->option('server') ?? config('expose.default_server');
|
|
||||||
$host = config('expose.servers.'.$server.'.host');
|
|
||||||
|
|
||||||
if (! is_null($host)) {
|
|
||||||
return $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->lookupRemoteServerHost($server);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getServerPort()
|
|
||||||
{
|
|
||||||
if ($this->option('server-port')) {
|
|
||||||
return $this->option('server-port');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to find the server in the servers array.
|
|
||||||
* If no array exists at all (when upgrading from v1),
|
|
||||||
* always return bitinflow.dev.
|
|
||||||
*/
|
|
||||||
if (config('expose.servers') === null) {
|
|
||||||
return static::DEFAULT_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
$server = $this->option('server') ?? config('expose.default_server');
|
|
||||||
$host = config('expose.servers.'.$server.'.port');
|
|
||||||
|
|
||||||
if (! is_null($host)) {
|
|
||||||
return $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->lookupRemoteServerPort($server);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function lookupRemoteServers()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return Http::withOptions([
|
|
||||||
'verify' => false,
|
|
||||||
])->get(config('expose.server_endpoint', static::DEFAULT_SERVER_ENDPOINT))->json();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function lookupRemoteServerHost($server)
|
|
||||||
{
|
|
||||||
$servers = $this->lookupRemoteServers();
|
|
||||||
$host = Arr::get($servers, $server.'.host');
|
|
||||||
|
|
||||||
throw_if(is_null($host), new InvalidServerProvided($server));
|
|
||||||
|
|
||||||
return $host;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function lookupRemoteServerPort($server)
|
|
||||||
{
|
|
||||||
$servers = $this->lookupRemoteServers();
|
|
||||||
$port = Arr::get($servers, $server.'.port');
|
|
||||||
|
|
||||||
throw_if(is_null($port), new InvalidServerProvided($server));
|
|
||||||
|
|
||||||
return $port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Commands;
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use LaravelZero\Framework\Commands\Command;
|
|
||||||
|
|
||||||
class ServerListCommand extends Command
|
|
||||||
{
|
|
||||||
const DEFAULT_SERVER_ENDPOINT = 'https://expose.dev/api/servers';
|
|
||||||
|
|
||||||
protected $signature = 'servers';
|
|
||||||
|
|
||||||
protected $description = 'Set or retrieve the default server to use with Expose.';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$servers = collect($this->lookupRemoteServers())->map(function ($server) {
|
|
||||||
return [
|
|
||||||
'key' => $server['key'],
|
|
||||||
'region' => $server['region'],
|
|
||||||
'plan' => Str::ucfirst($server['plan']),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->info('You can connect to a specific server with the --server=key option or set this server as default with the default-server command.');
|
|
||||||
$this->info('');
|
|
||||||
|
|
||||||
$this->table(['Key', 'Region', 'Type'], $servers);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function lookupRemoteServers()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return Http::withOptions([
|
|
||||||
'verify' => false,
|
|
||||||
])->get(config('expose.server_endpoint', static::DEFAULT_SERVER_ENDPOINT))->json();
|
|
||||||
} catch (\Throwable $e) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Commands;
|
|
||||||
|
|
||||||
use App\Client\Support\DefaultDomainNodeVisitor;
|
|
||||||
use App\Client\Support\DefaultServerNodeVisitor;
|
|
||||||
use App\Client\Support\InsertDefaultDomainNodeVisitor;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\NodeFinder;
|
|
||||||
use PhpParser\NodeTraverser;
|
|
||||||
use PhpParser\NodeVisitor\CloningVisitor;
|
|
||||||
use PhpParser\Parser\Php7;
|
|
||||||
use PhpParser\PrettyPrinter\Standard;
|
|
||||||
|
|
||||||
class SetDefaultDomainCommand extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'default-domain {domain?} {--server=}';
|
|
||||||
|
|
||||||
protected $description = 'Set or retrieve the default domain to use with Expose.';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$domain = $this->argument('domain');
|
|
||||||
$server = $this->option('server');
|
|
||||||
if (! is_null($domain)) {
|
|
||||||
$this->info('Setting the Expose default domain to "'.$domain.'"');
|
|
||||||
|
|
||||||
$configFile = implode(DIRECTORY_SEPARATOR, [
|
|
||||||
$_SERVER['HOME'] ?? $_SERVER['USERPROFILE'],
|
|
||||||
'.expose',
|
|
||||||
'config.php',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (! file_exists($configFile)) {
|
|
||||||
@mkdir(dirname($configFile), 0777, true);
|
|
||||||
$updatedConfigFile = $this->modifyConfigurationFile(base_path('config/expose.php'), $domain, $server);
|
|
||||||
} else {
|
|
||||||
$updatedConfigFile = $this->modifyConfigurationFile($configFile, $domain, $server);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($configFile, $updatedConfigFile);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($domain = config('expose.default_domain'))) {
|
|
||||||
$this->info('There is no default domain specified.');
|
|
||||||
} else {
|
|
||||||
$this->info('Current default domain: '.$domain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function modifyConfigurationFile(string $configFile, string $domain, ?string $server)
|
|
||||||
{
|
|
||||||
$lexer = new Emulative([
|
|
||||||
'usedAttributes' => [
|
|
||||||
'comments',
|
|
||||||
'startLine', 'endLine',
|
|
||||||
'startTokenPos', 'endTokenPos',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$parser = new Php7($lexer);
|
|
||||||
|
|
||||||
$oldStmts = $parser->parse(file_get_contents($configFile));
|
|
||||||
$oldTokens = $lexer->getTokens();
|
|
||||||
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new CloningVisitor());
|
|
||||||
$newStmts = $nodeTraverser->traverse($oldStmts);
|
|
||||||
|
|
||||||
$nodeFinder = new NodeFinder;
|
|
||||||
|
|
||||||
$defaultDomainNode = $nodeFinder->findFirst($newStmts, function (Node $node) {
|
|
||||||
return $node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'default_domain';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is_null($defaultDomainNode)) {
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new InsertDefaultDomainNodeVisitor());
|
|
||||||
$newStmts = $nodeTraverser->traverse($newStmts);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new DefaultDomainNodeVisitor($domain));
|
|
||||||
|
|
||||||
if (! is_null($server)) {
|
|
||||||
$nodeTraverser->addVisitor(new DefaultServerNodeVisitor($server));
|
|
||||||
}
|
|
||||||
|
|
||||||
$newStmts = $nodeTraverser->traverse($newStmts);
|
|
||||||
|
|
||||||
$prettyPrinter = new Standard();
|
|
||||||
|
|
||||||
return $prettyPrinter->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Commands;
|
|
||||||
|
|
||||||
use App\Client\Support\DefaultServerNodeVisitor;
|
|
||||||
use App\Client\Support\InsertDefaultServerNodeVisitor;
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use PhpParser\Lexer\Emulative;
|
|
||||||
use PhpParser\Node;
|
|
||||||
use PhpParser\NodeFinder;
|
|
||||||
use PhpParser\NodeTraverser;
|
|
||||||
use PhpParser\NodeVisitor\CloningVisitor;
|
|
||||||
use PhpParser\Parser\Php7;
|
|
||||||
use PhpParser\PrettyPrinter\Standard;
|
|
||||||
|
|
||||||
class SetDefaultServerCommand extends Command
|
|
||||||
{
|
|
||||||
protected $signature = 'default-server {server?}';
|
|
||||||
|
|
||||||
protected $description = 'Set or retrieve the default server to use with Expose.';
|
|
||||||
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$server = $this->argument('server');
|
|
||||||
if (! is_null($server)) {
|
|
||||||
$this->info('Setting the Expose default server to "'.$server.'"');
|
|
||||||
|
|
||||||
$configFile = implode(DIRECTORY_SEPARATOR, [
|
|
||||||
$_SERVER['HOME'] ?? $_SERVER['USERPROFILE'],
|
|
||||||
'.expose',
|
|
||||||
'config.php',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (! file_exists($configFile)) {
|
|
||||||
@mkdir(dirname($configFile), 0777, true);
|
|
||||||
$updatedConfigFile = $this->modifyConfigurationFile(base_path('config/expose.php'), $server);
|
|
||||||
} else {
|
|
||||||
$updatedConfigFile = $this->modifyConfigurationFile($configFile, $server);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($configFile, $updatedConfigFile);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_null($server = config('expose.default_server'))) {
|
|
||||||
$this->info('There is no default server specified.');
|
|
||||||
} else {
|
|
||||||
$this->info('Current default server: '.$server);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function modifyConfigurationFile(string $configFile, string $server)
|
|
||||||
{
|
|
||||||
$lexer = new Emulative([
|
|
||||||
'usedAttributes' => [
|
|
||||||
'comments',
|
|
||||||
'startLine', 'endLine',
|
|
||||||
'startTokenPos', 'endTokenPos',
|
|
||||||
],
|
|
||||||
]);
|
|
||||||
$parser = new Php7($lexer);
|
|
||||||
|
|
||||||
$oldStmts = $parser->parse(file_get_contents($configFile));
|
|
||||||
$oldTokens = $lexer->getTokens();
|
|
||||||
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new CloningVisitor());
|
|
||||||
$newStmts = $nodeTraverser->traverse($oldStmts);
|
|
||||||
|
|
||||||
$nodeFinder = new NodeFinder;
|
|
||||||
|
|
||||||
$defaultServerNode = $nodeFinder->findFirst($newStmts, function (Node $node) {
|
|
||||||
return $node instanceof Node\Expr\ArrayItem && $node->key && $node->key->value === 'default_server';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is_null($defaultServerNode)) {
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new InsertDefaultServerNodeVisitor());
|
|
||||||
$newStmts = $nodeTraverser->traverse($newStmts);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nodeTraverser = new NodeTraverser;
|
|
||||||
$nodeTraverser->addVisitor(new DefaultServerNodeVisitor($server));
|
|
||||||
|
|
||||||
$newStmts = $nodeTraverser->traverse($newStmts);
|
|
||||||
|
|
||||||
$prettyPrinter = new Standard();
|
|
||||||
|
|
||||||
return $prettyPrinter->printFormatPreserving($newStmts, $oldStmts, $oldTokens);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,63 +3,41 @@
|
|||||||
namespace App\Commands;
|
namespace App\Commands;
|
||||||
|
|
||||||
use App\Client\Factory;
|
use App\Client\Factory;
|
||||||
use Illuminate\Support\Str;
|
use App\Logger\CliRequestLogger;
|
||||||
|
use LaravelZero\Framework\Commands\Command;
|
||||||
use React\EventLoop\LoopInterface;
|
use React\EventLoop\LoopInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
|
|
||||||
class ShareCommand extends ServerAwareCommand
|
class ShareCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'share {host} {--subdomain=} {--auth=} {--basicAuth=} {--dns=} {--domain=}';
|
protected $signature = 'share {host} {--subdomain=} {--auth=} {--server-host=} {--server-port=}';
|
||||||
|
|
||||||
protected $description = 'Share a local url with a remote expose server';
|
protected $description = 'Share a local url with a remote expose server';
|
||||||
|
|
||||||
|
protected function configureConnectionLogger()
|
||||||
|
{
|
||||||
|
app()->bind(CliRequestLogger::class, function () {
|
||||||
|
return new CliRequestLogger(new ConsoleOutput());
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
$this->configureConnectionLogger();
|
||||||
|
|
||||||
|
$serverHost = $this->option('server-host') ?? config('expose.host', 'localhost');
|
||||||
|
$serverPort = $this->option('server-port') ?? config('expose.port', 8080);
|
||||||
$auth = $this->option('auth') ?? config('expose.auth_token', '');
|
$auth = $this->option('auth') ?? config('expose.auth_token', '');
|
||||||
$this->info('Using auth token: '.$auth, OutputInterface::VERBOSITY_DEBUG);
|
|
||||||
|
|
||||||
if (strstr($this->argument('host'), 'host.docker.internal')) {
|
|
||||||
config(['expose.dns' => true]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->option('dns') !== null) {
|
|
||||||
config(['expose.dns' => empty($this->option('dns')) ? true : $this->option('dns')]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$domain = config('expose.default_domain');
|
|
||||||
|
|
||||||
if (! is_null($this->option('server'))) {
|
|
||||||
$domain = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_null($this->option('domain'))) {
|
|
||||||
$domain = $this->option('domain');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_null($this->option('subdomain'))) {
|
|
||||||
$subdomains = explode(',', $this->option('subdomain'));
|
|
||||||
$this->info('Trying to use custom domain: '.$subdomains[0].PHP_EOL, OutputInterface::VERBOSITY_VERBOSE);
|
|
||||||
} else {
|
|
||||||
$host = Str::beforeLast($this->argument('host'), '.');
|
|
||||||
$host = str_replace('https://', '', $host);
|
|
||||||
$host = str_replace('http://', '', $host);
|
|
||||||
$host = Str::beforeLast($host, ':');
|
|
||||||
$subdomains = [Str::slug($host)];
|
|
||||||
$this->info('Trying to use custom domain: '.$subdomains[0].PHP_EOL, OutputInterface::VERBOSITY_VERBOSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
(new Factory())
|
(new Factory())
|
||||||
->setLoop(app(LoopInterface::class))
|
->setLoop(app(LoopInterface::class))
|
||||||
->setHost($this->getServerHost())
|
->setHost($serverHost)
|
||||||
->setPort($this->getServerPort())
|
->setPort($serverPort)
|
||||||
->setAuth($auth)
|
->setAuth($auth)
|
||||||
->setBasicAuth($this->option('basicAuth'))
|
|
||||||
->createClient()
|
->createClient()
|
||||||
->share(
|
->share($this->argument('host'), explode(',', $this->option('subdomain')))
|
||||||
$this->argument('host'),
|
|
||||||
$subdomains,
|
|
||||||
$domain
|
|
||||||
)
|
|
||||||
->createHttpServer()
|
->createHttpServer()
|
||||||
->run();
|
->run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,17 +4,17 @@ namespace App\Commands;
|
|||||||
|
|
||||||
class ShareCurrentWorkingDirectoryCommand extends ShareCommand
|
class ShareCurrentWorkingDirectoryCommand extends ShareCommand
|
||||||
{
|
{
|
||||||
protected $signature = 'share-cwd {host?} {--subdomain=} {--auth=} {--basicAuth=} {--dns=} {--domain=}';
|
protected $signature = 'share-cwd {host?} {--subdomain=} {--auth=} {--server-host=} {--server-port=}';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$folderName = $this->detectName();
|
$subdomain = $this->detectName();
|
||||||
$host = $this->prepareSharedHost($folderName.'.'.$this->detectTld());
|
$host = $this->prepareSharedHost($subdomain.'.'.$this->detectTld());
|
||||||
|
|
||||||
$this->input->setArgument('host', $host);
|
$this->input->setArgument('host', $host);
|
||||||
|
|
||||||
if (! $this->option('subdomain')) {
|
if (! $this->option('subdomain')) {
|
||||||
$this->input->setOption('subdomain', str_replace('.', '-', $folderName));
|
$this->input->setOption('subdomain', $subdomain);
|
||||||
}
|
}
|
||||||
|
|
||||||
parent::handle();
|
parent::handle();
|
||||||
@@ -56,22 +56,17 @@ class ShareCurrentWorkingDirectoryCommand extends ShareCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return basename($projectPath);
|
return str_replace('.', '-', basename($projectPath));
|
||||||
}
|
|
||||||
|
|
||||||
protected function detectProtocol($host): string
|
|
||||||
{
|
|
||||||
$certificateFile = ($_SERVER['HOME'] ?? $_SERVER['USERPROFILE']).DIRECTORY_SEPARATOR.'.config'.DIRECTORY_SEPARATOR.'valet'.DIRECTORY_SEPARATOR.'Certificates'.DIRECTORY_SEPARATOR.$host.'.crt';
|
|
||||||
|
|
||||||
if (file_exists($certificateFile)) {
|
|
||||||
return 'https://';
|
|
||||||
}
|
|
||||||
|
|
||||||
return config('expose.default_https', false) ? 'https://' : 'http://';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prepareSharedHost($host): string
|
protected function prepareSharedHost($host): string
|
||||||
{
|
{
|
||||||
return $this->detectProtocol($host).$host;
|
$certificateFile = ($_SERVER['HOME'] ?? $_SERVER['USERPROFILE']).DIRECTORY_SEPARATOR.'.config'.DIRECTORY_SEPARATOR.'valet'.DIRECTORY_SEPARATOR.'Certificates'.DIRECTORY_SEPARATOR.$host.'.crt';
|
||||||
|
|
||||||
|
if (file_exists($certificateFile)) {
|
||||||
|
return 'https://'.$host;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $host;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,23 +3,35 @@
|
|||||||
namespace App\Commands;
|
namespace App\Commands;
|
||||||
|
|
||||||
use App\Client\Factory;
|
use App\Client\Factory;
|
||||||
|
use App\Logger\CliRequestLogger;
|
||||||
|
use LaravelZero\Framework\Commands\Command;
|
||||||
use React\EventLoop\LoopInterface;
|
use React\EventLoop\LoopInterface;
|
||||||
|
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||||
|
|
||||||
class SharePortCommand extends ServerAwareCommand
|
class SharePortCommand extends Command
|
||||||
{
|
{
|
||||||
protected $signature = 'share-port {port} {--auth=}';
|
protected $signature = 'share-port {port} {--auth=}';
|
||||||
|
|
||||||
protected $description = 'Share a local port with a remote expose server';
|
protected $description = 'Share a local port with a remote expose server';
|
||||||
|
|
||||||
|
protected function configureConnectionLogger()
|
||||||
|
{
|
||||||
|
app()->bind(CliRequestLogger::class, function () {
|
||||||
|
return new CliRequestLogger(new ConsoleOutput());
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$auth = $this->option('auth') ?? config('expose.auth_token', '');
|
$this->configureConnectionLogger();
|
||||||
|
|
||||||
(new Factory())
|
(new Factory())
|
||||||
->setLoop(app(LoopInterface::class))
|
->setLoop(app(LoopInterface::class))
|
||||||
->setHost($this->getServerHost())
|
->setHost(config('expose.host', 'localhost'))
|
||||||
->setPort($this->getServerPort())
|
->setPort(config('expose.port', 8080))
|
||||||
->setAuth($auth)
|
->setAuth($this->option('auth'))
|
||||||
->createClient()
|
->createClient()
|
||||||
->sharePort($this->argument('port'))
|
->sharePort($this->argument('port'))
|
||||||
->createHttpServer()
|
->createHttpServer()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class StoreAuthenticationTokenCommand extends Command
|
|||||||
{
|
{
|
||||||
protected $signature = 'token {token?}';
|
protected $signature = 'token {token?}';
|
||||||
|
|
||||||
protected $description = 'Set or retrieve the authentication token to use with Expose.';
|
protected $description = 'Set or retrieve the authentication token to use with expose.';
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Ratchet\ConnectionInterface;
|
|||||||
|
|
||||||
interface ConnectionManager
|
interface ConnectionManager
|
||||||
{
|
{
|
||||||
public function storeConnection(string $host, ?string $subdomain, ?string $serverHost, ConnectionInterface $connection): ControlConnection;
|
public function storeConnection(string $host, ?string $subdomain, ConnectionInterface $connection): ControlConnection;
|
||||||
|
|
||||||
public function storeTcpConnection(int $port, ConnectionInterface $connection): ControlConnection;
|
public function storeTcpConnection(int $port, ConnectionInterface $connection): ControlConnection;
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ interface ConnectionManager
|
|||||||
|
|
||||||
public function removeControlConnection($connection);
|
public function removeControlConnection($connection);
|
||||||
|
|
||||||
public function findControlConnectionForSubdomainAndServerHost($subdomain, $serverHost): ?ControlConnection;
|
public function findControlConnectionForSubdomain($subdomain): ?ControlConnection;
|
||||||
|
|
||||||
public function findControlConnectionForClientId(string $clientId): ?ControlConnection;
|
public function findControlConnectionForClientId(string $clientId): ?ControlConnection;
|
||||||
|
|
||||||
@@ -29,8 +29,4 @@ interface ConnectionManager
|
|||||||
public function getConnectionsForAuthToken(string $authToken): array;
|
public function getConnectionsForAuthToken(string $authToken): array;
|
||||||
|
|
||||||
public function getTcpConnectionsForAuthToken(string $authToken): array;
|
public function getTcpConnectionsForAuthToken(string $authToken): array;
|
||||||
|
|
||||||
public function findControlConnectionsForIp(string $ip): array;
|
|
||||||
|
|
||||||
public function findControlConnectionsForAuthToken(string $token): array;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Contracts;
|
|
||||||
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
interface DomainRepository
|
|
||||||
{
|
|
||||||
public function getDomains(): PromiseInterface;
|
|
||||||
|
|
||||||
public function getDomainById($id): PromiseInterface;
|
|
||||||
|
|
||||||
public function getDomainByName(string $name): PromiseInterface;
|
|
||||||
|
|
||||||
public function getDomainsByUserId($id): PromiseInterface;
|
|
||||||
|
|
||||||
public function getDomainsByUserIdAndName($id, $name): PromiseInterface;
|
|
||||||
|
|
||||||
public function deleteDomainForUserId($userId, $domainId): PromiseInterface;
|
|
||||||
|
|
||||||
public function storeDomain(array $data): PromiseInterface;
|
|
||||||
|
|
||||||
public function updateDomain($id, array $data): PromiseInterface;
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Contracts;
|
|
||||||
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
interface LoggerRepository
|
|
||||||
{
|
|
||||||
public function logSubdomain($authToken, $subdomain);
|
|
||||||
|
|
||||||
public function getLogs(): PromiseInterface;
|
|
||||||
|
|
||||||
public function getLogsBySubdomain($subdomain): PromiseInterface;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Contracts;
|
|
||||||
|
|
||||||
interface StatisticsCollector
|
|
||||||
{
|
|
||||||
public function siteShared($authToken = null);
|
|
||||||
|
|
||||||
public function portShared($authToken = null);
|
|
||||||
|
|
||||||
public function incomingRequest();
|
|
||||||
|
|
||||||
public function flush();
|
|
||||||
|
|
||||||
public function save();
|
|
||||||
|
|
||||||
public function shouldCollectStatistics(): bool;
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Contracts;
|
|
||||||
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
interface StatisticsRepository
|
|
||||||
{
|
|
||||||
public function getStatistics($from, $until): PromiseInterface;
|
|
||||||
}
|
|
||||||
@@ -12,10 +12,6 @@ interface SubdomainRepository
|
|||||||
|
|
||||||
public function getSubdomainByName(string $name): PromiseInterface;
|
public function getSubdomainByName(string $name): PromiseInterface;
|
||||||
|
|
||||||
public function getSubdomainByNameAndDomain(string $name, string $domain): PromiseInterface;
|
|
||||||
|
|
||||||
public function getSubdomainsByNameAndDomain(string $name, string $domain): PromiseInterface;
|
|
||||||
|
|
||||||
public function getSubdomainsByUserId($id): PromiseInterface;
|
public function getSubdomainsByUserId($id): PromiseInterface;
|
||||||
|
|
||||||
public function getSubdomainsByUserIdAndName($id, $name): PromiseInterface;
|
public function getSubdomainsByUserIdAndName($id, $name): PromiseInterface;
|
||||||
|
|||||||
@@ -10,15 +10,11 @@ interface UserRepository
|
|||||||
|
|
||||||
public function getUserById($id): PromiseInterface;
|
public function getUserById($id): PromiseInterface;
|
||||||
|
|
||||||
public function paginateUsers(string $searchQuery, int $perPage, int $currentPage): PromiseInterface;
|
public function paginateUsers(int $perPage, int $currentPage): PromiseInterface;
|
||||||
|
|
||||||
public function getUserByToken(string $authToken): PromiseInterface;
|
public function getUserByToken(string $authToken): PromiseInterface;
|
||||||
|
|
||||||
public function storeUser(array $data): PromiseInterface;
|
public function storeUser(array $data): PromiseInterface;
|
||||||
|
|
||||||
public function deleteUser($id): PromiseInterface;
|
public function deleteUser($id): PromiseInterface;
|
||||||
|
|
||||||
public function getUsersByTokens(array $authTokens): PromiseInterface;
|
|
||||||
|
|
||||||
public function updateLastSharedAt($id): PromiseInterface;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use Twig\Loader\ArrayLoader;
|
|||||||
|
|
||||||
trait LoadsViews
|
trait LoadsViews
|
||||||
{
|
{
|
||||||
protected function getView(?ConnectionInterface $connection, string $view, array $data = [])
|
protected function getView(ConnectionInterface $connection, string $view, array $data = [])
|
||||||
{
|
{
|
||||||
$templatePath = implode(DIRECTORY_SEPARATOR, explode('.', $view));
|
$templatePath = implode(DIRECTORY_SEPARATOR, explode('.', $view));
|
||||||
|
|
||||||
@@ -23,10 +23,7 @@ trait LoadsViews
|
|||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'request' => $connection->laravelRequest ?? null,
|
'request' => $connection->laravelRequest ?? null,
|
||||||
]);
|
]);
|
||||||
try {
|
|
||||||
return stream_for($twig->render('template', $data));
|
return stream_for($twig->render('template', $data));
|
||||||
} catch (\Throwable $e) {
|
|
||||||
var_dump($e->getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,60 +2,29 @@
|
|||||||
|
|
||||||
namespace App\Logger;
|
namespace App\Logger;
|
||||||
|
|
||||||
use App\Client\Support\ConsoleSectionOutput;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Symfony\Component\Console\Helper\Table;
|
||||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||||
use Symfony\Component\Console\Terminal;
|
|
||||||
|
|
||||||
class CliRequestLogger extends Logger
|
class CliRequestLogger extends Logger
|
||||||
{
|
{
|
||||||
|
/** @var Table */
|
||||||
|
protected $table;
|
||||||
|
|
||||||
/** @var Collection */
|
/** @var Collection */
|
||||||
protected $requests;
|
protected $requests;
|
||||||
|
|
||||||
|
/** @var \Symfony\Component\Console\Output\ConsoleSectionOutput */
|
||||||
protected $section;
|
protected $section;
|
||||||
|
|
||||||
protected $verbColors = [
|
|
||||||
'GET' => 'blue',
|
|
||||||
'HEAD' => '#6C7280',
|
|
||||||
'OPTIONS' => '#6C7280',
|
|
||||||
'POST' => 'yellow',
|
|
||||||
'PUT' => 'yellow',
|
|
||||||
'PATCH' => 'yellow',
|
|
||||||
'DELETE' => 'red',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected $consoleSectionOutputs = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current terminal width.
|
|
||||||
*
|
|
||||||
* @var int|null
|
|
||||||
*/
|
|
||||||
protected $terminalWidth;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Computes the terminal width.
|
|
||||||
*
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
protected function getTerminalWidth()
|
|
||||||
{
|
|
||||||
if ($this->terminalWidth == null) {
|
|
||||||
$this->terminalWidth = (new Terminal)->getWidth();
|
|
||||||
|
|
||||||
$this->terminalWidth = $this->terminalWidth >= 30
|
|
||||||
? $this->terminalWidth
|
|
||||||
: 30;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->terminalWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __construct(ConsoleOutputInterface $consoleOutput)
|
public function __construct(ConsoleOutputInterface $consoleOutput)
|
||||||
{
|
{
|
||||||
parent::__construct($consoleOutput);
|
parent::__construct($consoleOutput);
|
||||||
|
|
||||||
$this->section = new ConsoleSectionOutput($this->output->getStream(), $this->consoleSectionOutputs, $this->output->getVerbosity(), $this->output->isDecorated(), $this->output->getFormatter());
|
$this->section = $this->output->section();
|
||||||
|
|
||||||
|
$this->table = new Table($this->section);
|
||||||
|
$this->table->setHeaders(['Method', 'URI', 'Response', 'Time', 'Duration']);
|
||||||
|
|
||||||
$this->requests = new Collection();
|
$this->requests = new Collection();
|
||||||
}
|
}
|
||||||
@@ -68,28 +37,8 @@ class CliRequestLogger extends Logger
|
|||||||
return $this->output;
|
return $this->output;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getRequestColor(?LoggedRequest $request)
|
|
||||||
{
|
|
||||||
$statusCode = optional($request->getResponse())->getStatusCode();
|
|
||||||
$color = 'white';
|
|
||||||
|
|
||||||
if ($statusCode >= 200 && $statusCode < 300) {
|
|
||||||
$color = 'green';
|
|
||||||
} elseif ($statusCode >= 300 && $statusCode < 400) {
|
|
||||||
$color = 'blue';
|
|
||||||
} elseif ($statusCode >= 400 && $statusCode < 500) {
|
|
||||||
$color = 'yellow';
|
|
||||||
} elseif ($statusCode >= 500) {
|
|
||||||
$color = 'red';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logRequest(LoggedRequest $loggedRequest)
|
public function logRequest(LoggedRequest $loggedRequest)
|
||||||
{
|
{
|
||||||
$dashboardUrl = 'http://127.0.0.1:'.config('expose.dashboard_port');
|
|
||||||
|
|
||||||
if ($this->requests->has($loggedRequest->id())) {
|
if ($this->requests->has($loggedRequest->id())) {
|
||||||
$this->requests[$loggedRequest->id()] = $loggedRequest;
|
$this->requests[$loggedRequest->id()] = $loggedRequest;
|
||||||
} else {
|
} else {
|
||||||
@@ -97,55 +46,18 @@ class CliRequestLogger extends Logger
|
|||||||
}
|
}
|
||||||
$this->requests = $this->requests->slice(0, config('expose.max_logged_requests', 10));
|
$this->requests = $this->requests->slice(0, config('expose.max_logged_requests', 10));
|
||||||
|
|
||||||
$terminalWidth = $this->getTerminalWidth();
|
$this->section->clear();
|
||||||
|
|
||||||
$requests = $this->requests->map(function (LoggedRequest $loggedRequest) {
|
$this->table->setRows($this->requests->map(function (LoggedRequest $loggedRequest) {
|
||||||
return [
|
return [
|
||||||
'method' => $loggedRequest->getRequest()->getMethod(),
|
$loggedRequest->getRequest()->getMethod(),
|
||||||
'url' => $loggedRequest->getRequest()->getUri(),
|
$loggedRequest->getRequest()->getUri(),
|
||||||
'duration' => $loggedRequest->getDuration(),
|
optional($loggedRequest->getResponse())->getStatusCode().' '.optional($loggedRequest->getResponse())->getReasonPhrase(),
|
||||||
'time' => $loggedRequest->getStartTime()->isToday() ? $loggedRequest->getStartTime()->toTimeString() : $loggedRequest->getStartTime()->toDateTimeString(),
|
$loggedRequest->getStartTime()->toDateTimeString(),
|
||||||
'color' => $this->getRequestColor($loggedRequest),
|
$loggedRequest->getDuration().'ms',
|
||||||
'status' => optional($loggedRequest->getResponse())->getStatusCode(),
|
|
||||||
];
|
];
|
||||||
});
|
})->toArray());
|
||||||
|
|
||||||
$maxMethod = mb_strlen($requests->max('method'));
|
$this->table->render();
|
||||||
$maxDuration = mb_strlen($requests->max('duration'));
|
|
||||||
|
|
||||||
$output = $requests->map(function ($loggedRequest) use ($terminalWidth, $maxMethod, $maxDuration) {
|
|
||||||
$method = $loggedRequest['method'];
|
|
||||||
$spaces = str_repeat(' ', max($maxMethod + 2 - mb_strlen($method), 0));
|
|
||||||
$url = $loggedRequest['url'];
|
|
||||||
$duration = $loggedRequest['duration'];
|
|
||||||
$time = $loggedRequest['time'];
|
|
||||||
$durationSpaces = str_repeat(' ', max($maxDuration + 2 - mb_strlen($duration), 0));
|
|
||||||
$color = $loggedRequest['color'];
|
|
||||||
$status = $loggedRequest['status'];
|
|
||||||
|
|
||||||
$dots = str_repeat('.', max($terminalWidth - strlen($method.$spaces.$url.$time.$durationSpaces.$duration) - 16, 0));
|
|
||||||
|
|
||||||
if (empty($dots)) {
|
|
||||||
$url = substr($url, 0, $terminalWidth - strlen($method.$spaces.$time.$durationSpaces.$duration) - 15 - 3).'...';
|
|
||||||
} else {
|
|
||||||
$dots .= ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf(
|
|
||||||
' <fg=%s;options=bold>%s </> <fg=%s;options=bold>%s%s</> %s<fg=#6C7280> %s%s%s%s ms</>',
|
|
||||||
$color,
|
|
||||||
$status,
|
|
||||||
$this->verbColors[$method] ?? 'default',
|
|
||||||
$method,
|
|
||||||
$spaces,
|
|
||||||
$url,
|
|
||||||
$dots,
|
|
||||||
$time,
|
|
||||||
$durationSpaces,
|
|
||||||
$duration,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->section->overwrite($output);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,9 @@
|
|||||||
namespace App\Logger;
|
namespace App\Logger;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use function GuzzleHttp\Psr7\parse_request;
|
use function GuzzleHttp\Psr7\parse_request;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Laminas\Http\Header\GenericHeader;
|
|
||||||
use Laminas\Http\Request;
|
use Laminas\Http\Request;
|
||||||
use Laminas\Http\Response;
|
use Laminas\Http\Response;
|
||||||
use Namshi\Cuzzle\Formatter\CurlFormatter;
|
use Namshi\Cuzzle\Formatter\CurlFormatter;
|
||||||
@@ -50,7 +48,6 @@ class LoggedRequest implements \JsonSerializable
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function jsonSerialize()
|
public function jsonSerialize()
|
||||||
{
|
{
|
||||||
$data = [
|
$data = [
|
||||||
@@ -174,7 +171,7 @@ class LoggedRequest implements \JsonSerializable
|
|||||||
return $postData;
|
return $postData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function detectSubdomain()
|
protected function detectSubdomain()
|
||||||
{
|
{
|
||||||
return collect($this->parsedRequest->getHeaders()->toArray())
|
return collect($this->parsedRequest->getHeaders()->toArray())
|
||||||
->mapWithKeys(function ($value, $key) {
|
->mapWithKeys(function ($value, $key) {
|
||||||
@@ -214,23 +211,4 @@ class LoggedRequest implements \JsonSerializable
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUrl()
|
|
||||||
{
|
|
||||||
$request = Message::parseRequest($this->rawRequest);
|
|
||||||
dd($request->getUri()->withFragment(''));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function refreshId()
|
|
||||||
{
|
|
||||||
$requestId = (string) Str::uuid();
|
|
||||||
|
|
||||||
$this->getRequest()->getHeaders()->removeHeader(
|
|
||||||
$this->getRequest()->getHeader('x-expose-request-id')
|
|
||||||
);
|
|
||||||
|
|
||||||
$this->getRequest()->getHeaders()->addHeader(new GenericHeader('x-expose-request-id', $requestId));
|
|
||||||
|
|
||||||
$this->id = $requestId;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
public function boot()
|
public function boot()
|
||||||
{
|
{
|
||||||
UriFactory::registerScheme('capacitor', Uri::class);
|
|
||||||
UriFactory::registerScheme('chrome-extension', Uri::class);
|
UriFactory::registerScheme('chrome-extension', Uri::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class Configuration implements \JsonSerializable
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function jsonSerialize()
|
public function jsonSerialize()
|
||||||
{
|
{
|
||||||
return array_merge([
|
return array_merge([
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
namespace App\Server\Connections;
|
namespace App\Server\Connections;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
||||||
use App\Contracts\LoggerRepository;
|
|
||||||
use App\Contracts\StatisticsCollector;
|
|
||||||
use App\Contracts\SubdomainGenerator;
|
use App\Contracts\SubdomainGenerator;
|
||||||
use App\Http\QueryParameters;
|
use App\Http\QueryParameters;
|
||||||
use App\Server\Exceptions\NoFreePortAvailable;
|
use App\Server\Exceptions\NoFreePortAvailable;
|
||||||
@@ -26,18 +24,10 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
/** @var LoopInterface */
|
/** @var LoopInterface */
|
||||||
protected $loop;
|
protected $loop;
|
||||||
|
|
||||||
/** @var StatisticsCollector */
|
public function __construct(SubdomainGenerator $subdomainGenerator, LoopInterface $loop)
|
||||||
protected $statisticsCollector;
|
|
||||||
|
|
||||||
/** @var LoggerRepository */
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
public function __construct(SubdomainGenerator $subdomainGenerator, StatisticsCollector $statisticsCollector, LoggerRepository $logger, LoopInterface $loop)
|
|
||||||
{
|
{
|
||||||
$this->subdomainGenerator = $subdomainGenerator;
|
$this->subdomainGenerator = $subdomainGenerator;
|
||||||
$this->loop = $loop;
|
$this->loop = $loop;
|
||||||
$this->statisticsCollector = $statisticsCollector;
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function limitConnectionLength(ControlConnection $connection, int $maximumConnectionLength)
|
public function limitConnectionLength(ControlConnection $connection, int $maximumConnectionLength)
|
||||||
@@ -53,41 +43,23 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeConnection(string $host, ?string $subdomain, ?string $serverHost, ConnectionInterface $connection): ControlConnection
|
public function storeConnection(string $host, ?string $subdomain, ConnectionInterface $connection): ControlConnection
|
||||||
{
|
{
|
||||||
$clientId = (string) uniqid();
|
$connection->client_id = sha1(uniqid('', true));
|
||||||
|
|
||||||
$connection->client_id = $clientId;
|
|
||||||
|
|
||||||
$storedConnection = new ControlConnection(
|
$storedConnection = new ControlConnection(
|
||||||
$connection,
|
$connection,
|
||||||
$host,
|
$host,
|
||||||
$subdomain ?? $this->subdomainGenerator->generateSubdomain(),
|
$subdomain ?? $this->subdomainGenerator->generateSubdomain(),
|
||||||
$clientId,
|
$connection->client_id,
|
||||||
$serverHost,
|
|
||||||
$this->getAuthTokenFromConnection($connection)
|
$this->getAuthTokenFromConnection($connection)
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->connections[] = $storedConnection;
|
$this->connections[] = $storedConnection;
|
||||||
|
|
||||||
$this->statisticsCollector->siteShared($this->getAuthTokenFromConnection($connection));
|
|
||||||
|
|
||||||
$this->logger->logSubdomain($storedConnection->authToken, $storedConnection->subdomain);
|
|
||||||
|
|
||||||
$this->performConnectionCallback($storedConnection);
|
|
||||||
|
|
||||||
return $storedConnection;
|
return $storedConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function performConnectionCallback(ControlConnection $connection)
|
|
||||||
{
|
|
||||||
$connectionCallback = config('expose.admin.connection_callback');
|
|
||||||
|
|
||||||
if ($connectionCallback !== null && class_exists($connectionCallback)) {
|
|
||||||
app($connectionCallback)->handle($connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function storeTcpConnection(int $port, ConnectionInterface $connection): ControlConnection
|
public function storeTcpConnection(int $port, ConnectionInterface $connection): ControlConnection
|
||||||
{
|
{
|
||||||
$clientId = (string) uniqid();
|
$clientId = (string) uniqid();
|
||||||
@@ -104,8 +76,6 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
|
|
||||||
$this->connections[] = $storedConnection;
|
$this->connections[] = $storedConnection;
|
||||||
|
|
||||||
$this->statisticsCollector->portShared($this->getAuthTokenFromConnection($connection));
|
|
||||||
|
|
||||||
return $storedConnection;
|
return $storedConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,10 +141,10 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findControlConnectionForSubdomainAndServerHost($subdomain, $serverHost): ?ControlConnection
|
public function findControlConnectionForSubdomain($subdomain): ?ControlConnection
|
||||||
{
|
{
|
||||||
return collect($this->connections)->last(function ($connection) use ($subdomain, $serverHost) {
|
return collect($this->connections)->last(function ($connection) use ($subdomain) {
|
||||||
return $connection->subdomain == $subdomain && $connection->serverHost === $serverHost;
|
return $connection->subdomain == $subdomain;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,20 +155,6 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findControlConnectionsForIp(string $ip): array
|
|
||||||
{
|
|
||||||
return collect($this->connections)->filter(function (ControlConnection $connection) use ($ip) {
|
|
||||||
return $connection->socket->remoteAddress == $ip;
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findControlConnectionsForAuthToken(string $token): array
|
|
||||||
{
|
|
||||||
return collect($this->connections)->filter(function (ControlConnection $connection) use ($token) {
|
|
||||||
return $connection->authToken === $token;
|
|
||||||
})->toArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getConnections(): array
|
public function getConnections(): array
|
||||||
{
|
{
|
||||||
return $this->connections;
|
return $this->connections;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Server\Connections;
|
namespace App\Server\Connections;
|
||||||
|
|
||||||
use App\Http\QueryParameters;
|
|
||||||
use Evenement\EventEmitterTrait;
|
use Evenement\EventEmitterTrait;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -13,24 +12,20 @@ class ControlConnection
|
|||||||
/** @var ConnectionInterface */
|
/** @var ConnectionInterface */
|
||||||
public $socket;
|
public $socket;
|
||||||
public $host;
|
public $host;
|
||||||
public $serverHost;
|
|
||||||
public $authToken;
|
public $authToken;
|
||||||
public $subdomain;
|
public $subdomain;
|
||||||
public $client_id;
|
public $client_id;
|
||||||
public $client_version;
|
|
||||||
public $proxies = [];
|
public $proxies = [];
|
||||||
protected $shared_at;
|
protected $shared_at;
|
||||||
|
|
||||||
public function __construct(ConnectionInterface $socket, string $host, string $subdomain, string $clientId, string $serverHost, string $authToken = '')
|
public function __construct(ConnectionInterface $socket, string $host, string $subdomain, string $clientId, string $authToken = '')
|
||||||
{
|
{
|
||||||
$this->socket = $socket;
|
$this->socket = $socket;
|
||||||
$this->host = $host;
|
$this->host = $host;
|
||||||
$this->subdomain = $subdomain;
|
$this->subdomain = $subdomain;
|
||||||
$this->client_id = $clientId;
|
$this->client_id = $clientId;
|
||||||
$this->authToken = $authToken;
|
$this->authToken = $authToken;
|
||||||
$this->serverHost = $serverHost;
|
|
||||||
$this->shared_at = now()->toDateTimeString();
|
$this->shared_at = now()->toDateTimeString();
|
||||||
$this->client_version = QueryParameters::create($socket->httpRequest)->get('version');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setMaximumConnectionLength(int $maximumConnectionLength)
|
public function setMaximumConnectionLength(int $maximumConnectionLength)
|
||||||
@@ -66,10 +61,7 @@ class ControlConnection
|
|||||||
return [
|
return [
|
||||||
'type' => 'http',
|
'type' => 'http',
|
||||||
'host' => $this->host,
|
'host' => $this->host,
|
||||||
'remote_address' => $this->socket->remoteAddress ?? null,
|
|
||||||
'server_host' => $this->serverHost,
|
|
||||||
'client_id' => $this->client_id,
|
'client_id' => $this->client_id,
|
||||||
'client_version' => $this->client_version,
|
|
||||||
'auth_token' => $this->authToken,
|
'auth_token' => $this->authToken,
|
||||||
'subdomain' => $this->subdomain,
|
'subdomain' => $this->subdomain,
|
||||||
'shared_at' => $this->shared_at,
|
'shared_at' => $this->shared_at,
|
||||||
|
|||||||
@@ -76,7 +76,6 @@ class TcpControlConnection extends ControlConnection
|
|||||||
return [
|
return [
|
||||||
'type' => 'tcp',
|
'type' => 'tcp',
|
||||||
'port' => $this->port,
|
'port' => $this->port,
|
||||||
'auth_token' => $this->authToken,
|
|
||||||
'client_id' => $this->client_id,
|
'client_id' => $this->client_id,
|
||||||
'shared_port' => $this->shared_port,
|
'shared_port' => $this->shared_port,
|
||||||
'shared_at' => $this->shared_at,
|
'shared_at' => $this->shared_at,
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\DomainRepository;
|
|
||||||
|
|
||||||
use App\Contracts\DomainRepository;
|
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
|
||||||
use Clue\React\SQLite\Result;
|
|
||||||
use React\Promise\Deferred;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
class DatabaseDomainRepository implements DomainRepository
|
|
||||||
{
|
|
||||||
/** @var DatabaseInterface */
|
|
||||||
protected $database;
|
|
||||||
|
|
||||||
public function __construct(DatabaseInterface $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomains(): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM domains ORDER by created_at DESC')
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomainById($id): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM domains WHERE id = :id', ['id' => $id])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows[0] ?? null);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomainByName(string $name): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM domains WHERE domain = :name', ['name' => $name])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows[0] ?? null);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomainsByUserId($id): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM domains WHERE user_id = :user_id ORDER by created_at DESC', [
|
|
||||||
'user_id' => $id,
|
|
||||||
])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function storeDomain(array $data): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->getDomainByName($data['domain'])
|
|
||||||
->then(function ($registeredDomain) use ($data, $deferred) {
|
|
||||||
$this->database->query("
|
|
||||||
INSERT INTO domains (user_id, domain, created_at)
|
|
||||||
VALUES (:user_id, :domain, DATETIME('now'))
|
|
||||||
", $data)
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$this->database->query('SELECT * FROM domains WHERE id = :id', ['id' => $result->insertId])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getDomainsByUserIdAndName($id, $name): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM domains WHERE user_id = :user_id AND domain = :name ORDER by created_at DESC', [
|
|
||||||
'user_id' => $id,
|
|
||||||
'name' => $name,
|
|
||||||
])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function deleteDomainForUserId($userId, $domainId): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database->query('DELETE FROM domains WHERE id = :id AND user_id = :user_id', [
|
|
||||||
'id' => $domainId,
|
|
||||||
'user_id' => $userId,
|
|
||||||
])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateDomain($id, array $data): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,27 +3,18 @@
|
|||||||
namespace App\Server;
|
namespace App\Server;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
use App\Contracts\ConnectionManager as ConnectionManagerContract;
|
||||||
use App\Contracts\DomainRepository;
|
|
||||||
use App\Contracts\LoggerRepository;
|
|
||||||
use App\Contracts\StatisticsCollector;
|
|
||||||
use App\Contracts\StatisticsRepository;
|
|
||||||
use App\Contracts\SubdomainGenerator;
|
use App\Contracts\SubdomainGenerator;
|
||||||
use App\Contracts\SubdomainRepository;
|
use App\Contracts\SubdomainRepository;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use App\Http\RouteGenerator;
|
use App\Http\RouteGenerator;
|
||||||
use App\Http\Server as HttpServer;
|
use App\Http\Server as HttpServer;
|
||||||
use App\Server\Connections\ConnectionManager;
|
use App\Server\Connections\ConnectionManager;
|
||||||
use App\Server\DomainRepository\DatabaseDomainRepository;
|
|
||||||
use App\Server\Http\Controllers\Admin\DeleteSubdomainController;
|
use App\Server\Http\Controllers\Admin\DeleteSubdomainController;
|
||||||
use App\Server\Http\Controllers\Admin\DeleteUsersController;
|
use App\Server\Http\Controllers\Admin\DeleteUsersController;
|
||||||
use App\Server\Http\Controllers\Admin\DisconnectSiteController;
|
use App\Server\Http\Controllers\Admin\DisconnectSiteController;
|
||||||
use App\Server\Http\Controllers\Admin\DisconnectTcpConnectionController;
|
use App\Server\Http\Controllers\Admin\DisconnectTcpConnectionController;
|
||||||
use App\Server\Http\Controllers\Admin\GetLogsController;
|
|
||||||
use App\Server\Http\Controllers\Admin\GetLogsForSubdomainController;
|
|
||||||
use App\Server\Http\Controllers\Admin\GetSettingsController;
|
use App\Server\Http\Controllers\Admin\GetSettingsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetSiteDetailsController;
|
|
||||||
use App\Server\Http\Controllers\Admin\GetSitesController;
|
use App\Server\Http\Controllers\Admin\GetSitesController;
|
||||||
use App\Server\Http\Controllers\Admin\GetStatisticsController;
|
|
||||||
use App\Server\Http\Controllers\Admin\GetTcpConnectionsController;
|
use App\Server\Http\Controllers\Admin\GetTcpConnectionsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetUserDetailsController;
|
use App\Server\Http\Controllers\Admin\GetUserDetailsController;
|
||||||
use App\Server\Http\Controllers\Admin\GetUsersController;
|
use App\Server\Http\Controllers\Admin\GetUsersController;
|
||||||
@@ -32,17 +23,12 @@ use App\Server\Http\Controllers\Admin\ListTcpConnectionsController;
|
|||||||
use App\Server\Http\Controllers\Admin\ListUsersController;
|
use App\Server\Http\Controllers\Admin\ListUsersController;
|
||||||
use App\Server\Http\Controllers\Admin\RedirectToUsersController;
|
use App\Server\Http\Controllers\Admin\RedirectToUsersController;
|
||||||
use App\Server\Http\Controllers\Admin\ShowSettingsController;
|
use App\Server\Http\Controllers\Admin\ShowSettingsController;
|
||||||
use App\Server\Http\Controllers\Admin\StoreDomainController;
|
|
||||||
use App\Server\Http\Controllers\Admin\StoreSettingsController;
|
use App\Server\Http\Controllers\Admin\StoreSettingsController;
|
||||||
use App\Server\Http\Controllers\Admin\StoreSubdomainController;
|
use App\Server\Http\Controllers\Admin\StoreSubdomainController;
|
||||||
use App\Server\Http\Controllers\Admin\StoreUsersController;
|
use App\Server\Http\Controllers\Admin\StoreUsersController;
|
||||||
use App\Server\Http\Controllers\ControlMessageController;
|
use App\Server\Http\Controllers\ControlMessageController;
|
||||||
use App\Server\Http\Controllers\TunnelMessageController;
|
use App\Server\Http\Controllers\TunnelMessageController;
|
||||||
use App\Server\Http\Router;
|
use App\Server\Http\Router;
|
||||||
use App\Server\LoggerRepository\NullLogger;
|
|
||||||
use App\Server\StatisticsCollector\DatabaseStatisticsCollector;
|
|
||||||
use App\Server\StatisticsRepository\DatabaseStatisticsRepository;
|
|
||||||
use App\Server\SubdomainRepository\DatabaseSubdomainRepository;
|
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
use Phar;
|
use Phar;
|
||||||
use Ratchet\Server\IoServer;
|
use Ratchet\Server\IoServer;
|
||||||
@@ -142,28 +128,16 @@ class Factory
|
|||||||
$this->router->get('/sites', ListSitesController::class, $adminCondition);
|
$this->router->get('/sites', ListSitesController::class, $adminCondition);
|
||||||
$this->router->get('/tcp', ListTcpConnectionsController::class, $adminCondition);
|
$this->router->get('/tcp', ListTcpConnectionsController::class, $adminCondition);
|
||||||
|
|
||||||
$this->router->get('/api/statistics', GetStatisticsController::class, $adminCondition);
|
|
||||||
$this->router->get('/api/settings', GetSettingsController::class, $adminCondition);
|
$this->router->get('/api/settings', GetSettingsController::class, $adminCondition);
|
||||||
$this->router->post('/api/settings', StoreSettingsController::class, $adminCondition);
|
$this->router->post('/api/settings', StoreSettingsController::class, $adminCondition);
|
||||||
|
|
||||||
$this->router->get('/api/users', GetUsersController::class, $adminCondition);
|
$this->router->get('/api/users', GetUsersController::class, $adminCondition);
|
||||||
$this->router->post('/api/users', StoreUsersController::class, $adminCondition);
|
$this->router->post('/api/users', StoreUsersController::class, $adminCondition);
|
||||||
$this->router->get('/api/users/{id}', GetUserDetailsController::class, $adminCondition);
|
$this->router->get('/api/users/{id}', GetUserDetailsController::class, $adminCondition);
|
||||||
$this->router->delete('/api/users/{id}', DeleteUsersController::class, $adminCondition);
|
|
||||||
|
|
||||||
$this->router->get('/api/logs', GetLogsController::class, $adminCondition);
|
|
||||||
$this->router->get('/api/logs/{subdomain}', GetLogsForSubdomainController::class, $adminCondition);
|
|
||||||
|
|
||||||
$this->router->post('/api/domains', StoreDomainController::class, $adminCondition);
|
|
||||||
$this->router->delete('/api/domains/{domain}', DeleteSubdomainController::class, $adminCondition);
|
|
||||||
|
|
||||||
$this->router->post('/api/subdomains', StoreSubdomainController::class, $adminCondition);
|
$this->router->post('/api/subdomains', StoreSubdomainController::class, $adminCondition);
|
||||||
$this->router->delete('/api/subdomains/{subdomain}', DeleteSubdomainController::class, $adminCondition);
|
$this->router->delete('/api/subdomains/{subdomain}', DeleteSubdomainController::class, $adminCondition);
|
||||||
|
$this->router->delete('/api/users/{id}', DeleteUsersController::class, $adminCondition);
|
||||||
$this->router->get('/api/sites', GetSitesController::class, $adminCondition);
|
$this->router->get('/api/sites', GetSitesController::class, $adminCondition);
|
||||||
$this->router->get('/api/sites/{site}', GetSiteDetailsController::class, $adminCondition);
|
|
||||||
$this->router->delete('/api/sites/{id}', DisconnectSiteController::class, $adminCondition);
|
$this->router->delete('/api/sites/{id}', DisconnectSiteController::class, $adminCondition);
|
||||||
|
|
||||||
$this->router->get('/api/tcp', GetTcpConnectionsController::class, $adminCondition);
|
$this->router->get('/api/tcp', GetTcpConnectionsController::class, $adminCondition);
|
||||||
$this->router->delete('/api/tcp/{id}', DisconnectTcpConnectionController::class, $adminCondition);
|
$this->router->delete('/api/tcp/{id}', DisconnectTcpConnectionController::class, $adminCondition);
|
||||||
}
|
}
|
||||||
@@ -202,12 +176,9 @@ class Factory
|
|||||||
$this->bindConfiguration()
|
$this->bindConfiguration()
|
||||||
->bindSubdomainGenerator()
|
->bindSubdomainGenerator()
|
||||||
->bindUserRepository()
|
->bindUserRepository()
|
||||||
->bindLoggerRepository()
|
|
||||||
->bindSubdomainRepository()
|
->bindSubdomainRepository()
|
||||||
->bindDomainRepository()
|
|
||||||
->bindDatabase()
|
->bindDatabase()
|
||||||
->ensureDatabaseIsInitialized()
|
->ensureDatabaseIsInitialized()
|
||||||
->registerStatisticsCollector()
|
|
||||||
->bindConnectionManager()
|
->bindConnectionManager()
|
||||||
->addAdminRoutes();
|
->addAdminRoutes();
|
||||||
|
|
||||||
@@ -245,25 +216,7 @@ class Factory
|
|||||||
protected function bindSubdomainRepository()
|
protected function bindSubdomainRepository()
|
||||||
{
|
{
|
||||||
app()->singleton(SubdomainRepository::class, function () {
|
app()->singleton(SubdomainRepository::class, function () {
|
||||||
return app(config('expose.admin.subdomain_repository', DatabaseSubdomainRepository::class));
|
return app(config('expose.admin.subdomain_repository'));
|
||||||
});
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function bindLoggerRepository()
|
|
||||||
{
|
|
||||||
app()->singleton(LoggerRepository::class, function () {
|
|
||||||
return app(config('expose.admin.logger_repository', NullLogger::class));
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function bindDomainRepository()
|
|
||||||
{
|
|
||||||
app()->singleton(DomainRepository::class, function () {
|
|
||||||
return app(config('expose.admin.domain_repository', DatabaseDomainRepository::class));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
@@ -295,8 +248,7 @@ class Factory
|
|||||||
->files()
|
->files()
|
||||||
->ignoreDotFiles(true)
|
->ignoreDotFiles(true)
|
||||||
->in(database_path('migrations'))
|
->in(database_path('migrations'))
|
||||||
->name('*.sql')
|
->name('*.sql');
|
||||||
->sortByName();
|
|
||||||
|
|
||||||
/** @var SplFileInfo $migration */
|
/** @var SplFileInfo $migration */
|
||||||
foreach ($migrations as $migration) {
|
foreach ($migrations as $migration) {
|
||||||
@@ -312,27 +264,4 @@ class Factory
|
|||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function registerStatisticsCollector()
|
|
||||||
{
|
|
||||||
if (config('expose.admin.statistics.enable_statistics', true) === false) {
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
app()->singleton(StatisticsRepository::class, function () {
|
|
||||||
return app(config('expose.admin.statistics.repository', DatabaseStatisticsRepository::class));
|
|
||||||
});
|
|
||||||
|
|
||||||
app()->singleton(StatisticsCollector::class, function () {
|
|
||||||
return app(DatabaseStatisticsCollector::class);
|
|
||||||
});
|
|
||||||
|
|
||||||
$intervalInSeconds = config('expose.admin.statistics.interval_in_seconds', 3600);
|
|
||||||
|
|
||||||
$this->loop->addPeriodicTimer($intervalInSeconds, function () {
|
|
||||||
app(StatisticsCollector::class)->save();
|
|
||||||
});
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
@@ -14,7 +14,7 @@ abstract class AdminController extends Controller
|
|||||||
protected function shouldHandleRequest(Request $request, ConnectionInterface $httpConnection): bool
|
protected function shouldHandleRequest(Request $request, ConnectionInterface $httpConnection): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$authorization = Str::after($request->header('Authorization', ''), 'Basic ');
|
$authorization = Str::after($request->header('Authorization'), 'Basic ');
|
||||||
$authParts = explode(':', base64_decode($authorization), 2);
|
$authParts = explode(':', base64_decode($authorization), 2);
|
||||||
[$user, $password] = $authParts;
|
[$user, $password] = $authParts;
|
||||||
|
|
||||||
@@ -24,11 +24,9 @@ abstract class AdminController extends Controller
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$httpConnection->send(Message::toString(new Response(401, [
|
$httpConnection->send(str(new Response(401, [
|
||||||
'WWW-Authenticate' => 'Basic realm="Expose"',
|
'WWW-Authenticate' => 'Basic realm="Expose"',
|
||||||
])));
|
])));
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -22,11 +22,7 @@ class DisconnectSiteController extends AdminController
|
|||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
if ($request->has('server_host')) {
|
$connection = $this->connectionManager->findControlConnectionForClientId($request->get('id'));
|
||||||
$connection = $this->connectionManager->findControlConnectionForSubdomainAndServerHost($request->get('id'), $request->get('server_host'));
|
|
||||||
} else {
|
|
||||||
$connection = $this->connectionManager->findControlConnectionForClientId($request->get('id'));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_null($connection)) {
|
if (! is_null($connection)) {
|
||||||
$connection->close();
|
$connection->close();
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Contracts\LoggerRepository;
|
|
||||||
use App\Server\Configuration;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Ratchet\ConnectionInterface;
|
|
||||||
|
|
||||||
class GetLogsController extends AdminController
|
|
||||||
{
|
|
||||||
protected $keepConnectionOpen = true;
|
|
||||||
|
|
||||||
/** @var Configuration */
|
|
||||||
protected $configuration;
|
|
||||||
|
|
||||||
/** @var LoggerRepository */
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
public function __construct(LoggerRepository $logger)
|
|
||||||
{
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
|
||||||
{
|
|
||||||
$subdomain = $request->get('subdomain');
|
|
||||||
$this->logger->getLogs()
|
|
||||||
->then(function ($logs) use ($httpConnection) {
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json(['logs' => $logs])
|
|
||||||
);
|
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Contracts\LoggerRepository;
|
|
||||||
use App\Server\Configuration;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Ratchet\ConnectionInterface;
|
|
||||||
|
|
||||||
class GetLogsForSubdomainController extends AdminController
|
|
||||||
{
|
|
||||||
protected $keepConnectionOpen = true;
|
|
||||||
|
|
||||||
/** @var Configuration */
|
|
||||||
protected $configuration;
|
|
||||||
|
|
||||||
/** @var LoggerRepository */
|
|
||||||
protected $logger;
|
|
||||||
|
|
||||||
public function __construct(LoggerRepository $logger)
|
|
||||||
{
|
|
||||||
$this->logger = $logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
|
||||||
{
|
|
||||||
$subdomain = $request->get('subdomain');
|
|
||||||
$this->logger->getLogsBySubdomain($subdomain)
|
|
||||||
->then(function ($logs) use ($httpConnection) {
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json(['logs' => $logs])
|
|
||||||
);
|
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
|
||||||
use App\Server\Configuration;
|
|
||||||
use App\Server\Connections\ControlConnection;
|
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Ratchet\ConnectionInterface;
|
|
||||||
|
|
||||||
class GetSiteDetailsController extends AdminController
|
|
||||||
{
|
|
||||||
/** @var ConnectionManager */
|
|
||||||
protected $connectionManager;
|
|
||||||
|
|
||||||
/** @var Configuration */
|
|
||||||
protected $configuration;
|
|
||||||
|
|
||||||
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
|
||||||
{
|
|
||||||
$this->connectionManager = $connectionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
|
||||||
{
|
|
||||||
$domain = $request->get('site');
|
|
||||||
|
|
||||||
$connectedSite = collect($this->connectionManager->getConnections())
|
|
||||||
->filter(function ($connection) {
|
|
||||||
return get_class($connection) === ControlConnection::class;
|
|
||||||
})
|
|
||||||
->first(function (ControlConnection $site) use ($domain) {
|
|
||||||
return "{$site->subdomain}.{$site->serverHost}" === $domain;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is_null($connectedSite)) {
|
|
||||||
$httpConnection->send(
|
|
||||||
Message::toString(new Response(404))
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json($connectedSite->toArray())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
use App\Contracts\UserRepository;
|
|
||||||
use App\Server\Configuration;
|
use App\Server\Configuration;
|
||||||
use App\Server\Connections\ControlConnection;
|
use App\Server\Connections\ControlConnection;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -11,55 +10,31 @@ use Ratchet\ConnectionInterface;
|
|||||||
|
|
||||||
class GetSitesController extends AdminController
|
class GetSitesController extends AdminController
|
||||||
{
|
{
|
||||||
protected $keepConnectionOpen = true;
|
|
||||||
|
|
||||||
/** @var ConnectionManager */
|
/** @var ConnectionManager */
|
||||||
protected $connectionManager;
|
protected $connectionManager;
|
||||||
|
|
||||||
/** @var Configuration */
|
/** @var Configuration */
|
||||||
protected $configuration;
|
protected $configuration;
|
||||||
|
|
||||||
/** @var UserRepository */
|
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
||||||
protected $userRepository;
|
|
||||||
|
|
||||||
public function __construct(ConnectionManager $connectionManager, Configuration $configuration, UserRepository $userRepository)
|
|
||||||
{
|
{
|
||||||
$this->connectionManager = $connectionManager;
|
$this->connectionManager = $connectionManager;
|
||||||
$this->userRepository = $userRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$authTokens = [];
|
$httpConnection->send(
|
||||||
|
respond_json([
|
||||||
|
'sites' => collect($this->connectionManager->getConnections())
|
||||||
|
->filter(function ($connection) {
|
||||||
|
return get_class($connection) === ControlConnection::class;
|
||||||
|
})
|
||||||
|
->map(function ($site, $siteId) {
|
||||||
|
$site = $site->toArray();
|
||||||
|
$site['id'] = $siteId;
|
||||||
|
|
||||||
$sites = collect($this->connectionManager->getConnections())
|
return $site;
|
||||||
->filter(function ($connection) {
|
})->values(),
|
||||||
return get_class($connection) === ControlConnection::class;
|
])
|
||||||
})
|
);
|
||||||
->map(function ($site, $siteId) use (&$authTokens) {
|
|
||||||
$site = $site->toArray();
|
|
||||||
$site['id'] = $siteId;
|
|
||||||
$authTokens[] = $site['auth_token'];
|
|
||||||
|
|
||||||
return $site;
|
|
||||||
})->values();
|
|
||||||
|
|
||||||
$this->userRepository->getUsersByTokens($authTokens)
|
|
||||||
->then(function ($users) use ($httpConnection, $sites) {
|
|
||||||
$users = collect($users);
|
|
||||||
$sites = collect($sites)->map(function ($site) use ($users) {
|
|
||||||
$site['user'] = $users->firstWhere('auth_token', $site['auth_token']);
|
|
||||||
|
|
||||||
return $site;
|
|
||||||
})->toArray();
|
|
||||||
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json([
|
|
||||||
'sites' => $sites,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Contracts\StatisticsRepository;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Ratchet\ConnectionInterface;
|
|
||||||
|
|
||||||
class GetStatisticsController extends AdminController
|
|
||||||
{
|
|
||||||
protected $keepConnectionOpen = true;
|
|
||||||
|
|
||||||
/** @var StatisticsRepository */
|
|
||||||
protected $statisticsRepository;
|
|
||||||
|
|
||||||
public function __construct(StatisticsRepository $statisticsRepository)
|
|
||||||
{
|
|
||||||
$this->statisticsRepository = $statisticsRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
|
||||||
{
|
|
||||||
$from = today()->subWeek()->toDateString();
|
|
||||||
$until = today()->toDateString();
|
|
||||||
|
|
||||||
$this->statisticsRepository->getStatistics($request->get('from', $from), $request->get('until', $until))
|
|
||||||
->then(function ($statistics) use ($httpConnection) {
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json([
|
|
||||||
'statistics' => $statistics,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
use App\Contracts\UserRepository;
|
|
||||||
use App\Server\Configuration;
|
use App\Server\Configuration;
|
||||||
use App\Server\Connections\TcpControlConnection;
|
use App\Server\Connections\TcpControlConnection;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -11,55 +10,32 @@ use Ratchet\ConnectionInterface;
|
|||||||
|
|
||||||
class GetTcpConnectionsController extends AdminController
|
class GetTcpConnectionsController extends AdminController
|
||||||
{
|
{
|
||||||
protected $keepConnectionOpen = true;
|
|
||||||
|
|
||||||
/** @var ConnectionManager */
|
/** @var ConnectionManager */
|
||||||
protected $connectionManager;
|
protected $connectionManager;
|
||||||
|
|
||||||
/** @var Configuration */
|
/** @var Configuration */
|
||||||
protected $configuration;
|
protected $configuration;
|
||||||
|
|
||||||
/** @var UserRepository */
|
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
||||||
protected $userRepository;
|
|
||||||
|
|
||||||
public function __construct(ConnectionManager $connectionManager, Configuration $configuration, UserRepository $userRepository)
|
|
||||||
{
|
{
|
||||||
$this->connectionManager = $connectionManager;
|
$this->connectionManager = $connectionManager;
|
||||||
$this->userRepository = $userRepository;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$authTokens = [];
|
$httpConnection->send(
|
||||||
$connections = collect($this->connectionManager->getConnections())
|
respond_json([
|
||||||
->filter(function ($connection) {
|
'tcp_connections' => collect($this->connectionManager->getConnections())
|
||||||
return get_class($connection) === TcpControlConnection::class;
|
->filter(function ($connection) {
|
||||||
})
|
return get_class($connection) === TcpControlConnection::class;
|
||||||
->map(function ($site, $siteId) use (&$authTokens) {
|
})
|
||||||
$site = $site->toArray();
|
->map(function ($site, $siteId) {
|
||||||
$site['id'] = $siteId;
|
$site = $site->toArray();
|
||||||
$authTokens[] = $site['auth_token'];
|
$site['id'] = $siteId;
|
||||||
|
|
||||||
return $site;
|
return $site;
|
||||||
})
|
})
|
||||||
->values();
|
->values(),
|
||||||
|
])
|
||||||
$this->userRepository->getUsersByTokens($authTokens)
|
);
|
||||||
->then(function ($users) use ($httpConnection, $connections) {
|
|
||||||
$users = collect($users);
|
|
||||||
$connections = collect($connections)->map(function ($connection) use ($users) {
|
|
||||||
$connection['user'] = $users->firstWhere('auth_token', $connection['auth_token']);
|
|
||||||
|
|
||||||
return $connection;
|
|
||||||
})->toArray();
|
|
||||||
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json([
|
|
||||||
'tcp_connections' => $connections,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,25 +25,10 @@ class GetUserDetailsController extends AdminController
|
|||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$id = $request->get('id');
|
$this->userRepository
|
||||||
|
->getUserById($request->get('id'))
|
||||||
if (! is_numeric($id)) {
|
->then(function ($user) use ($httpConnection, $request) {
|
||||||
$promise = $this->userRepository->getUserByToken($id);
|
$this->subdomainRepository->getSubdomainsByUserId($request->get('id'))
|
||||||
} else {
|
|
||||||
$promise = $this->userRepository->getUserById($id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$promise->then(function ($user) use ($httpConnection) {
|
|
||||||
if (is_null($user)) {
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json([], 404)
|
|
||||||
);
|
|
||||||
|
|
||||||
$httpConnection->close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->subdomainRepository->getSubdomainsByUserId($user['id'])
|
|
||||||
->then(function ($subdomains) use ($httpConnection, $user) {
|
->then(function ($subdomains) use ($httpConnection, $user) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
respond_json([
|
respond_json([
|
||||||
@@ -54,6 +39,6 @@ class GetUserDetailsController extends AdminController
|
|||||||
|
|
||||||
$httpConnection->close();
|
$httpConnection->close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class GetUsersController extends AdminController
|
|||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$this->userRepository
|
$this->userRepository
|
||||||
->paginateUsers($request->get('search', ''), (int) $request->get('perPage', 20), (int) $request->get('page', 1))
|
->paginateUsers(20, (int) $request->get('page', 1))
|
||||||
->then(function ($paginated) use ($httpConnection) {
|
->then(function ($paginated) use ($httpConnection) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
respond_json(['paginated' => $paginated])
|
respond_json(['paginated' => $paginated])
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Server\Http\Controllers\Admin;
|
|||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
use App\Server\Configuration;
|
use App\Server\Configuration;
|
||||||
|
use App\Server\Connections\ControlConnection;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -25,6 +26,17 @@ class ListSitesController extends AdminController
|
|||||||
$sites = $this->getView($httpConnection, 'server.sites.index', [
|
$sites = $this->getView($httpConnection, 'server.sites.index', [
|
||||||
'scheme' => $this->configuration->port() === 443 ? 'https' : 'http',
|
'scheme' => $this->configuration->port() === 443 ? 'https' : 'http',
|
||||||
'configuration' => $this->configuration,
|
'configuration' => $this->configuration,
|
||||||
|
'sites' => collect($this->connectionManager->getConnections())
|
||||||
|
->filter(function ($connection) {
|
||||||
|
return get_class($connection) === ControlConnection::class;
|
||||||
|
})
|
||||||
|
->map(function ($site, $siteId) {
|
||||||
|
$site = $site->toArray();
|
||||||
|
$site['id'] = $siteId;
|
||||||
|
|
||||||
|
return $site;
|
||||||
|
})
|
||||||
|
->values(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace App\Server\Http\Controllers\Admin;
|
|||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
use App\Server\Configuration;
|
use App\Server\Configuration;
|
||||||
|
use App\Server\Connections\TcpControlConnection;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
@@ -25,6 +26,17 @@ class ListTcpConnectionsController extends AdminController
|
|||||||
$sites = $this->getView($httpConnection, 'server.tcp.index', [
|
$sites = $this->getView($httpConnection, 'server.tcp.index', [
|
||||||
'scheme' => $this->configuration->port() === 443 ? 'https' : 'http',
|
'scheme' => $this->configuration->port() === 443 ? 'https' : 'http',
|
||||||
'configuration' => $this->configuration,
|
'configuration' => $this->configuration,
|
||||||
|
'connections' => collect($this->connectionManager->getConnections())
|
||||||
|
->filter(function ($connection) {
|
||||||
|
return get_class($connection) === TcpControlConnection::class;
|
||||||
|
})
|
||||||
|
->map(function ($connection, $connectionId) {
|
||||||
|
$connection = $connection->toArray();
|
||||||
|
$connection['id'] = $connectionId;
|
||||||
|
|
||||||
|
return $connection;
|
||||||
|
})
|
||||||
|
->values(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class ListUsersController extends AdminController
|
|||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$this->userRepository
|
$this->userRepository
|
||||||
->paginateUsers($request->get('search', ''), 20, (int) $request->get('page', 1))
|
->paginateUsers(20, (int) $request->get('page', 1))
|
||||||
->then(function ($paginated) use ($httpConnection) {
|
->then(function ($paginated) use ($httpConnection) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
respond_html($this->getView($httpConnection, 'server.users.index', ['paginated' => $paginated]))
|
respond_html($this->getView($httpConnection, 'server.users.index', ['paginated' => $paginated]))
|
||||||
|
|||||||
@@ -2,16 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
class RedirectToUsersController extends AdminController
|
class RedirectToUsersController extends AdminController
|
||||||
{
|
{
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$httpConnection->send(Message::toString(new Response(301, [
|
$httpConnection->send(str(new Response(301, [
|
||||||
'Location' => '/sites',
|
'Location' => '/sites',
|
||||||
])));
|
])));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
|
||||||
|
|
||||||
use App\Contracts\DomainRepository;
|
|
||||||
use App\Contracts\UserRepository;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use Illuminate\Support\Facades\Validator;
|
|
||||||
use Ratchet\ConnectionInterface;
|
|
||||||
|
|
||||||
class StoreDomainController extends AdminController
|
|
||||||
{
|
|
||||||
protected $keepConnectionOpen = true;
|
|
||||||
|
|
||||||
/** @var DomainRepository */
|
|
||||||
protected $domainRepository;
|
|
||||||
|
|
||||||
/** @var UserRepository */
|
|
||||||
protected $userRepository;
|
|
||||||
|
|
||||||
public function __construct(UserRepository $userRepository, DomainRepository $domainRepository)
|
|
||||||
{
|
|
||||||
$this->userRepository = $userRepository;
|
|
||||||
$this->domainRepository = $domainRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
|
||||||
{
|
|
||||||
$validator = Validator::make($request->all(), [
|
|
||||||
'domain' => 'required',
|
|
||||||
], [
|
|
||||||
'required' => 'The :attribute field is required.',
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($validator->fails()) {
|
|
||||||
$httpConnection->send(respond_json(['errors' => $validator->getMessageBag()], 401));
|
|
||||||
$httpConnection->close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->userRepository
|
|
||||||
->getUserByToken($request->get('auth_token', ''))
|
|
||||||
->then(function ($user) use ($httpConnection, $request) {
|
|
||||||
if (is_null($user)) {
|
|
||||||
$httpConnection->send(respond_json(['error' => 'The user does not exist'], 404));
|
|
||||||
$httpConnection->close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($user['can_specify_domains'] === 0) {
|
|
||||||
$httpConnection->send(respond_json(['error' => 'The user is not allowed to reserve custom domains.'], 401));
|
|
||||||
$httpConnection->close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$insertData = [
|
|
||||||
'user_id' => $user['id'],
|
|
||||||
'domain' => $request->get('domain'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$this->domainRepository
|
|
||||||
->storeDomain($insertData)
|
|
||||||
->then(function ($domain) use ($httpConnection) {
|
|
||||||
$httpConnection->send(respond_json(['domain' => $domain], 200));
|
|
||||||
$httpConnection->close();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -31,14 +31,6 @@ class StoreSettingsController extends AdminController
|
|||||||
|
|
||||||
config()->set('expose.admin.messages.message_of_the_day', Arr::get($messages, 'message_of_the_day'));
|
config()->set('expose.admin.messages.message_of_the_day', Arr::get($messages, 'message_of_the_day'));
|
||||||
|
|
||||||
config()->set('expose.admin.messages.custom_subdomain_unauthorized', Arr::get($messages, 'custom_subdomain_unauthorized'));
|
|
||||||
|
|
||||||
config()->set('expose.admin.messages.no_free_tcp_port_available', Arr::get($messages, 'no_free_tcp_port_available'));
|
|
||||||
|
|
||||||
config()->set('expose.admin.messages.tcp_port_sharing_unauthorized', Arr::get($messages, 'tcp_port_sharing_unauthorized'));
|
|
||||||
|
|
||||||
config()->set('expose.admin.messages.tcp_port_sharing_disabled', Arr::get($messages, 'tcp_port_sharing_disabled'));
|
|
||||||
|
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
respond_json([
|
respond_json([
|
||||||
'configuration' => $this->configuration,
|
'configuration' => $this->configuration,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ namespace App\Server\Http\Controllers\Admin;
|
|||||||
|
|
||||||
use App\Contracts\SubdomainRepository;
|
use App\Contracts\SubdomainRepository;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use App\Server\Configuration;
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
@@ -19,14 +18,10 @@ class StoreSubdomainController extends AdminController
|
|||||||
/** @var UserRepository */
|
/** @var UserRepository */
|
||||||
protected $userRepository;
|
protected $userRepository;
|
||||||
|
|
||||||
/** @var Configuration */
|
public function __construct(UserRepository $userRepository, SubdomainRepository $subdomainRepository)
|
||||||
protected $configuration;
|
|
||||||
|
|
||||||
public function __construct(UserRepository $userRepository, SubdomainRepository $subdomainRepository, Configuration $configuration)
|
|
||||||
{
|
{
|
||||||
$this->userRepository = $userRepository;
|
$this->userRepository = $userRepository;
|
||||||
$this->subdomainRepository = $subdomainRepository;
|
$this->subdomainRepository = $subdomainRepository;
|
||||||
$this->configuration = $configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
@@ -44,8 +39,7 @@ class StoreSubdomainController extends AdminController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->userRepository
|
$this->userRepository->getUserByToken($request->get('auth_token', ''))
|
||||||
->getUserByToken($request->get('auth_token', ''))
|
|
||||||
->then(function ($user) use ($httpConnection, $request) {
|
->then(function ($user) use ($httpConnection, $request) {
|
||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
$httpConnection->send(respond_json(['error' => 'The user does not exist'], 404));
|
$httpConnection->send(respond_json(['error' => 'The user does not exist'], 404));
|
||||||
@@ -61,22 +55,20 @@ class StoreSubdomainController extends AdminController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in_array($request->get('subdomain'), config('expose.admin.reserved_subdomains', []))) {
|
|
||||||
$httpConnection->send(respond_json(['error' => 'The subdomain is already taken.'], 422));
|
|
||||||
$httpConnection->close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$insertData = [
|
$insertData = [
|
||||||
'user_id' => $user['id'],
|
'user_id' => $user['id'],
|
||||||
'subdomain' => $request->get('subdomain'),
|
'subdomain' => $request->get('subdomain'),
|
||||||
'domain' => $request->get('domain', $this->configuration->hostname()),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->subdomainRepository
|
$this->subdomainRepository
|
||||||
->storeSubdomain($insertData)
|
->storeSubdomain($insertData)
|
||||||
->then(function ($subdomain) use ($httpConnection) {
|
->then(function ($subdomain) use ($httpConnection) {
|
||||||
|
if (is_null($subdomain)) {
|
||||||
|
$httpConnection->send(respond_json(['error' => 'The subdomain is already taken.'], 422));
|
||||||
|
$httpConnection->close();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$httpConnection->send(respond_json(['subdomain' => $subdomain], 200));
|
$httpConnection->send(respond_json(['subdomain' => $subdomain], 200));
|
||||||
$httpConnection->close();
|
$httpConnection->close();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
@@ -37,11 +38,9 @@ class StoreUsersController extends AdminController
|
|||||||
|
|
||||||
$insertData = [
|
$insertData = [
|
||||||
'name' => $request->get('name'),
|
'name' => $request->get('name'),
|
||||||
'auth_token' => $request->get('token', (string) Str::uuid()),
|
'auth_token' => (string) Str::uuid(),
|
||||||
'can_specify_subdomains' => (int) $request->get('can_specify_subdomains'),
|
'can_specify_subdomains' => (int) $request->get('can_specify_subdomains'),
|
||||||
'can_specify_domains' => (int) $request->get('can_specify_domains'),
|
|
||||||
'can_share_tcp_ports' => (int) $request->get('can_share_tcp_ports'),
|
'can_share_tcp_ports' => (int) $request->get('can_share_tcp_ports'),
|
||||||
'max_connections' => (int) $request->get('max_connections'),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->userRepository
|
$this->userRepository
|
||||||
|
|||||||
@@ -3,18 +3,14 @@
|
|||||||
namespace App\Server\Http\Controllers;
|
namespace App\Server\Http\Controllers;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
use App\Contracts\DomainRepository;
|
|
||||||
use App\Contracts\SubdomainRepository;
|
use App\Contracts\SubdomainRepository;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use App\Http\QueryParameters;
|
use App\Http\QueryParameters;
|
||||||
use App\Server\Configuration;
|
|
||||||
use App\Server\Exceptions\NoFreePortAvailable;
|
use App\Server\Exceptions\NoFreePortAvailable;
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
use Ratchet\WebSocket\MessageComponentInterface;
|
use Ratchet\WebSocket\MessageComponentInterface;
|
||||||
use React\Promise\Deferred;
|
use React\Promise\Deferred;
|
||||||
use React\Promise\PromiseInterface;
|
use React\Promise\PromiseInterface;
|
||||||
use function React\Promise\reject;
|
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
class ControlMessageController implements MessageComponentInterface
|
class ControlMessageController implements MessageComponentInterface
|
||||||
@@ -28,19 +24,11 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
/** @var SubdomainRepository */
|
/** @var SubdomainRepository */
|
||||||
protected $subdomainRepository;
|
protected $subdomainRepository;
|
||||||
|
|
||||||
/** @var DomainRepository */
|
public function __construct(ConnectionManager $connectionManager, UserRepository $userRepository, SubdomainRepository $subdomainRepository)
|
||||||
protected $domainRepository;
|
|
||||||
|
|
||||||
/** @var Configuration */
|
|
||||||
protected $configuration;
|
|
||||||
|
|
||||||
public function __construct(ConnectionManager $connectionManager, UserRepository $userRepository, SubdomainRepository $subdomainRepository, Configuration $configuration, DomainRepository $domainRepository)
|
|
||||||
{
|
{
|
||||||
$this->connectionManager = $connectionManager;
|
$this->connectionManager = $connectionManager;
|
||||||
$this->userRepository = $userRepository;
|
$this->userRepository = $userRepository;
|
||||||
$this->subdomainRepository = $subdomainRepository;
|
$this->subdomainRepository = $subdomainRepository;
|
||||||
$this->domainRepository = $domainRepository;
|
|
||||||
$this->configuration = $configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,42 +85,7 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
|
|
||||||
protected function authenticate(ConnectionInterface $connection, $data)
|
protected function authenticate(ConnectionInterface $connection, $data)
|
||||||
{
|
{
|
||||||
if (! isset($data->subdomain)) {
|
|
||||||
$data->subdomain = null;
|
|
||||||
}
|
|
||||||
if (! isset($data->type)) {
|
|
||||||
$data->type = 'http';
|
|
||||||
}
|
|
||||||
if (! isset($data->server_host) || is_null($data->server_host)) {
|
|
||||||
$data->server_host = $this->configuration->hostname();
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->verifyAuthToken($connection)
|
$this->verifyAuthToken($connection)
|
||||||
->then(function ($user) use ($connection) {
|
|
||||||
$maximumConnectionCount = config('expose.admin.maximum_open_connections_per_user', 0);
|
|
||||||
|
|
||||||
if (is_null($user)) {
|
|
||||||
$connectionCount = count($this->connectionManager->findControlConnectionsForIp($connection->remoteAddress));
|
|
||||||
} else {
|
|
||||||
$maximumConnectionCount = Arr::get($user, 'max_connections', $maximumConnectionCount);
|
|
||||||
|
|
||||||
$connectionCount = count($this->connectionManager->findControlConnectionsForAuthToken($user['auth_token']));
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($maximumConnectionCount > 0 && $connectionCount + 1 > $maximumConnectionCount) {
|
|
||||||
$connection->send(json_encode([
|
|
||||||
'event' => 'authenticationFailed',
|
|
||||||
'data' => [
|
|
||||||
'message' => config('expose.admin.messages.maximum_connection_count'),
|
|
||||||
],
|
|
||||||
]));
|
|
||||||
$connection->close();
|
|
||||||
|
|
||||||
reject(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $user;
|
|
||||||
})
|
|
||||||
->then(function ($user) use ($connection, $data) {
|
->then(function ($user) use ($connection, $data) {
|
||||||
if ($data->type === 'http') {
|
if ($data->type === 'http') {
|
||||||
$this->handleHttpConnection($connection, $data, $user);
|
$this->handleHttpConnection($connection, $data, $user);
|
||||||
@@ -150,57 +103,28 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveConnectionMessage($connectionInfo, $user)
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$connectionMessageResolver = config('expose.admin.messages.resolve_connection_message')($connectionInfo, $user);
|
|
||||||
|
|
||||||
if ($connectionMessageResolver instanceof PromiseInterface) {
|
|
||||||
$connectionMessageResolver->then(function ($connectionMessage) use ($connectionInfo, $deferred) {
|
|
||||||
$connectionInfo->message = $connectionMessage;
|
|
||||||
$deferred->resolve($connectionInfo);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$connectionInfo->message = $connectionMessageResolver;
|
|
||||||
|
|
||||||
return \React\Promise\resolve($connectionInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function handleHttpConnection(ConnectionInterface $connection, $data, $user = null)
|
protected function handleHttpConnection(ConnectionInterface $connection, $data, $user = null)
|
||||||
{
|
{
|
||||||
$this->hasValidDomain($connection, $data->server_host, $user)
|
$this->hasValidSubdomain($connection, $data->subdomain, $user)->then(function ($subdomain) use ($data, $connection) {
|
||||||
->then(function () use ($connection, $data, $user) {
|
if ($subdomain === false) {
|
||||||
return $this->hasValidSubdomain($connection, $data->subdomain, $user, $data->server_host);
|
return;
|
||||||
})
|
}
|
||||||
->then(function ($subdomain) use ($data, $connection, $user) {
|
|
||||||
if ($subdomain === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data->subdomain = $subdomain;
|
$data->subdomain = $subdomain;
|
||||||
|
|
||||||
$connectionInfo = $this->connectionManager->storeConnection($data->host, $data->subdomain, $data->server_host, $connection);
|
$connectionInfo = $this->connectionManager->storeConnection($data->host, $data->subdomain, $connection);
|
||||||
|
|
||||||
$this->connectionManager->limitConnectionLength($connectionInfo, config('expose.admin.maximum_connection_length'));
|
$this->connectionManager->limitConnectionLength($connectionInfo, config('expose.admin.maximum_connection_length'));
|
||||||
|
|
||||||
return $this->resolveConnectionMessage($connectionInfo, $user);
|
$connection->send(json_encode([
|
||||||
})
|
'event' => 'authenticated',
|
||||||
->then(function ($connectionInfo) use ($connection, $user) {
|
'data' => [
|
||||||
$connection->send(json_encode([
|
'message' => config('expose.admin.messages.message_of_the_day'),
|
||||||
'event' => 'authenticated',
|
'subdomain' => $connectionInfo->subdomain,
|
||||||
'data' => [
|
'client_id' => $connectionInfo->client_id,
|
||||||
'message' => $connectionInfo->message,
|
],
|
||||||
'subdomain' => $connectionInfo->subdomain,
|
]));
|
||||||
'server_host' => $connectionInfo->serverHost,
|
});
|
||||||
'user' => $user,
|
|
||||||
'client_id' => $connectionInfo->client_id,
|
|
||||||
],
|
|
||||||
]));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function handleTcpConnection(ConnectionInterface $connection, $data, $user = null)
|
protected function handleTcpConnection(ConnectionInterface $connection, $data, $user = null)
|
||||||
@@ -226,8 +150,7 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
$connection->send(json_encode([
|
$connection->send(json_encode([
|
||||||
'event' => 'authenticated',
|
'event' => 'authenticated',
|
||||||
'data' => [
|
'data' => [
|
||||||
'message' => config('expose.admin.messages.resolve_connection_message')($connectionInfo, $user),
|
'message' => config('expose.admin.messages.message_of_the_day'),
|
||||||
'user' => $user,
|
|
||||||
'port' => $connectionInfo->port,
|
'port' => $connectionInfo->port,
|
||||||
'shared_port' => $connectionInfo->shared_port,
|
'shared_port' => $connectionInfo->shared_port,
|
||||||
'client_id' => $connectionInfo->client_id,
|
'client_id' => $connectionInfo->client_id,
|
||||||
@@ -282,60 +205,21 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
if (is_null($user)) {
|
if (is_null($user)) {
|
||||||
$deferred->reject();
|
$deferred->reject();
|
||||||
} else {
|
} else {
|
||||||
$this->userRepository
|
$deferred->resolve($user);
|
||||||
->updateLastSharedAt($user['id'])
|
|
||||||
->then(function () use ($deferred, $user) {
|
|
||||||
$deferred->resolve($user);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function hasValidDomain(ConnectionInterface $connection, ?string $serverHost, ?array $user): PromiseInterface
|
protected function hasValidSubdomain(ConnectionInterface $connection, ?string $subdomain, ?array $user): PromiseInterface
|
||||||
{
|
|
||||||
if (! is_null($user) && $serverHost !== $this->configuration->hostname()) {
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->domainRepository
|
|
||||||
->getDomainsByUserId($user['id'])
|
|
||||||
->then(function ($domains) use ($connection, $deferred, $serverHost) {
|
|
||||||
$userDomain = collect($domains)->first(function ($domain) use ($serverHost) {
|
|
||||||
return strtolower($domain['domain']) === strtolower($serverHost);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (is_null($userDomain)) {
|
|
||||||
$connection->send(json_encode([
|
|
||||||
'event' => 'authenticationFailed',
|
|
||||||
'data' => [
|
|
||||||
'message' => config('expose.admin.messages.custom_domain_unauthorized').PHP_EOL,
|
|
||||||
],
|
|
||||||
]));
|
|
||||||
$connection->close();
|
|
||||||
|
|
||||||
$deferred->reject(null);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$deferred->resolve(null);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
} else {
|
|
||||||
return \React\Promise\resolve(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function hasValidSubdomain(ConnectionInterface $connection, ?string $subdomain, ?array $user, string $serverHost): PromiseInterface
|
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Check if the user can specify a custom subdomain in the first place.
|
* Check if the user can specify a custom subdomain in the first place.
|
||||||
*/
|
*/
|
||||||
if (! is_null($user) && $user['can_specify_subdomains'] === 0 && ! is_null($subdomain)) {
|
if (! is_null($user) && $user['can_specify_subdomains'] === 0 && ! is_null($subdomain)) {
|
||||||
$connection->send(json_encode([
|
$connection->send(json_encode([
|
||||||
'event' => 'error',
|
'event' => 'info',
|
||||||
'data' => [
|
'data' => [
|
||||||
'message' => config('expose.admin.messages.custom_subdomain_unauthorized').PHP_EOL,
|
'message' => config('expose.admin.messages.custom_subdomain_unauthorized').PHP_EOL,
|
||||||
],
|
],
|
||||||
@@ -348,14 +232,10 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
* Check if the given subdomain is reserved for a different user.
|
* Check if the given subdomain is reserved for a different user.
|
||||||
*/
|
*/
|
||||||
if (! is_null($subdomain)) {
|
if (! is_null($subdomain)) {
|
||||||
return $this->subdomainRepository->getSubdomainsByNameAndDomain($subdomain, $serverHost)
|
return $this->subdomainRepository->getSubdomainByName($subdomain)
|
||||||
->then(function ($foundSubdomains) use ($connection, $subdomain, $user, $serverHost) {
|
->then(function ($foundSubdomain) use ($connection, $subdomain, $user) {
|
||||||
$ownSubdomain = collect($foundSubdomains)->first(function ($subdomain) use ($user) {
|
if (! is_null($foundSubdomain) && ! is_null($user) && $foundSubdomain['user_id'] !== $user['id']) {
|
||||||
return $subdomain['user_id'] === $user['id'];
|
$message = config('expose.admin.messages.subdomain_reserved');
|
||||||
});
|
|
||||||
|
|
||||||
if (count($foundSubdomains) > 0 && ! is_null($user) && is_null($ownSubdomain)) {
|
|
||||||
$message = config('expose.admin.messages.subdomain_reserved', '');
|
|
||||||
$message = str_replace(':subdomain', $subdomain, $message);
|
$message = str_replace(':subdomain', $subdomain, $message);
|
||||||
|
|
||||||
$connection->send(json_encode([
|
$connection->send(json_encode([
|
||||||
@@ -369,9 +249,9 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
return \React\Promise\resolve(false);
|
return \React\Promise\resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$controlConnection = $this->connectionManager->findControlConnectionForSubdomainAndServerHost($subdomain, $serverHost);
|
$controlConnection = $this->connectionManager->findControlConnectionForSubdomain($subdomain);
|
||||||
|
|
||||||
if (! is_null($controlConnection) || $subdomain === config('expose.admin.subdomain') || in_array($subdomain, config('expose.admin.reserved_subdomains', []))) {
|
if (! is_null($controlConnection) || $subdomain === config('expose.admin.subdomain')) {
|
||||||
$message = config('expose.admin.messages.subdomain_taken');
|
$message = config('expose.admin.messages.subdomain_taken');
|
||||||
$message = str_replace(':subdomain', $subdomain, $message);
|
$message = str_replace(':subdomain', $subdomain, $message);
|
||||||
|
|
||||||
@@ -395,23 +275,11 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
|
|
||||||
protected function canShareTcpPorts(ConnectionInterface $connection, $data, $user)
|
protected function canShareTcpPorts(ConnectionInterface $connection, $data, $user)
|
||||||
{
|
{
|
||||||
if (! config('expose.admin.allow_tcp_port_sharing', true)) {
|
|
||||||
$connection->send(json_encode([
|
|
||||||
'event' => 'authenticationFailed',
|
|
||||||
'data' => [
|
|
||||||
'message' => config('expose.admin.messages.tcp_port_sharing_disabled'),
|
|
||||||
],
|
|
||||||
]));
|
|
||||||
$connection->close();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_null($user) && $user['can_share_tcp_ports'] === 0) {
|
if (! is_null($user) && $user['can_share_tcp_ports'] === 0) {
|
||||||
$connection->send(json_encode([
|
$connection->send(json_encode([
|
||||||
'event' => 'authenticationFailed',
|
'event' => 'authenticationFailed',
|
||||||
'data' => [
|
'data' => [
|
||||||
'message' => config('expose.admin.messages.tcp_port_sharing_unauthorized'),
|
'message' => config('expose.admin.messages.custom_subdomain_unauthorized'),
|
||||||
],
|
],
|
||||||
]));
|
]));
|
||||||
$connection->close();
|
$connection->close();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace App\Server\Http\Controllers;
|
namespace App\Server\Http\Controllers;
|
||||||
|
|
||||||
use App\Contracts\ConnectionManager;
|
use App\Contracts\ConnectionManager;
|
||||||
use App\Contracts\StatisticsCollector;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Server\Configuration;
|
use App\Server\Configuration;
|
||||||
use App\Server\Connections\ControlConnection;
|
use App\Server\Connections\ControlConnection;
|
||||||
@@ -28,20 +27,15 @@ class TunnelMessageController extends Controller
|
|||||||
|
|
||||||
protected $modifiers = [];
|
protected $modifiers = [];
|
||||||
|
|
||||||
/** @var StatisticsCollector */
|
public function __construct(ConnectionManager $connectionManager, Configuration $configuration)
|
||||||
protected $statisticsCollector;
|
|
||||||
|
|
||||||
public function __construct(ConnectionManager $connectionManager, StatisticsCollector $statisticsCollector, Configuration $configuration)
|
|
||||||
{
|
{
|
||||||
$this->connectionManager = $connectionManager;
|
$this->connectionManager = $connectionManager;
|
||||||
$this->configuration = $configuration;
|
$this->configuration = $configuration;
|
||||||
$this->statisticsCollector = $statisticsCollector;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$subdomain = $this->detectSubdomain($request);
|
$subdomain = $this->detectSubdomain($request);
|
||||||
$serverHost = $this->detectServerHost($request);
|
|
||||||
|
|
||||||
if (is_null($subdomain)) {
|
if (is_null($subdomain)) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
@@ -52,7 +46,7 @@ class TunnelMessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$controlConnection = $this->connectionManager->findControlConnectionForSubdomainAndServerHost($subdomain, $serverHost);
|
$controlConnection = $this->connectionManager->findControlConnectionForSubdomain($subdomain);
|
||||||
|
|
||||||
if (is_null($controlConnection)) {
|
if (is_null($controlConnection)) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
@@ -63,23 +57,14 @@ class TunnelMessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->statisticsCollector->incomingRequest();
|
|
||||||
|
|
||||||
$this->sendRequestToClient($request, $controlConnection, $httpConnection);
|
$this->sendRequestToClient($request, $controlConnection, $httpConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function detectSubdomain(Request $request): ?string
|
protected function detectSubdomain(Request $request): ?string
|
||||||
{
|
{
|
||||||
$serverHost = $this->detectServerHost($request);
|
$subdomain = Str::before($request->getHost(), '.'.$this->configuration->hostname());
|
||||||
|
|
||||||
$subdomain = Str::before($request->header('Host'), '.'.$serverHost);
|
return $subdomain === $request->getHost() ? null : $subdomain;
|
||||||
|
|
||||||
return $subdomain === $request->header('Host') ? null : $subdomain;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function detectServerHost(Request $request): ?string
|
|
||||||
{
|
|
||||||
return Str::before(Str::after($request->header('Host'), '.'), ':');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sendRequestToClient(Request $request, ControlConnection $controlConnection, ConnectionInterface $httpConnection)
|
protected function sendRequestToClient(Request $request, ControlConnection $controlConnection, ConnectionInterface $httpConnection)
|
||||||
@@ -122,15 +107,19 @@ class TunnelMessageController extends Controller
|
|||||||
{
|
{
|
||||||
$request::setTrustedProxies([$controlConnection->socket->remoteAddress, '127.0.0.1'], Request::HEADER_X_FORWARDED_ALL);
|
$request::setTrustedProxies([$controlConnection->socket->remoteAddress, '127.0.0.1'], Request::HEADER_X_FORWARDED_ALL);
|
||||||
|
|
||||||
$host = $controlConnection->serverHost;
|
$host = $this->configuration->hostname();
|
||||||
|
|
||||||
if (! $request->isSecure()) {
|
if (! $request->isSecure()) {
|
||||||
$host .= ":{$this->configuration->port()}";
|
$host .= ":{$this->configuration->port()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->headers->set('Host', $controlConnection->host);
|
$exposeUrl = parse_url($controlConnection->host);
|
||||||
|
|
||||||
|
$request->headers->set('Host', "{$controlConnection->subdomain}.{$host}");
|
||||||
$request->headers->set('X-Forwarded-Proto', $request->isSecure() ? 'https' : 'http');
|
$request->headers->set('X-Forwarded-Proto', $request->isSecure() ? 'https' : 'http');
|
||||||
$request->headers->set('X-Expose-Request-ID', uniqid());
|
$request->headers->set('X-Expose-Request-ID', sha1(uniqid('', true)));
|
||||||
|
$request->headers->set('X-Expose-Host', sprintf('%s:%s', $exposeUrl['host'], $exposeUrl['port']));
|
||||||
|
$request->headers->set('X-Expose-Proto', $exposeUrl['scheme']);
|
||||||
$request->headers->set('Upgrade-Insecure-Requests', 1);
|
$request->headers->set('Upgrade-Insecure-Requests', 1);
|
||||||
$request->headers->set('X-Exposed-By', config('app.name').' '.config('app.version'));
|
$request->headers->set('X-Exposed-By', config('app.name').' '.config('app.version'));
|
||||||
$request->headers->set('X-Original-Host', "{$controlConnection->subdomain}.{$host}");
|
$request->headers->set('X-Original-Host', "{$controlConnection->subdomain}.{$host}");
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ class Router implements HttpServerInterface
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*
|
|
||||||
* @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
|
* @throws \UnexpectedValueException If a controller is not \Ratchet\Http\HttpServerInterface
|
||||||
*/
|
*/
|
||||||
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)
|
public function onOpen(ConnectionInterface $conn, RequestInterface $request = null)
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\LoggerRepository;
|
|
||||||
|
|
||||||
use App\Contracts\LoggerRepository;
|
|
||||||
use App\Contracts\UserRepository;
|
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
|
||||||
use Clue\React\SQLite\Result;
|
|
||||||
use React\Promise\Deferred;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
class DatabaseLogger implements LoggerRepository
|
|
||||||
{
|
|
||||||
/** @var DatabaseInterface */
|
|
||||||
protected $database;
|
|
||||||
|
|
||||||
public function __construct(DatabaseInterface $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logSubdomain($authToken, $subdomain)
|
|
||||||
{
|
|
||||||
app(UserRepository::class)->getUserByToken($authToken)
|
|
||||||
->then(function ($user) use ($subdomain) {
|
|
||||||
$this->database->query("
|
|
||||||
INSERT INTO logs (user_id, subdomain, created_at)
|
|
||||||
VALUES (:user_id, :subdomain, DATETIME('now'))
|
|
||||||
", [
|
|
||||||
'user_id' => $user['id'],
|
|
||||||
'subdomain' => $subdomain,
|
|
||||||
])->then(function () {
|
|
||||||
$this->cleanOldLogs();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function cleanOldLogs()
|
|
||||||
{
|
|
||||||
$this->database->query("DELETE FROM logs WHERE created_at < date('now', '-30 day')");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLogsBySubdomain($subdomain): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('
|
|
||||||
SELECT
|
|
||||||
logs.id AS log_id,
|
|
||||||
logs.subdomain,
|
|
||||||
users.*
|
|
||||||
FROM logs
|
|
||||||
INNER JOIN users
|
|
||||||
ON users.id = logs.user_id
|
|
||||||
WHERE logs.subdomain = :subdomain', ['subdomain' => $subdomain])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLogs(): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('
|
|
||||||
SELECT
|
|
||||||
logs.id AS log_id,
|
|
||||||
logs.subdomain,
|
|
||||||
users.*
|
|
||||||
FROM logs
|
|
||||||
INNER JOIN users
|
|
||||||
ON users.id = logs.user_id')
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\LoggerRepository;
|
|
||||||
|
|
||||||
use App\Contracts\LoggerRepository;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
class NullLogger implements LoggerRepository
|
|
||||||
{
|
|
||||||
public function logSubdomain($authToken, $subdomain)
|
|
||||||
{
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLogsBySubdomain($subdomain): PromiseInterface
|
|
||||||
{
|
|
||||||
return \React\Promise\resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getLogs(): PromiseInterface
|
|
||||||
{
|
|
||||||
return \React\Promise\resolve([]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\StatisticsCollector;
|
|
||||||
|
|
||||||
use App\Contracts\StatisticsCollector;
|
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
|
||||||
|
|
||||||
class DatabaseStatisticsCollector implements StatisticsCollector
|
|
||||||
{
|
|
||||||
/** @var DatabaseInterface */
|
|
||||||
protected $database;
|
|
||||||
|
|
||||||
/** @var array */
|
|
||||||
protected $sharedPorts = [];
|
|
||||||
|
|
||||||
/** @var array */
|
|
||||||
protected $sharedSites = [];
|
|
||||||
|
|
||||||
/** @var int */
|
|
||||||
protected $requests = 0;
|
|
||||||
|
|
||||||
public function __construct(DatabaseInterface $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush the stored statistics.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function flush()
|
|
||||||
{
|
|
||||||
$this->sharedPorts = [];
|
|
||||||
$this->sharedSites = [];
|
|
||||||
$this->requests = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function siteShared($authToken = null)
|
|
||||||
{
|
|
||||||
if (! $this->shouldCollectStatistics()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! isset($this->sharedSites[$authToken])) {
|
|
||||||
$this->sharedSites[$authToken] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->sharedSites[$authToken]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function portShared($authToken = null)
|
|
||||||
{
|
|
||||||
if (! $this->shouldCollectStatistics()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! isset($this->sharedPorts[$authToken])) {
|
|
||||||
$this->sharedPorts[$authToken] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->sharedPorts[$authToken]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function incomingRequest()
|
|
||||||
{
|
|
||||||
if (! $this->shouldCollectStatistics()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->requests++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function save()
|
|
||||||
{
|
|
||||||
$sharedSites = 0;
|
|
||||||
collect($this->sharedSites)->map(function ($numSites) use (&$sharedSites) {
|
|
||||||
$sharedSites += $numSites;
|
|
||||||
});
|
|
||||||
|
|
||||||
$sharedPorts = 0;
|
|
||||||
collect($this->sharedPorts)->map(function ($numPorts) use (&$sharedPorts) {
|
|
||||||
$sharedPorts += $numPorts;
|
|
||||||
});
|
|
||||||
|
|
||||||
$this->database->query('
|
|
||||||
INSERT INTO statistics (timestamp, shared_sites, shared_ports, unique_shared_sites, unique_shared_ports, incoming_requests)
|
|
||||||
VALUES (:timestamp, :shared_sites, :shared_ports, :unique_shared_sites, :unique_shared_ports, :incoming_requests)
|
|
||||||
', [
|
|
||||||
'timestamp' => today()->toDateString(),
|
|
||||||
'shared_sites' => $sharedSites,
|
|
||||||
'shared_ports' => $sharedPorts,
|
|
||||||
'unique_shared_sites' => count($this->sharedSites),
|
|
||||||
'unique_shared_ports' => count($this->sharedPorts),
|
|
||||||
'incoming_requests' => $this->requests,
|
|
||||||
])
|
|
||||||
->then(function () {
|
|
||||||
$this->flush();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public function shouldCollectStatistics(): bool
|
|
||||||
{
|
|
||||||
return config('expose.admin.statistics.enable_statistics', true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\StatisticsRepository;
|
|
||||||
|
|
||||||
use App\Contracts\StatisticsRepository;
|
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
|
||||||
use Clue\React\SQLite\Result;
|
|
||||||
use React\Promise\Deferred;
|
|
||||||
use React\Promise\PromiseInterface;
|
|
||||||
|
|
||||||
class DatabaseStatisticsRepository implements StatisticsRepository
|
|
||||||
{
|
|
||||||
/** @var DatabaseInterface */
|
|
||||||
protected $database;
|
|
||||||
|
|
||||||
public function __construct(DatabaseInterface $database)
|
|
||||||
{
|
|
||||||
$this->database = $database;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getStatistics($from, $until): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT
|
|
||||||
timestamp,
|
|
||||||
SUM(shared_sites) as shared_sites,
|
|
||||||
SUM(shared_ports) as shared_ports,
|
|
||||||
SUM(unique_shared_sites) as unique_shared_sites,
|
|
||||||
SUM(unique_shared_ports) as unique_shared_ports,
|
|
||||||
SUM(incoming_requests) as incoming_requests
|
|
||||||
FROM statistics
|
|
||||||
WHERE
|
|
||||||
`timestamp` >= "'.$from.'" AND `timestamp` <= "'.$until.'"')
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -57,38 +57,6 @@ class DatabaseSubdomainRepository implements SubdomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSubdomainByNameAndDomain(string $name, string $domain): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM subdomains WHERE subdomain = :name AND domain = :domain', [
|
|
||||||
'name' => $name,
|
|
||||||
'domain' => $domain,
|
|
||||||
])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows[0] ?? null);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSubdomainsByNameAndDomain(string $name, string $domain): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query('SELECT * FROM subdomains WHERE subdomain = :name AND domain = :domain', [
|
|
||||||
'name' => $name,
|
|
||||||
'domain' => $domain,
|
|
||||||
])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSubdomainsByUserId($id): PromiseInterface
|
public function getSubdomainsByUserId($id): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
@@ -108,14 +76,23 @@ class DatabaseSubdomainRepository implements SubdomainRepository
|
|||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database->query("
|
$this->getSubdomainByName($data['subdomain'])
|
||||||
INSERT INTO subdomains (user_id, subdomain, domain, created_at)
|
->then(function ($registeredSubdomain) use ($data, $deferred) {
|
||||||
VALUES (:user_id, :subdomain, :domain, DATETIME('now'))
|
if (! is_null($registeredSubdomain)) {
|
||||||
", $data)
|
$deferred->resolve(null);
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$this->database->query('SELECT * FROM subdomains WHERE id = :id', ['id' => $result->insertId])
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->database->query("
|
||||||
|
INSERT INTO subdomains (user_id, subdomain, created_at)
|
||||||
|
VALUES (:user_id, :subdomain, DATETIME('now'))
|
||||||
|
", $data)
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$deferred->resolve($result->rows[0]);
|
$this->database->query('SELECT * FROM subdomains WHERE id = :id', ['id' => $result->insertId])
|
||||||
|
->then(function (Result $result) use ($deferred) {
|
||||||
|
$deferred->resolve($result->rows[0]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -142,7 +119,7 @@ class DatabaseSubdomainRepository implements SubdomainRepository
|
|||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database->query('DELETE FROM subdomains WHERE (id = :id OR subdomain = :id) AND user_id = :user_id', [
|
$this->database->query('DELETE FROM subdomains WHERE id = :id AND user_id = :user_id', [
|
||||||
'id' => $subdomainId,
|
'id' => $subdomainId,
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Server\Support;
|
|
||||||
|
|
||||||
use App\Server\Connections\ControlConnection;
|
|
||||||
use Clue\React\Buzz\Browser;
|
|
||||||
use Psr\Http\Message\ResponseInterface;
|
|
||||||
|
|
||||||
class RetrieveWelcomeMessageFromApi
|
|
||||||
{
|
|
||||||
/** @var Browser */
|
|
||||||
protected $browser;
|
|
||||||
|
|
||||||
/** @var string */
|
|
||||||
protected $url;
|
|
||||||
|
|
||||||
public function __construct(Browser $browser)
|
|
||||||
{
|
|
||||||
$this->browser = $browser;
|
|
||||||
|
|
||||||
$this->url = config('expose.admin.welcome_message_api_url');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function forUser(ControlConnection $connectionInfo, $user)
|
|
||||||
{
|
|
||||||
return $this->browser
|
|
||||||
->post($this->url, [
|
|
||||||
'Content-Type' => 'application/json',
|
|
||||||
'Accept' => 'application/json',
|
|
||||||
], json_encode([
|
|
||||||
'user' => $user,
|
|
||||||
'connectionInfo' => $connectionInfo->toArray(),
|
|
||||||
]))
|
|
||||||
->then(function (ResponseInterface $response) {
|
|
||||||
$result = json_decode($response->getBody());
|
|
||||||
|
|
||||||
return $result->message ?? '';
|
|
||||||
}, function (Exception $e) {
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -36,52 +36,34 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function paginateUsers(string $searchQuery, int $perPage, int $currentPage): PromiseInterface
|
public function paginateUsers(int $perPage, int $currentPage): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database
|
$this->database
|
||||||
->query('SELECT COUNT(*) AS count FROM users')
|
->query('SELECT * FROM users ORDER by created_at DESC LIMIT :limit OFFSET :offset', [
|
||||||
->then(function (Result $result) use ($searchQuery, $deferred, $perPage, $currentPage) {
|
'limit' => $perPage + 1,
|
||||||
$totalUsers = $result->rows[0]['count'];
|
'offset' => $currentPage < 2 ? 0 : ($currentPage - 1) * $perPage,
|
||||||
|
])
|
||||||
$query = 'SELECT * FROM users ';
|
->then(function (Result $result) use ($deferred, $perPage, $currentPage) {
|
||||||
|
if (count($result->rows) == $perPage + 1) {
|
||||||
$bindings = [
|
array_pop($result->rows);
|
||||||
'limit' => $perPage + 1,
|
$nextPage = $currentPage + 1;
|
||||||
'offset' => $currentPage < 2 ? 0 : ($currentPage - 1) * $perPage,
|
|
||||||
];
|
|
||||||
|
|
||||||
if ($searchQuery !== '') {
|
|
||||||
$query .= "WHERE name LIKE '%".$searchQuery."%' ";
|
|
||||||
$bindings['search'] = $searchQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$query .= ' ORDER by created_at DESC LIMIT :limit OFFSET :offset';
|
$users = collect($result->rows)->map(function ($user) {
|
||||||
|
return $this->getUserDetails($user);
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
$this->database
|
$paginated = [
|
||||||
->query($query, $bindings)
|
'users' => $users,
|
||||||
->then(function (Result $result) use ($deferred, $perPage, $currentPage, $totalUsers) {
|
'current_page' => $currentPage,
|
||||||
if (count($result->rows) == $perPage + 1) {
|
'per_page' => $perPage,
|
||||||
array_pop($result->rows);
|
'next_page' => $nextPage ?? null,
|
||||||
$nextPage = $currentPage + 1;
|
'previous_page' => $currentPage > 1 ? $currentPage - 1 : null,
|
||||||
}
|
];
|
||||||
|
|
||||||
$users = collect($result->rows)->map(function ($user) {
|
$deferred->resolve($paginated);
|
||||||
return $this->getUserDetails($user);
|
|
||||||
})->toArray();
|
|
||||||
|
|
||||||
$paginated = [
|
|
||||||
'total' => $totalUsers,
|
|
||||||
'users' => $users,
|
|
||||||
'current_page' => $currentPage,
|
|
||||||
'per_page' => $perPage,
|
|
||||||
'next_page' => $nextPage ?? null,
|
|
||||||
'previous_page' => $currentPage > 1 ? $currentPage - 1 : null,
|
|
||||||
];
|
|
||||||
|
|
||||||
$deferred->resolve($paginated);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
@@ -114,19 +96,6 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateLastSharedAt($id): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$this->database
|
|
||||||
->query("UPDATE users SET last_shared_at = date('now') WHERE id = :id", ['id' => $id])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve();
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getUserByToken(string $authToken): PromiseInterface
|
public function getUserByToken(string $authToken): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
@@ -134,13 +103,7 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
$this->database
|
$this->database
|
||||||
->query('SELECT * FROM users WHERE auth_token = :token', ['token' => $authToken])
|
->query('SELECT * FROM users WHERE auth_token = :token', ['token' => $authToken])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$user = $result->rows[0] ?? null;
|
$deferred->resolve($result->rows[0] ?? null);
|
||||||
|
|
||||||
if (! is_null($user)) {
|
|
||||||
$user = $this->getUserDetails($user);
|
|
||||||
}
|
|
||||||
|
|
||||||
$deferred->resolve($user);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
@@ -150,38 +113,15 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->getUserByToken($data['auth_token'])
|
$this->database->query("
|
||||||
->then(function ($existingUser) use ($data, $deferred) {
|
INSERT INTO users (name, auth_token, can_specify_subdomains, can_share_tcp_ports, created_at)
|
||||||
if (is_null($existingUser)) {
|
VALUES (:name, :auth_token, :can_specify_subdomains, :can_share_tcp_ports, DATETIME('now'))
|
||||||
$this->database->query("
|
|
||||||
INSERT INTO users (name, auth_token, can_specify_subdomains, can_specify_domains, can_share_tcp_ports, max_connections, created_at)
|
|
||||||
VALUES (:name, :auth_token, :can_specify_subdomains, :can_specify_domains, :can_share_tcp_ports, :max_connections, DATETIME('now'))
|
|
||||||
", $data)
|
", $data)
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId])
|
$this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$deferred->resolve($result->rows[0]);
|
$deferred->resolve($result->rows[0]);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$this->database->query('
|
|
||||||
UPDATE users
|
|
||||||
SET
|
|
||||||
name = :name,
|
|
||||||
can_specify_subdomains = :can_specify_subdomains,
|
|
||||||
can_specify_domains = :can_specify_domains,
|
|
||||||
can_share_tcp_ports = :can_share_tcp_ports,
|
|
||||||
max_connections = :max_connections
|
|
||||||
WHERE
|
|
||||||
auth_token = :auth_token
|
|
||||||
', $data)
|
|
||||||
->then(function (Result $result) use ($existingUser, $deferred) {
|
|
||||||
$this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $existingUser['id']])
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$deferred->resolve($result->rows[0]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
@@ -191,31 +131,11 @@ class DatabaseUserRepository implements UserRepository
|
|||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database->query('DELETE FROM users WHERE id = :id OR auth_token = :id', ['id' => $id])
|
$this->database->query('DELETE FROM users WHERE id = :id', ['id' => $id])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$deferred->resolve($result);
|
$deferred->resolve($result);
|
||||||
});
|
});
|
||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUsersByTokens(array $authTokens): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
$authTokenString = collect($authTokens)->map(function ($token) {
|
|
||||||
return '"'.$token.'"';
|
|
||||||
})->join(',');
|
|
||||||
|
|
||||||
$this->database->query('SELECT * FROM users WHERE auth_token IN ('.$authTokenString.')')
|
|
||||||
->then(function (Result $result) use ($deferred) {
|
|
||||||
$users = collect($result->rows)->map(function ($user) {
|
|
||||||
return $this->getUserDetails($user);
|
|
||||||
})->toArray();
|
|
||||||
|
|
||||||
$deferred->resolve($users);
|
|
||||||
});
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use GuzzleHttp\Psr7\Message;
|
|
||||||
use GuzzleHttp\Psr7\Response;
|
use GuzzleHttp\Psr7\Response;
|
||||||
|
use function GuzzleHttp\Psr7\str;
|
||||||
|
|
||||||
function respond_json($responseData, int $statusCode = 200)
|
function respond_json($responseData, int $statusCode = 200)
|
||||||
{
|
{
|
||||||
return Message::toString(new Response(
|
return str(new Response(
|
||||||
$statusCode,
|
$statusCode,
|
||||||
['Content-Type' => 'application/json'],
|
['Content-Type' => 'application/json'],
|
||||||
json_encode($responseData, JSON_INVALID_UTF8_IGNORE)
|
json_encode($responseData, JSON_INVALID_UTF8_IGNORE)
|
||||||
@@ -14,7 +14,7 @@ function respond_json($responseData, int $statusCode = 200)
|
|||||||
|
|
||||||
function respond_html(string $html, int $statusCode = 200)
|
function respond_html(string $html, int $statusCode = 200)
|
||||||
{
|
{
|
||||||
return Message::toString(new Response(
|
return str(new Response(
|
||||||
$statusCode,
|
$statusCode,
|
||||||
['Content-Type' => 'text/html'],
|
['Content-Type' => 'text/html'],
|
||||||
$html
|
$html
|
||||||
|
|||||||
2
builds/.gitignore
vendored
Normal file
2
builds/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
!.gitignore
|
||||||
|
*
|
||||||
BIN
builds/expose
BIN
builds/expose
Binary file not shown.
@@ -1,56 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "bitinflow/expose",
|
"name": "beyondcode/expose",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"description": "Create public URLs for local sites through any firewall and VPN.",
|
"description": "Expose",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"expose",
|
"expose",
|
||||||
"tunnel",
|
"tunnel",
|
||||||
"ngrok"
|
"ngrok"
|
||||||
],
|
],
|
||||||
"homepage": "https://bitinflow.dev",
|
"homepage": "https://sharedwithexpose.com",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
|
||||||
"name": "René Preuß",
|
|
||||||
"email": "rene@bitinflow.com"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Marcel Pociot",
|
"name": "Marcel Pociot",
|
||||||
"email": "marcel@beyondco.de"
|
"email": "marcel@beyondco.de"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.0",
|
"php": "^7.3.0",
|
||||||
"ext-json": "*",
|
"ext-json": "*"
|
||||||
"laravel-zero/phar-updater": "^1.2"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"cboden/ratchet": "^0.4.3",
|
"cboden/ratchet": "^0.4.2",
|
||||||
"clue/block-react": "^1.4",
|
"clue/block-react": "^1.3",
|
||||||
"clue/buzz-react": "^2.9",
|
"clue/buzz-react": "^2.7",
|
||||||
"clue/reactphp-sqlite": "dev-modular-worker-for-phar-support",
|
"clue/reactphp-sqlite": "dev-modular-worker-for-phar-support",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^6.5",
|
||||||
"guzzlehttp/psr7": "^1.7",
|
"guzzlehttp/psr7": "dev-master as 1.6.1",
|
||||||
"illuminate/log": "^8.0",
|
"illuminate/http": "5.8.* || ^6.0 || ^7.0",
|
||||||
"illuminate/http": "5.8.* || ^6.0 || ^7.0 || ^8.0",
|
"illuminate/pipeline": "^7.6",
|
||||||
"illuminate/pipeline": "^7.6 || ^8.0",
|
"illuminate/validation": "^7.7",
|
||||||
"illuminate/validation": "^7.7 || ^8.0",
|
"laminas/laminas-http": "^2.11",
|
||||||
"laminas/laminas-http": "^2.13",
|
"laravel-zero/framework": "^7.0",
|
||||||
"laravel-zero/framework": "^8.2",
|
"mockery/mockery": "^1.3",
|
||||||
"mockery/mockery": "^1.4.2",
|
"namshi/cuzzle": "^2.0",
|
||||||
"octoper/cuzzle": "^3.1",
|
"nikic/php-parser": "^4.4",
|
||||||
"nikic/php-parser": "^v4.10",
|
"nyholm/psr7": "^1.2",
|
||||||
"nyholm/psr7": "^1.3",
|
"phpunit/phpunit": "^8.5",
|
||||||
"phpunit/phpunit": "^9.4.3",
|
"ratchet/pawl": "^0.3.4",
|
||||||
"ratchet/pawl": "^0.3.5",
|
"react/http": "^0.8.6",
|
||||||
"react/http": "^1.1.0",
|
|
||||||
"react/socket": "^1.6",
|
"react/socket": "^1.6",
|
||||||
"react/stream": "^1.1.1",
|
"react/stream": "^1.1.1",
|
||||||
"riverline/multipart-parser": "^2.0",
|
"riverline/multipart-parser": "^2.0",
|
||||||
"symfony/expression-language": "^5.2",
|
"symfony/expression-language": "^5.0",
|
||||||
"symfony/http-kernel": "^4.0 || ^5.2",
|
"symfony/http-kernel": "^4.0 || ^5.0",
|
||||||
"symfony/psr-http-message-bridge": "^2.0",
|
"symfony/psr-http-message-bridge": "^2.0",
|
||||||
"twig/twig": "^3.1"
|
"twig/twig": "^3.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true,
|
"optimize-autoloader": true,
|
||||||
|
|||||||
7893
composer.lock
generated
Normal file
7893
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -26,7 +26,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'version' => '2.2.2',
|
'version' => '1.3.0',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -59,6 +59,4 @@ return [
|
|||||||
Illuminate\Translation\TranslationServiceProvider::class,
|
Illuminate\Translation\TranslationServiceProvider::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
'locale' => 'en',
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,58 +4,30 @@ return [
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Servers
|
| Host
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| The available Expose servers that your client can connect to.
|
| The expose server to connect to. By default, expose is using the free
|
||||||
| When sharing sites or TCP ports, you can specify the server
|
| sharedwithexpose.com server, offered by Beyond Code. You will need a free
|
||||||
| that should be used using the `--server=` option.
|
| Beyond Code account in order to authenticate with the server.
|
||||||
|
| Feel free to host your own server and change this value.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'servers' => [
|
'host' => 'sharedwithexpose.com',
|
||||||
'free' => [
|
|
||||||
'host' => 'bitinflow.dev',
|
|
||||||
'port' => 443,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Server Endpoint
|
| Port
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| When you specify a server that does not exist in above static array,
|
| The port that expose will try to connect to. If you want to bypass
|
||||||
| Expose will perform a GET request to this URL and tries to retrieve
|
| firewalls and have proper SSL encrypted tunnels, make sure to use
|
||||||
| a JSON payload that looks like the configurations servers array.
|
| port 443 and use a reverse proxy for Expose.
|
||||||
|
|
|
|
||||||
| Expose then tries to load the configuration for the given server
|
| The free default server is already running on port 443.
|
||||||
| if available.
|
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'server_endpoint' => 'https://bitinflow.dev/api/servers',
|
'port' => 443,
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Server
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The default server from the servers array,
|
|
||||||
| or the servers endpoint above.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'default_server' => 'free',
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| DNS
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The DNS server to use when resolving the shared URLs.
|
|
||||||
| When Expose is running from within Docker containers, you should set this to
|
|
||||||
| `true` to fall-back to the system default DNS servers.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'dns' => '127.0.0.1',
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -71,20 +43,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'auth_token' => '',
|
'auth_token' => '',
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Domain
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| The custom domain to use when sharing sites with Expose.
|
|
||||||
| You can register your own custom domain using Expose Pro
|
|
||||||
| Learn more at: https://expose.dev/get-pro
|
|
||||||
|
|
|
||||||
| > expose default-domain YOUR-CUSTOM-WHITELABEL-DOMAIN
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'default_domain' => null,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Default TLD
|
| Default TLD
|
||||||
@@ -97,18 +55,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'default_tld' => 'test',
|
'default_tld' => 'test',
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default HTTPS
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Whether to use HTTPS as a default when sharing your local sites. Expose
|
|
||||||
| will try to look up the protocol if you are using Laravel Valet
|
|
||||||
| automatically. Otherwise you can specify it here manually.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'default_https' => false,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Maximum Logged Requests
|
| Maximum Logged Requests
|
||||||
@@ -205,19 +151,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'validate_auth_tokens' => false,
|
'validate_auth_tokens' => false,
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| TCP Port Sharing
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Control if you want to allow users to share TCP ports with your Expose
|
|
||||||
| server. You can add fine-grained control per authentication token,
|
|
||||||
| but if you want to disable TCP port sharing in general, set this
|
|
||||||
| value to false.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'allow_tcp_port_sharing' => true,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| TCP Port Range
|
| TCP Port Range
|
||||||
@@ -249,21 +182,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'maximum_connection_length' => 0,
|
'maximum_connection_length' => 0,
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Maximum number of open connections
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| You can limit the amount of connections that one client/user can have
|
|
||||||
| open. A maximum connection count of 0 means that clients can open
|
|
||||||
| as many connections as they want.
|
|
||||||
|
|
|
||||||
| When creating users with the API/admin interface, you can
|
|
||||||
| override this setting per user.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'maximum_open_connections_per_user' => 0,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Subdomain
|
| Subdomain
|
||||||
@@ -276,17 +194,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'subdomain' => 'expose',
|
'subdomain' => 'expose',
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Reserved Subdomain
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Specify any subdomains that you don't want to be able to register
|
|
||||||
| on your expose server.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'reserved_subdomains' => [],
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Subdomain Generator
|
| Subdomain Generator
|
||||||
@@ -299,25 +206,6 @@ return [
|
|||||||
*/
|
*/
|
||||||
'subdomain_generator' => \App\Server\SubdomainGenerator\RandomSubdomainGenerator::class,
|
'subdomain_generator' => \App\Server\SubdomainGenerator\RandomSubdomainGenerator::class,
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Connection Callback
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This is a callback method that will be called when a new connection is
|
|
||||||
| established.
|
|
||||||
| The \App\Client\Callbacks\WebHookConnectionCallback::class is included out of the box.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
'connection_callback' => null,
|
|
||||||
|
|
||||||
'connection_callbacks' => [
|
|
||||||
'webhook' => [
|
|
||||||
'url' => null,
|
|
||||||
'secret' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Users
|
| Users
|
||||||
@@ -346,8 +234,6 @@ return [
|
|||||||
|
|
||||||
'subdomain_repository' => \App\Server\SubdomainRepository\DatabaseSubdomainRepository::class,
|
'subdomain_repository' => \App\Server\SubdomainRepository\DatabaseSubdomainRepository::class,
|
||||||
|
|
||||||
'logger_repository' => \App\Server\LoggerRepository\NullLogger::class,
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Messages
|
| Messages
|
||||||
@@ -359,35 +245,15 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'messages' => [
|
'messages' => [
|
||||||
'resolve_connection_message' => function ($connectionInfo, $user) {
|
|
||||||
return config('expose.admin.messages.message_of_the_day');
|
|
||||||
},
|
|
||||||
|
|
||||||
'message_of_the_day' => 'Thank you for using expose.',
|
'message_of_the_day' => 'Thank you for using expose.',
|
||||||
|
|
||||||
'invalid_auth_token' => 'Authentication failed. Please check your authentication token and try again.',
|
'invalid_auth_token' => 'Authentication failed. Please check your authentication token and try again.',
|
||||||
|
|
||||||
'subdomain_taken' => 'The chosen subdomain :subdomain is already taken. Please choose a different subdomain.',
|
'subdomain_taken' => 'The chosen subdomain :subdomain is already taken. Please choose a different subdomain.',
|
||||||
|
|
||||||
'subdomain_reserved' => 'The chosen subdomain :subdomain is not available. Please choose a different subdomain.',
|
|
||||||
|
|
||||||
'custom_subdomain_unauthorized' => 'You are not allowed to specify custom subdomains. Please upgrade to Expose Pro. Assigning a random subdomain instead.',
|
'custom_subdomain_unauthorized' => 'You are not allowed to specify custom subdomains. Please upgrade to Expose Pro. Assigning a random subdomain instead.',
|
||||||
|
|
||||||
'custom_domain_unauthorized' => 'You are not allowed to use this custom domain.',
|
|
||||||
|
|
||||||
'tcp_port_sharing_unauthorized' => 'You are not allowed to share TCP ports. Please upgrade to Expose Pro.',
|
|
||||||
|
|
||||||
'no_free_tcp_port_available' => 'There are no free TCP ports available on this server. Please try again later.',
|
'no_free_tcp_port_available' => 'There are no free TCP ports available on this server. Please try again later.',
|
||||||
|
|
||||||
'tcp_port_sharing_disabled' => 'TCP port sharing is not available on this Expose server.',
|
|
||||||
],
|
|
||||||
|
|
||||||
'statistics' => [
|
|
||||||
'enable_statistics' => true,
|
|
||||||
|
|
||||||
'interval_in_seconds' => 3600,
|
|
||||||
|
|
||||||
'repository' => \App\Server\StatisticsRepository\DatabaseStatisticsRepository::class,
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Monolog\Handler\NullHandler;
|
|
||||||
use Monolog\Handler\StreamHandler;
|
|
||||||
use Monolog\Handler\SyslogUdpHandler;
|
|
||||||
|
|
||||||
return [
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Default Log Channel
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| This option defines the default log channel that gets used when writing
|
|
||||||
| messages to the logs. The name specified in this option should match
|
|
||||||
| one of the channels defined in the "channels" configuration array.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'default' => env('LOG_CHANNEL', 'stack'),
|
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Log Channels
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Here you may configure the log channels for your application. Out of
|
|
||||||
| the box, Laravel uses the Monolog PHP logging library. This gives
|
|
||||||
| you a variety of powerful log handlers / formatters to utilize.
|
|
||||||
|
|
|
||||||
| Available Drivers: "single", "daily", "slack", "syslog",
|
|
||||||
| "errorlog", "monolog",
|
|
||||||
| "custom", "stack"
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'channels' => [
|
|
||||||
'stack' => [
|
|
||||||
'driver' => 'stack',
|
|
||||||
'channels' => ['stderr'],
|
|
||||||
'ignore_exceptions' => false,
|
|
||||||
],
|
|
||||||
|
|
||||||
'single' => [
|
|
||||||
'driver' => 'single',
|
|
||||||
'path' => storage_path('logs/laravel.log'),
|
|
||||||
'level' => 'debug',
|
|
||||||
],
|
|
||||||
|
|
||||||
'daily' => [
|
|
||||||
'driver' => 'daily',
|
|
||||||
'path' => storage_path('logs/laravel.log'),
|
|
||||||
'level' => 'debug',
|
|
||||||
'days' => 14,
|
|
||||||
],
|
|
||||||
|
|
||||||
'slack' => [
|
|
||||||
'driver' => 'slack',
|
|
||||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
|
||||||
'username' => 'Laravel Log',
|
|
||||||
'emoji' => ':boom:',
|
|
||||||
'level' => 'critical',
|
|
||||||
],
|
|
||||||
|
|
||||||
'papertrail' => [
|
|
||||||
'driver' => 'monolog',
|
|
||||||
'level' => 'debug',
|
|
||||||
'handler' => SyslogUdpHandler::class,
|
|
||||||
'handler_with' => [
|
|
||||||
'host' => env('PAPERTRAIL_URL'),
|
|
||||||
'port' => env('PAPERTRAIL_PORT'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'stderr' => [
|
|
||||||
'driver' => 'monolog',
|
|
||||||
'handler' => StreamHandler::class,
|
|
||||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
|
||||||
'with' => [
|
|
||||||
'stream' => 'php://stderr',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'deprecations' => [
|
|
||||||
'driver' => 'monolog',
|
|
||||||
'handler' => StreamHandler::class,
|
|
||||||
'formatter' => env('LOG_STDERR_FORMATTER'),
|
|
||||||
'with' => [
|
|
||||||
'stream' => 'php://stderr',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
'syslog' => [
|
|
||||||
'driver' => 'syslog',
|
|
||||||
'level' => 'debug',
|
|
||||||
],
|
|
||||||
|
|
||||||
'errorlog' => [
|
|
||||||
'driver' => 'errorlog',
|
|
||||||
'level' => 'debug',
|
|
||||||
],
|
|
||||||
|
|
||||||
'null' => [
|
|
||||||
'driver' => 'monolog',
|
|
||||||
'handler' => NullHandler::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
'emergency' => [
|
|
||||||
'path' => storage_path('logs/laravel.log'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE users ADD max_connections INTEGER NOT NULL DEFAULT 0;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
ALTER TABLE users ADD last_shared_at DATETIME;
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS statistics (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
timestamp DATE,
|
|
||||||
shared_sites INTEGER,
|
|
||||||
shared_ports INTEGER,
|
|
||||||
unique_shared_sites INTEGER,
|
|
||||||
unique_shared_ports INTEGER,
|
|
||||||
incoming_requests INTEGER
|
|
||||||
)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
ALTER TABLE users ADD can_specify_domains BOOLEAN DEFAULT 1;
|
|
||||||
ALTER TABLE subdomains ADD domain STRING;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS domains (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
domain STRING NOT NULL,
|
|
||||||
created_at DATETIME,
|
|
||||||
updated_at DATETIME
|
|
||||||
)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
CREATE TABLE IF NOT EXISTS logs (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
user_id INTEGER NOT NULL,
|
|
||||||
subdomain STRING NOT NULL,
|
|
||||||
created_at DATETIME
|
|
||||||
)
|
|
||||||
@@ -3,7 +3,7 @@ services:
|
|||||||
expose:
|
expose:
|
||||||
image: beyondcodegmbh/expose-server:latest
|
image: beyondcodegmbh/expose-server:latest
|
||||||
ports:
|
ports:
|
||||||
- 8080:${PORT}
|
- 127.0.0.1:8080:${PORT}
|
||||||
environment:
|
environment:
|
||||||
port: ${PORT}
|
port: ${PORT}
|
||||||
domain: ${DOMAIN}
|
domain: ${DOMAIN}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
sed -i "s|username|${username}|g" ${exposeConfigPath} && sed -i "s|password|${password}|g" ${exposeConfigPath}
|
|
||||||
|
|
||||||
if [[ $# -eq 0 ]]; then
|
|
||||||
exec /src/expose serve ${domain} --port ${port} --validateAuthTokens
|
|
||||||
else
|
|
||||||
exec /src/expose "$@"
|
|
||||||
fi
|
|
||||||
@@ -16,7 +16,7 @@ The result looks like this:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"configuration":{
|
"configuration":{
|
||||||
"hostname": "bitinflow.dev",
|
"hostname": "sharedwithexpose.com",
|
||||||
"port": 8080,
|
"port": 8080,
|
||||||
"database": "/home/forge/expose/database/expose.db",
|
"database": "/home/forge/expose/database/expose.db",
|
||||||
"validate_auth_tokens": false,
|
"validate_auth_tokens": false,
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
---
|
---
|
||||||
title: Basic Authentication
|
title: Basic Authentication
|
||||||
order: 4
|
order: 2
|
||||||
---
|
---
|
||||||
|
|
||||||
# Sharing sites with basic authentication
|
# Sharing sites with basic authentication
|
||||||
|
|
||||||
Expose allows you to share your local sites with custom basic authentication credentials.
|
Expose allows you to share your local sites with custom basic authentication credentials.
|
||||||
|
|
||||||
This is useful, if you have a static subdomain that you share with someone else, for example a client, and you want to provide some additional security to it. Before someone can access your shared site, they need to provide the correct credentials.
|
This can be useful, if you have a static subdomain that you share with someone else, for example a client, and you want to provide some additional security to it. Before someone can access your shared site, they need to provide the correct credentials.
|
||||||
|
|
||||||
> **Warning**: You can not add basic authentication to a website that already uses basic authentication.
|
> **Warning**: You can not add basic authentication to a website that already uses basic authentication.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: Configuration
|
title: Configuration
|
||||||
order: 6
|
order: 3
|
||||||
---
|
---
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
@@ -34,12 +34,12 @@ return [
|
|||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
|
||||||
| The expose server to connect to. By default, expose is using the free
|
| The expose server to connect to. By default, expose is using the free
|
||||||
| bitinflow.dev server, offered by Beyond Code. You will need a free
|
| sharedwithexpose.com server, offered by Beyond Code. You will need a free
|
||||||
| Beyond Code account in order to authenticate with the server.
|
| Beyond Code account in order to authenticate with the server.
|
||||||
| Feel free to host your own server and change this value.
|
| Feel free to host your own server and change this value.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'host' => 'bitinflow.dev',
|
'host' => 'sharedwithexpose.com',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
---
|
---
|
||||||
title: Local Dashboard
|
title: Dashboard
|
||||||
order: 2
|
order: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
# Dashboard
|
# Dashboard
|
||||||
|
|
||||||
Once you share a local site, Expose shows you all incoming HTTP requests along with their status code and duration in your terminal:
|
Once you share a local site, expose will show you all incoming HTTP requests along with their status code and duration in your terminal:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
While this is great to get a quick look of the incoming requests, you often need more information than this.
|
While this is great to get a quick look of the incoming requests, you sometimes need more information than this.
|
||||||
|
|
||||||
Because of that, Expose is also exposing a web based dashboard on port 4040.
|
Because of that, expose is also exposing a web based dashboard on port 4040.
|
||||||
|
|
||||||
Once you start sharing a site, Expose shows you a QR code that you can scan with your mobile device, to easily browse your shared sites on your phone or tablet.
|
Once you start sharing a site, expose will show you a QR code that you can scan with your mobile device, to easily browse your shared sites on your phone or tablet.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
When a request comes in, you can see all incoming HTTP requests as they hit your local site in realtime.
|
Once a request comes in, you can see all incoming HTTP requests as they hit your local site in realtime.
|
||||||
You can click on a specific request and see detailed information about the request and response. Expose provides several tools for developers to make webhook testing easier – the most powerful one is that you can replay requests without firing the webhook again. So if your previous process required to create multiple test orders to see how the paylods of your payment provider look like, Expose makes this a breeze by allowing you to replay these requests without creating more orders.
|
When you click on a specific request, you can see detailed information about the request and response.
|
||||||
|
|
||||||

|

|
||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
title: Global Server Infrastructure
|
|
||||||
order: 4
|
|
||||||
---
|
|
||||||
|
|
||||||
# Global Server Infrastructure ::pro
|
|
||||||
|
|
||||||
[Expose Pro](/get-pro) allows you to choose between multiple Expose servers around the world, so that you can use an endpoint closest to you.
|
|
||||||
|
|
||||||
To get a list of all the available Expose servers, you can run `expose servers`
|
|
||||||
|
|
||||||
```
|
|
||||||
$ expose servers
|
|
||||||
|
|
||||||
+------+---------------------------+------+
|
|
||||||
| Key | Region | Type |
|
|
||||||
+------+---------------------------+------+
|
|
||||||
| eu-1 | EU (Frankfurt) | Pro |
|
|
||||||
| us-1 | US (New York) | Pro |
|
|
||||||
| us-2 | US (San Francisco) | Pro |
|
|
||||||
| ap-1 | Asia Pacific (Singapore) | Pro |
|
|
||||||
| in-1 | India (Bangalore) | Pro |
|
|
||||||
| sa-1 | South America (São Paulo) | Pro |
|
|
||||||
| au-1 | Australia (Sydney) | Pro |
|
|
||||||
+------+---------------------------+------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changing servers
|
|
||||||
|
|
||||||
When you share a local URL, or a local TCP port, you can specify the Expose server region, using the `--server` command line option. Pass the server key as the option to connect to this specific server.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
expose share my-local-site.test --server=eu-1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setting a default server
|
|
||||||
|
|
||||||
Most of the time you will want to always use the server location that is closest to you for all of your Expose commands. You can define the default server that Expose should use, by calling the `expose default-server` command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
expose default-server us-2
|
|
||||||
```
|
|
||||||
|
|
||||||
Now the next time that you will share a local URL or port, Expose is automatically going to connect to the `us-2` server for your.
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user