mirror of
https://github.com/bitinflow/expose.git
synced 2026-03-14 14:05:54 +00:00
Compare commits
3 Commits
master
...
custom-hos
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6acf67ab39 | ||
|
|
5595a9de3d | ||
|
|
cec52c4229 |
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,23 +40,18 @@ class Client
|
|||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldExit($shouldExit = true)
|
public function share(string $sharedUrl, array $subdomains = [], string $hostname = '')
|
||||||
{
|
|
||||||
$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, $hostname, config('expose.auth_token'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sharePort(int $port)
|
public function sharePort(int $port)
|
||||||
{
|
{
|
||||||
$this->connectToServerAndShareTcp($port, $this->configuration->auth());
|
$this->connectToServerAndShareTcp($port, config('expose.auth_token'));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function prepareSharedUrl(string $sharedUrl): string
|
protected function prepareSharedUrl(string $sharedUrl): string
|
||||||
@@ -81,30 +72,28 @@ class Client
|
|||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function connectToServer(string $sharedUrl, $subdomain, $serverHost = null, $authToken = ''): PromiseInterface
|
public function connectToServer(string $sharedUrl, $subdomain, $hostname = '', $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, $hostname, $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, $hostname);
|
||||||
|
|
||||||
$clientConnection->on('close', function () use ($sharedUrl, $subdomain, $serverHost, $authToken) {
|
$clientConnection->on('close', function () use ($sharedUrl, $subdomain, $hostname, $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, $hostname, $authToken) {
|
||||||
$this->connectToServer($sharedUrl, $subdomain, $serverHost, $authToken);
|
$this->connectToServer($sharedUrl, $subdomain, $hostname, $authToken);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,22 +107,25 @@ 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();
|
if ($data->hostname !== '' && ! is_null($data->hostname)) {
|
||||||
|
$exposeUrl = "{$httpProtocol}://{$data->hostname}";
|
||||||
$this->configuration->setServerHost($host);
|
} else {
|
||||||
|
$exposeUrl = "{$httpProtocol}://{$data->subdomain}.{$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{$exposeUrl}");
|
||||||
$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 +152,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 +177,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 +207,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 +237,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,15 +57,15 @@ 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, ?string $hostname)
|
||||||
{
|
{
|
||||||
$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,
|
||||||
|
'hostname' => empty($hostname) ? null : $hostname,
|
||||||
],
|
],
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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, $hostname = null)
|
||||||
{
|
{
|
||||||
app('expose.client')->share($sharedUrl, $subdomain, $serverHost);
|
app('expose.client')->share($sharedUrl, $subdomain, $hostname);
|
||||||
|
|
||||||
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,8 +6,8 @@ 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;
|
||||||
@@ -74,7 +74,7 @@ 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'),
|
'dns' => '127.0.0.1',
|
||||||
'tls' => [
|
'tls' => [
|
||||||
'verify_peer' => false,
|
'verify_peer' => false,
|
||||||
'verify_peer_name' => false,
|
'verify_peer_name' => false,
|
||||||
@@ -85,19 +85,17 @@ class HttpClient
|
|||||||
protected function sendRequestToApplication(RequestInterface $request, $proxyConnection = null)
|
protected function sendRequestToApplication(RequestInterface $request, $proxyConnection = null)
|
||||||
{
|
{
|
||||||
(new Browser($this->loop, $this->createConnector()))
|
(new Browser($this->loop, $this->createConnector()))
|
||||||
->withFollowRedirects(false)
|
->withOptions([
|
||||||
->withRejectErrorResponse(false)
|
'followRedirects' => false,
|
||||||
->requestStreaming(
|
'obeySuccessCode' => false,
|
||||||
$request->getMethod(),
|
'streaming' => true,
|
||||||
$request->getUri(),
|
])
|
||||||
$request->getHeaders(),
|
->send($request)
|
||||||
$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 = $this->rewriteResponseHeaders($response);
|
||||||
|
|
||||||
$response->buffer = Message::toString($response);
|
$response->buffer = str($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->sendChunkToServer($response->buffer, $proxyConnection);
|
$this->sendChunkToServer($response->buffer, $proxyConnection);
|
||||||
@@ -105,7 +103,7 @@ class HttpClient
|
|||||||
/* @var $body \React\Stream\ReadableStreamInterface */
|
/* @var $body \React\Stream\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;
|
||||||
|
|||||||
@@ -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,62 +3,46 @@
|
|||||||
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} {--hostname=} {--subdomain=} {--auth=}';
|
||||||
|
|
||||||
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()
|
||||||
{
|
{
|
||||||
$auth = $this->option('auth') ?? config('expose.auth_token', '');
|
if (! empty($this->option('hostname')) && ! empty($this->option('subdomain'))) {
|
||||||
$this->info('Using auth token: '.$auth, OutputInterface::VERBOSITY_DEBUG);
|
$this->error('You can only specify one. Either a custom hostname or a subdomain.');
|
||||||
|
|
||||||
if (strstr($this->argument('host'), 'host.docker.internal')) {
|
return;
|
||||||
config(['expose.dns' => true]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->option('dns') !== null) {
|
$this->configureConnectionLogger();
|
||||||
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(config('expose.host', 'localhost'))
|
||||||
->setPort($this->getServerPort())
|
->setPort(config('expose.port', 8080))
|
||||||
->setAuth($auth)
|
->setAuth($this->option('auth'))
|
||||||
->setBasicAuth($this->option('basicAuth'))
|
|
||||||
->createClient()
|
->createClient()
|
||||||
->share(
|
->share(
|
||||||
$this->argument('host'),
|
$this->argument('host'),
|
||||||
$subdomains,
|
explode(',', $this->option('subdomain')),
|
||||||
$domain
|
$this->option('hostname')
|
||||||
)
|
)
|
||||||
->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?} {--hostname=} {--subdomain=} {--auth=}';
|
||||||
|
|
||||||
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->option('hostname')) {
|
||||||
$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, ?string $hostname, ConnectionInterface $connection): ControlConnection;
|
||||||
|
|
||||||
public function storeTcpConnection(int $port, ConnectionInterface $connection): ControlConnection;
|
public function storeTcpConnection(int $port, ConnectionInterface $connection): ControlConnection;
|
||||||
|
|
||||||
@@ -20,7 +20,9 @@ interface ConnectionManager
|
|||||||
|
|
||||||
public function removeControlConnection($connection);
|
public function removeControlConnection($connection);
|
||||||
|
|
||||||
public function findControlConnectionForSubdomainAndServerHost($subdomain, $serverHost): ?ControlConnection;
|
public function findControlConnectionForSubdomain($subdomain): ?ControlConnection;
|
||||||
|
|
||||||
|
public function findControlConnectionForHostname(string $hostname): ?ControlConnection;
|
||||||
|
|
||||||
public function findControlConnectionForClientId(string $clientId): ?ControlConnection;
|
public function findControlConnectionForClientId(string $clientId): ?ControlConnection;
|
||||||
|
|
||||||
@@ -29,8 +31,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;
|
|
||||||
}
|
|
||||||
22
app/Contracts/HostnameRepository.php
Normal file
22
app/Contracts/HostnameRepository.php
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Contracts;
|
||||||
|
|
||||||
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
|
interface HostnameRepository
|
||||||
|
{
|
||||||
|
public function getHostnames(): PromiseInterface;
|
||||||
|
|
||||||
|
public function getHostnameById($id): PromiseInterface;
|
||||||
|
|
||||||
|
public function getHostnameByName(string $name): PromiseInterface;
|
||||||
|
|
||||||
|
public function getHostnamesByUserId($id): PromiseInterface;
|
||||||
|
|
||||||
|
public function getHostnamesByUserIdAndName($id, $name): PromiseInterface;
|
||||||
|
|
||||||
|
public function deleteHostnameForUserId($userId, $hostnameId): PromiseInterface;
|
||||||
|
|
||||||
|
public function storeHostname(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,14 +37,6 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
$builtInConfig = config('expose');
|
$builtInConfig = config('expose');
|
||||||
|
|
||||||
$keyServerVariable = 'EXPOSE_CONFIG_FILE';
|
|
||||||
if (array_key_exists($keyServerVariable, $_SERVER) && is_string($_SERVER[$keyServerVariable]) && file_exists($_SERVER[$keyServerVariable])) {
|
|
||||||
$localConfig = require $_SERVER[$keyServerVariable];
|
|
||||||
config()->set('expose', array_merge($builtInConfig, $localConfig));
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$localConfigFile = getcwd().DIRECTORY_SEPARATOR.'.expose.php';
|
$localConfigFile = getcwd().DIRECTORY_SEPARATOR.'.expose.php';
|
||||||
|
|
||||||
if (file_exists($localConfigFile)) {
|
if (file_exists($localConfigFile)) {
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class Configuration implements \JsonSerializable
|
|||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
#[\ReturnTypeWillChange]
|
|
||||||
public function jsonSerialize()
|
public function jsonSerialize()
|
||||||
{
|
{
|
||||||
return array_merge([
|
return array_merge([
|
||||||
|
|||||||
35
app/Server/Connections/ConnectionConfiguration.php
Normal file
35
app/Server/Connections/ConnectionConfiguration.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Server\Connections;
|
||||||
|
|
||||||
|
class ConnectionConfiguration
|
||||||
|
{
|
||||||
|
protected $hostname;
|
||||||
|
protected $subdomain;
|
||||||
|
|
||||||
|
private function __construct($subdomain, $hostname)
|
||||||
|
{
|
||||||
|
$this->subdomain = $subdomain;
|
||||||
|
$this->hostname = $hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function withSubdomain($subdomain)
|
||||||
|
{
|
||||||
|
return new static($subdomain, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function withHostname($hostname)
|
||||||
|
{
|
||||||
|
return new static(null, $hostname);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSubdomain()
|
||||||
|
{
|
||||||
|
return $this->subdomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHostname()
|
||||||
|
{
|
||||||
|
return $this->hostname;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,32 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeConnection(string $host, ?string $subdomain, ?string $serverHost, ConnectionInterface $connection): ControlConnection
|
public function storeConnection(string $host, ?string $subdomain, ?string $hostname, ConnectionInterface $connection): ControlConnection
|
||||||
{
|
{
|
||||||
$clientId = (string) uniqid();
|
$clientId = (string) uniqid();
|
||||||
|
|
||||||
$connection->client_id = $clientId;
|
$connection->client_id = $clientId;
|
||||||
|
|
||||||
|
if (! is_null($hostname) && $hostname !== '') {
|
||||||
|
$subdomain = '';
|
||||||
|
} else {
|
||||||
|
$subdomain = $subdomain ?? $this->subdomainGenerator->generateSubdomain();
|
||||||
|
}
|
||||||
|
|
||||||
$storedConnection = new ControlConnection(
|
$storedConnection = new ControlConnection(
|
||||||
$connection,
|
$connection,
|
||||||
$host,
|
$host,
|
||||||
$subdomain ?? $this->subdomainGenerator->generateSubdomain(),
|
$subdomain,
|
||||||
|
$hostname,
|
||||||
$clientId,
|
$clientId,
|
||||||
$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 +85,6 @@ class ConnectionManager implements ConnectionManagerContract
|
|||||||
|
|
||||||
$this->connections[] = $storedConnection;
|
$this->connections[] = $storedConnection;
|
||||||
|
|
||||||
$this->statisticsCollector->portShared($this->getAuthTokenFromConnection($connection));
|
|
||||||
|
|
||||||
return $storedConnection;
|
return $storedConnection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,10 +150,17 @@ 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function findControlConnectionForHostname($hostname): ?ControlConnection
|
||||||
|
{
|
||||||
|
return collect($this->connections)->last(function ($connection) use ($hostname) {
|
||||||
|
return $connection->hostname == $hostname;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,20 +171,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,22 @@ 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 $hostname;
|
||||||
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 $hostname, string $clientId, string $authToken = '')
|
||||||
{
|
{
|
||||||
$this->socket = $socket;
|
$this->socket = $socket;
|
||||||
$this->host = $host;
|
$this->host = $host;
|
||||||
$this->subdomain = $subdomain;
|
$this->subdomain = $subdomain;
|
||||||
|
$this->hostname = $hostname;
|
||||||
$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,12 +63,10 @@ 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,
|
||||||
|
'hostname' => $this->hostname,
|
||||||
'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,
|
||||||
|
|||||||
@@ -3,27 +3,20 @@
|
|||||||
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\HostnameRepository;
|
||||||
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\DeleteHostnameController;
|
||||||
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 +25,13 @@ 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\StoreHostnameController;
|
||||||
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 +131,18 @@ 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->post('/api/hostnames', StoreHostnameController::class, $adminCondition);
|
||||||
|
$this->router->delete('/api/hostnames/{hostname}', DeleteHostnameController::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 +181,10 @@ class Factory
|
|||||||
$this->bindConfiguration()
|
$this->bindConfiguration()
|
||||||
->bindSubdomainGenerator()
|
->bindSubdomainGenerator()
|
||||||
->bindUserRepository()
|
->bindUserRepository()
|
||||||
->bindLoggerRepository()
|
|
||||||
->bindSubdomainRepository()
|
->bindSubdomainRepository()
|
||||||
->bindDomainRepository()
|
->bindHostnameRepository()
|
||||||
->bindDatabase()
|
->bindDatabase()
|
||||||
->ensureDatabaseIsInitialized()
|
->ensureDatabaseIsInitialized()
|
||||||
->registerStatisticsCollector()
|
|
||||||
->bindConnectionManager()
|
->bindConnectionManager()
|
||||||
->addAdminRoutes();
|
->addAdminRoutes();
|
||||||
|
|
||||||
@@ -245,25 +222,16 @@ 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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function bindLoggerRepository()
|
protected function bindHostnameRepository()
|
||||||
{
|
{
|
||||||
app()->singleton(LoggerRepository::class, function () {
|
app()->singleton(HostnameRepository::class, function () {
|
||||||
return app(config('expose.admin.logger_repository', NullLogger::class));
|
return app(config('expose.admin.hostname_repository'));
|
||||||
});
|
|
||||||
|
|
||||||
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 +263,8 @@ class Factory
|
|||||||
->files()
|
->files()
|
||||||
->ignoreDotFiles(true)
|
->ignoreDotFiles(true)
|
||||||
->in(database_path('migrations'))
|
->in(database_path('migrations'))
|
||||||
->name('*.sql')
|
->sortByName()
|
||||||
->sortByName();
|
->name('*.sql');
|
||||||
|
|
||||||
/** @var SplFileInfo $migration */
|
/** @var SplFileInfo $migration */
|
||||||
foreach ($migrations as $migration) {
|
foreach ($migrations as $migration) {
|
||||||
@@ -312,27 +280,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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Server\DomainRepository;
|
namespace App\Server\HostnameRepository;
|
||||||
|
|
||||||
use App\Contracts\DomainRepository;
|
use App\Contracts\HostnameRepository;
|
||||||
use Clue\React\SQLite\DatabaseInterface;
|
use Clue\React\SQLite\DatabaseInterface;
|
||||||
use Clue\React\SQLite\Result;
|
use Clue\React\SQLite\Result;
|
||||||
use React\Promise\Deferred;
|
use React\Promise\Deferred;
|
||||||
use React\Promise\PromiseInterface;
|
use React\Promise\PromiseInterface;
|
||||||
|
|
||||||
class DatabaseDomainRepository implements DomainRepository
|
class DatabaseHostnameRepository implements HostnameRepository
|
||||||
{
|
{
|
||||||
/** @var DatabaseInterface */
|
/** @var DatabaseInterface */
|
||||||
protected $database;
|
protected $database;
|
||||||
@@ -18,12 +18,12 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomains(): PromiseInterface
|
public function getHostnames(): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database
|
$this->database
|
||||||
->query('SELECT * FROM domains ORDER by created_at DESC')
|
->query('SELECT * FROM hostnames ORDER by created_at DESC')
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$deferred->resolve($result->rows);
|
$deferred->resolve($result->rows);
|
||||||
});
|
});
|
||||||
@@ -31,12 +31,12 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomainById($id): PromiseInterface
|
public function getHostnameById($id): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database
|
$this->database
|
||||||
->query('SELECT * FROM domains WHERE id = :id', ['id' => $id])
|
->query('SELECT * FROM hostnames WHERE id = :id', ['id' => $id])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$deferred->resolve($result->rows[0] ?? null);
|
$deferred->resolve($result->rows[0] ?? null);
|
||||||
});
|
});
|
||||||
@@ -44,12 +44,12 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomainByName(string $name): PromiseInterface
|
public function getHostnameByName(string $name): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database
|
$this->database
|
||||||
->query('SELECT * FROM domains WHERE domain = :name', ['name' => $name])
|
->query('SELECT * FROM hostnames WHERE hostname = :name', ['name' => $name])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$deferred->resolve($result->rows[0] ?? null);
|
$deferred->resolve($result->rows[0] ?? null);
|
||||||
});
|
});
|
||||||
@@ -57,12 +57,12 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomainsByUserId($id): PromiseInterface
|
public function getHostnamesByUserId($id): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database
|
$this->database
|
||||||
->query('SELECT * FROM domains WHERE user_id = :user_id ORDER by created_at DESC', [
|
->query('SELECT * FROM hostnames WHERE user_id = :user_id ORDER by created_at DESC', [
|
||||||
'user_id' => $id,
|
'user_id' => $id,
|
||||||
])
|
])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
@@ -72,18 +72,24 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storeDomain(array $data): PromiseInterface
|
public function storeHostname(array $data): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->getDomainByName($data['domain'])
|
$this->getHostnameByName($data['hostname'])
|
||||||
->then(function ($registeredDomain) use ($data, $deferred) {
|
->then(function ($registeredHostname) use ($data, $deferred) {
|
||||||
|
if (! is_null($registeredHostname)) {
|
||||||
|
$deferred->resolve(null);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$this->database->query("
|
$this->database->query("
|
||||||
INSERT INTO domains (user_id, domain, created_at)
|
INSERT INTO hostnames (user_id, hostname, created_at)
|
||||||
VALUES (:user_id, :domain, DATETIME('now'))
|
VALUES (:user_id, :hostname, DATETIME('now'))
|
||||||
", $data)
|
", $data)
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
$this->database->query('SELECT * FROM domains WHERE id = :id', ['id' => $result->insertId])
|
$this->database->query('SELECT * FROM hostnames 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]);
|
||||||
});
|
});
|
||||||
@@ -93,12 +99,12 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDomainsByUserIdAndName($id, $name): PromiseInterface
|
public function getHostnamesByUserIdAndName($id, $name): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database
|
$this->database
|
||||||
->query('SELECT * FROM domains WHERE user_id = :user_id AND domain = :name ORDER by created_at DESC', [
|
->query('SELECT * FROM hostnames WHERE user_id = :user_id AND hostname = :name ORDER by created_at DESC', [
|
||||||
'user_id' => $id,
|
'user_id' => $id,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
])
|
])
|
||||||
@@ -109,12 +115,12 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteDomainForUserId($userId, $domainId): PromiseInterface
|
public function deleteHostnameForUserId($userId, $hostnameId): PromiseInterface
|
||||||
{
|
{
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
|
|
||||||
$this->database->query('DELETE FROM domains WHERE id = :id AND user_id = :user_id', [
|
$this->database->query('DELETE FROM hostnames WHERE id = :id AND user_id = :user_id', [
|
||||||
'id' => $domainId,
|
'id' => $hostnameId,
|
||||||
'user_id' => $userId,
|
'user_id' => $userId,
|
||||||
])
|
])
|
||||||
->then(function (Result $result) use ($deferred) {
|
->then(function (Result $result) use ($deferred) {
|
||||||
@@ -123,13 +129,4 @@ class DatabaseDomainRepository implements DomainRepository
|
|||||||
|
|
||||||
return $deferred->promise();
|
return $deferred->promise();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateDomain($id, array $data): PromiseInterface
|
|
||||||
{
|
|
||||||
$deferred = new Deferred();
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
return $deferred->promise();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Contracts\HostnameRepository;
|
||||||
|
use App\Contracts\UserRepository;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
|
class DeleteHostnameController extends AdminController
|
||||||
|
{
|
||||||
|
protected $keepConnectionOpen = true;
|
||||||
|
|
||||||
|
/** @var HostnameRepository */
|
||||||
|
protected $hostnameRepository;
|
||||||
|
|
||||||
|
/** @var UserRepository */
|
||||||
|
protected $userRepository;
|
||||||
|
|
||||||
|
public function __construct(UserRepository $userRepository, HostnameRepository $hostnameRepository)
|
||||||
|
{
|
||||||
|
$this->userRepository = $userRepository;
|
||||||
|
$this->hostnameRepository = $hostnameRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
|
{
|
||||||
|
$this->userRepository->getUserByToken($request->get('auth_token', ''))
|
||||||
|
->then(function ($user) use ($request, $httpConnection) {
|
||||||
|
if (is_null($user)) {
|
||||||
|
$httpConnection->send(respond_json(['error' => 'The user does not exist'], 404));
|
||||||
|
$httpConnection->close();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->hostnameRepository->deleteHostnameForUserId($user['id'], $request->get('hostname'))
|
||||||
|
->then(function ($deleted) use ($httpConnection) {
|
||||||
|
$httpConnection->send(respond_json(['deleted' => $deleted], 200));
|
||||||
|
$httpConnection->close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Contracts\HostnameRepository;
|
||||||
use App\Contracts\SubdomainRepository;
|
use App\Contracts\SubdomainRepository;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -17,43 +18,36 @@ class GetUserDetailsController extends AdminController
|
|||||||
/** @var SubdomainRepository */
|
/** @var SubdomainRepository */
|
||||||
protected $subdomainRepository;
|
protected $subdomainRepository;
|
||||||
|
|
||||||
public function __construct(UserRepository $userRepository, SubdomainRepository $subdomainRepository)
|
/** @var HostnameRepository */
|
||||||
|
protected $hostnameRepository;
|
||||||
|
|
||||||
|
public function __construct(UserRepository $userRepository, SubdomainRepository $subdomainRepository, HostnameRepository $hostnameRepository)
|
||||||
{
|
{
|
||||||
$this->userRepository = $userRepository;
|
$this->userRepository = $userRepository;
|
||||||
$this->subdomainRepository = $subdomainRepository;
|
$this->subdomainRepository = $subdomainRepository;
|
||||||
|
$this->hostnameRepository = $hostnameRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$id = $request->get('id');
|
$this->userRepository
|
||||||
|
->getUserById($request->get('id'))
|
||||||
|
->then(function ($user) use ($httpConnection, $request) {
|
||||||
|
$this->subdomainRepository->getSubdomainsByUserId($request->get('id'))
|
||||||
|
->then(function ($subdomains) use ($httpConnection, $user, $request) {
|
||||||
|
$this->hostnameRepository->getHostnamesByUserId($request->get('id'))
|
||||||
|
->then(function ($hostnames) use ($httpConnection, $user, $subdomains) {
|
||||||
|
$httpConnection->send(
|
||||||
|
respond_json([
|
||||||
|
'user' => $user,
|
||||||
|
'subdomains' => $subdomains,
|
||||||
|
'hostnames' => $hostnames,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
if (! is_numeric($id)) {
|
$httpConnection->close();
|
||||||
$promise = $this->userRepository->getUserByToken($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) {
|
|
||||||
$httpConnection->send(
|
|
||||||
respond_json([
|
|
||||||
'user' => $user,
|
|
||||||
'subdomains' => $subdomains,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
$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',
|
||||||
])));
|
])));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,32 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Server\Http\Controllers\Admin;
|
namespace App\Server\Http\Controllers\Admin;
|
||||||
|
|
||||||
use App\Contracts\DomainRepository;
|
use App\Contracts\HostnameRepository;
|
||||||
use App\Contracts\UserRepository;
|
use App\Contracts\UserRepository;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Ratchet\ConnectionInterface;
|
use Ratchet\ConnectionInterface;
|
||||||
|
|
||||||
class StoreDomainController extends AdminController
|
class StoreHostnameController extends AdminController
|
||||||
{
|
{
|
||||||
protected $keepConnectionOpen = true;
|
protected $keepConnectionOpen = true;
|
||||||
|
|
||||||
/** @var DomainRepository */
|
/** @var HostnameRepository */
|
||||||
protected $domainRepository;
|
protected $hostnameRepository;
|
||||||
|
|
||||||
/** @var UserRepository */
|
/** @var UserRepository */
|
||||||
protected $userRepository;
|
protected $userRepository;
|
||||||
|
|
||||||
public function __construct(UserRepository $userRepository, DomainRepository $domainRepository)
|
public function __construct(UserRepository $userRepository, HostnameRepository $hostnameRepository)
|
||||||
{
|
{
|
||||||
$this->userRepository = $userRepository;
|
$this->userRepository = $userRepository;
|
||||||
$this->domainRepository = $domainRepository;
|
$this->hostnameRepository = $hostnameRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle(Request $request, ConnectionInterface $httpConnection)
|
public function handle(Request $request, ConnectionInterface $httpConnection)
|
||||||
{
|
{
|
||||||
$validator = Validator::make($request->all(), [
|
$validator = Validator::make($request->all(), [
|
||||||
'domain' => 'required',
|
'hostname' => 'required',
|
||||||
], [
|
], [
|
||||||
'required' => 'The :attribute field is required.',
|
'required' => 'The :attribute field is required.',
|
||||||
]);
|
]);
|
||||||
@@ -39,8 +39,7 @@ class StoreDomainController 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));
|
||||||
@@ -49,8 +48,8 @@ class StoreDomainController extends AdminController
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($user['can_specify_domains'] === 0) {
|
if ($user['can_specify_hostnames'] === 0) {
|
||||||
$httpConnection->send(respond_json(['error' => 'The user is not allowed to reserve custom domains.'], 401));
|
$httpConnection->send(respond_json(['error' => 'The user is not allowed to reserve hostnames.'], 401));
|
||||||
$httpConnection->close();
|
$httpConnection->close();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -58,13 +57,19 @@ class StoreDomainController extends AdminController
|
|||||||
|
|
||||||
$insertData = [
|
$insertData = [
|
||||||
'user_id' => $user['id'],
|
'user_id' => $user['id'],
|
||||||
'domain' => $request->get('domain'),
|
'hostname' => $request->get('hostname'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->domainRepository
|
$this->hostnameRepository
|
||||||
->storeDomain($insertData)
|
->storeHostname($insertData)
|
||||||
->then(function ($domain) use ($httpConnection) {
|
->then(function ($hostname) use ($httpConnection) {
|
||||||
$httpConnection->send(respond_json(['domain' => $domain], 200));
|
if (is_null($hostname)) {
|
||||||
|
$httpConnection->send(respond_json(['error' => 'The hostname is already taken.'], 422));
|
||||||
|
$httpConnection->close();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$httpConnection->send(respond_json(['hostname' => $hostname], 200));
|
||||||
$httpConnection->close();
|
$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,10 @@ 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_hostnames' => (int) $request->get('can_specify_hostnames'),
|
||||||
'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,19 @@
|
|||||||
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\HostnameRepository;
|
||||||
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\Connections\ConnectionConfiguration;
|
||||||
use App\Server\Exceptions\NoFreePortAvailable;
|
use App\Server\Exceptions\NoFreePortAvailable;
|
||||||
use Illuminate\Support\Arr;
|
use Illuminate\Support\Str;
|
||||||
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 function React\Promise\reject;
|
||||||
|
use function React\Promise\resolve as resolvePromise;
|
||||||
use stdClass;
|
use stdClass;
|
||||||
|
|
||||||
class ControlMessageController implements MessageComponentInterface
|
class ControlMessageController implements MessageComponentInterface
|
||||||
@@ -28,19 +29,15 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
/** @var SubdomainRepository */
|
/** @var SubdomainRepository */
|
||||||
protected $subdomainRepository;
|
protected $subdomainRepository;
|
||||||
|
|
||||||
/** @var DomainRepository */
|
/** @var HostnameRepository */
|
||||||
protected $domainRepository;
|
protected $hostnameRepository;
|
||||||
|
|
||||||
/** @var Configuration */
|
public function __construct(ConnectionManager $connectionManager, UserRepository $userRepository, SubdomainRepository $subdomainRepository, HostnameRepository $hostnameRepository)
|
||||||
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->hostnameRepository = $hostnameRepository;
|
||||||
$this->configuration = $configuration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -97,42 +94,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,56 +112,26 @@ 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->hasValidConfiguration($connection, $data, $user)
|
||||||
->then(function () use ($connection, $data, $user) {
|
->then(function (ConnectionConfiguration $configuration) use ($data, $connection) {
|
||||||
return $this->hasValidSubdomain($connection, $data->subdomain, $user, $data->server_host);
|
$data->subdomain = $configuration->getSubdomain();
|
||||||
})
|
$data->hostname = $configuration->getHostname();
|
||||||
->then(function ($subdomain) use ($data, $connection, $user) {
|
|
||||||
if ($subdomain === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data->subdomain = $subdomain;
|
$connectionInfo = $this->connectionManager->storeConnection($data->host, $data->subdomain, $data->hostname, $connection);
|
||||||
|
|
||||||
$connectionInfo = $this->connectionManager->storeConnection($data->host, $data->subdomain, $data->server_host, $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);
|
|
||||||
})
|
|
||||||
->then(function ($connectionInfo) use ($connection, $user) {
|
|
||||||
$connection->send(json_encode([
|
$connection->send(json_encode([
|
||||||
'event' => 'authenticated',
|
'event' => 'authenticated',
|
||||||
'data' => [
|
'data' => [
|
||||||
'message' => $connectionInfo->message,
|
'message' => config('expose.admin.messages.message_of_the_day'),
|
||||||
'subdomain' => $connectionInfo->subdomain,
|
'subdomain' => $connectionInfo->subdomain,
|
||||||
'server_host' => $connectionInfo->serverHost,
|
'hostname' => $connectionInfo->hostname,
|
||||||
'user' => $user,
|
|
||||||
'client_id' => $connectionInfo->client_id,
|
'client_id' => $connectionInfo->client_id,
|
||||||
],
|
],
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,8 +158,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,
|
||||||
@@ -269,7 +200,7 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
protected function verifyAuthToken(ConnectionInterface $connection): PromiseInterface
|
protected function verifyAuthToken(ConnectionInterface $connection): PromiseInterface
|
||||||
{
|
{
|
||||||
if (config('expose.admin.validate_auth_tokens') !== true) {
|
if (config('expose.admin.validate_auth_tokens') !== true) {
|
||||||
return \React\Promise\resolve(null);
|
return resolvePromise(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
$deferred = new Deferred();
|
$deferred = new Deferred();
|
||||||
@@ -282,80 +213,37 @@ 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,
|
||||||
],
|
],
|
||||||
]));
|
]));
|
||||||
|
|
||||||
return \React\Promise\resolve(null);
|
return resolvePromise(ConnectionConfiguration::withSubdomain(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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([
|
||||||
@@ -366,12 +254,12 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
]));
|
]));
|
||||||
$connection->close();
|
$connection->close();
|
||||||
|
|
||||||
return \React\Promise\resolve(false);
|
return reject(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);
|
||||||
|
|
||||||
@@ -383,35 +271,84 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
]));
|
]));
|
||||||
$connection->close();
|
$connection->close();
|
||||||
|
|
||||||
return \React\Promise\resolve(false);
|
return reject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return \React\Promise\resolve($subdomain);
|
return resolvePromise(ConnectionConfiguration::withSubdomain($subdomain));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return \React\Promise\resolve($subdomain);
|
return resolvePromise(ConnectionConfiguration::withSubdomain($subdomain));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasValidHostname(ConnectionInterface $connection, string $hostname, ?array $user): PromiseInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Check if the user can specify a custom hostname in the first place.
|
||||||
|
*/
|
||||||
|
if (! is_null($user) && $user['can_specify_hostnames'] === 0) {
|
||||||
|
$connection->send(json_encode([
|
||||||
|
'event' => 'info',
|
||||||
|
'data' => [
|
||||||
|
'message' => config('expose.admin.messages.custom_hostname_unauthorized').PHP_EOL,
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
|
||||||
|
return reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given hostname is reserved for a different user.
|
||||||
|
*/
|
||||||
|
return $this->hostnameRepository->getHostnamesByUserId($user['id'])
|
||||||
|
->then(function ($foundHostnames) use ($connection, $hostname) {
|
||||||
|
$foundHostname = collect($foundHostnames)->first(function ($foundHostname) use ($hostname) {
|
||||||
|
return Str::is($foundHostname['hostname'], $hostname);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is_null($foundHostname)) {
|
||||||
|
$message = config('expose.admin.messages.hostname_invalid');
|
||||||
|
$message = str_replace(':hostname', $hostname, $message);
|
||||||
|
|
||||||
|
$connection->send(json_encode([
|
||||||
|
'event' => 'hostnameTaken',
|
||||||
|
'data' => [
|
||||||
|
'message' => $message,
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
$connection->close();
|
||||||
|
|
||||||
|
return reject(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
$controlConnection = $this->connectionManager->findControlConnectionForHostname($hostname);
|
||||||
|
|
||||||
|
if (! is_null($controlConnection)) {
|
||||||
|
$message = config('expose.admin.messages.hostname_taken');
|
||||||
|
$message = str_replace(':hostname', $hostname, $message);
|
||||||
|
|
||||||
|
$connection->send(json_encode([
|
||||||
|
'event' => 'hostnameTaken',
|
||||||
|
'data' => [
|
||||||
|
'message' => $message,
|
||||||
|
],
|
||||||
|
]));
|
||||||
|
$connection->close();
|
||||||
|
|
||||||
|
return reject(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvePromise(ConnectionConfiguration::withHostname($hostname));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
||||||
@@ -421,4 +358,13 @@ class ControlMessageController implements MessageComponentInterface
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function hasValidConfiguration(ConnectionInterface $connection, $data, $user)
|
||||||
|
{
|
||||||
|
if (isset($data->hostname) && ! is_null($data->hostname)) {
|
||||||
|
return $this->hasValidHostname($connection, $data->hostname, $user);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->hasValidSubdomain($connection, $data->subdomain, $user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,22 +27,18 @@ 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);
|
$hostname = $request->getHost();
|
||||||
|
|
||||||
if (is_null($subdomain)) {
|
if (is_null($subdomain) && $hostname === $this->configuration->hostname()) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
respond_html($this->getView($httpConnection, 'server.homepage'), 200)
|
respond_html($this->getView($httpConnection, 'server.homepage'), 200)
|
||||||
);
|
);
|
||||||
@@ -52,7 +47,11 @@ class TunnelMessageController extends Controller
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$controlConnection = $this->connectionManager->findControlConnectionForSubdomainAndServerHost($subdomain, $serverHost);
|
if (! is_null($subdomain)) {
|
||||||
|
$controlConnection = $this->connectionManager->findControlConnectionForSubdomain($subdomain);
|
||||||
|
} else {
|
||||||
|
$controlConnection = $this->connectionManager->findControlConnectionForHostname($hostname);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_null($controlConnection)) {
|
if (is_null($controlConnection)) {
|
||||||
$httpConnection->send(
|
$httpConnection->send(
|
||||||
@@ -63,23 +62,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,19 +112,25 @@ 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()}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (empty($controlConnection->subdomain)) {
|
||||||
|
$originalHost = $controlConnection->hostname;
|
||||||
|
} else {
|
||||||
|
$originalHost = "{$controlConnection->subdomain}.{$host}";
|
||||||
|
}
|
||||||
|
|
||||||
$request->headers->set('Host', $controlConnection->host);
|
$request->headers->set('Host', $controlConnection->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', uniqid());
|
||||||
$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', $originalHost);
|
||||||
$request->headers->set('X-Forwarded-Host', "{$controlConnection->subdomain}.{$host}");
|
$request->headers->set('X-Forwarded-Host', $originalHost);
|
||||||
|
|
||||||
return $request;
|
return $request;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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_specify_hostnames, can_share_tcp_ports, created_at)
|
||||||
if (is_null($existingUser)) {
|
VALUES (:name, :auth_token, :can_specify_subdomains, :can_specify_hostnames, :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,7 +234,7 @@ return [
|
|||||||
|
|
||||||
'subdomain_repository' => \App\Server\SubdomainRepository\DatabaseSubdomainRepository::class,
|
'subdomain_repository' => \App\Server\SubdomainRepository\DatabaseSubdomainRepository::class,
|
||||||
|
|
||||||
'logger_repository' => \App\Server\LoggerRepository\NullLogger::class,
|
'hostname_repository' => \App\Server\HostnameRepository\DatabaseHostnameRepository::class,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -359,35 +247,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'),
|
|
||||||
],
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE users ADD can_specify_hostnames BOOLEAN DEFAULT 1;
|
||||||
@@ -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;
|
|
||||||
7
database/migrations/06_create_hostnames_table.sql
Normal file
7
database/migrations/06_create_hostnames_table.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS hostnames (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
hostname STRING NOT NULL,
|
||||||
|
created_at DATETIME,
|
||||||
|
updated_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
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user