diff --git a/app/Server/Http/Controllers/TunnelMessageController.php b/app/Server/Http/Controllers/TunnelMessageController.php index a2635e5..f3e107f 100644 --- a/app/Server/Http/Controllers/TunnelMessageController.php +++ b/app/Server/Http/Controllers/TunnelMessageController.php @@ -70,11 +70,9 @@ class TunnelMessageController extends Controller protected function detectSubdomain(Request $request): ?string { - if (substr_count($request->header('Host'), '.') === 1) { - return null; - } + $serverHost = $this->detectServerHost($request); - $subdomain = Str::before($request->header('Host'), '.'); + $subdomain = Str::before($request->header('Host'), '.'.$serverHost); return $subdomain === $request->header('Host') ? null : $subdomain; } diff --git a/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php b/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php index d540eeb..eaf3d00 100644 --- a/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php +++ b/app/Server/SubdomainRepository/DatabaseSubdomainRepository.php @@ -135,7 +135,7 @@ class DatabaseSubdomainRepository implements SubdomainRepository { $deferred = new Deferred(); - $this->database->query('DELETE FROM subdomains WHERE id = :id AND user_id = :user_id', [ + $this->database->query('DELETE FROM subdomains WHERE (id = :id OR subdomain = :id) AND user_id = :user_id', [ 'id' => $subdomainId, 'user_id' => $userId, ]) diff --git a/app/Server/UserRepository/DatabaseUserRepository.php b/app/Server/UserRepository/DatabaseUserRepository.php index 62ce315..8f03031 100644 --- a/app/Server/UserRepository/DatabaseUserRepository.php +++ b/app/Server/UserRepository/DatabaseUserRepository.php @@ -150,15 +150,38 @@ class DatabaseUserRepository implements UserRepository { $deferred = new Deferred(); - $this->database->query(" + $this->getUserByToken($data['auth_token']) + ->then(function ($existingUser) use ($data, $deferred) { + if (is_null($existingUser)) { + $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) - ->then(function (Result $result) use ($deferred) { - $this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId]) - ->then(function (Result $result) use ($deferred) { - $deferred->resolve($result->rows[0]); - }); + ->then(function (Result $result) use ($deferred) { + $this->database->query('SELECT * FROM users WHERE id = :id', ['id' => $result->insertId]) + ->then(function (Result $result) use ($deferred) { + $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(); diff --git a/tests/Feature/Server/ApiTest.php b/tests/Feature/Server/ApiTest.php index 879963e..9026bc2 100644 --- a/tests/Feature/Server/ApiTest.php +++ b/tests/Feature/Server/ApiTest.php @@ -65,6 +65,97 @@ class ApiTest extends TestCase $this->assertSame([], $users[0]->sites); } + /** @test */ + public function it_can_specify_a_token_when_creating_a_user() + { + /** @var Response $response */ + $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'name' => 'Marcel', + 'token' => 'my-token', + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $users = $body->paginated->users; + + $this->assertCount(1, $users); + $this->assertSame('Marcel', $users[0]->name); + $this->assertSame('my-token', $users[0]->auth_token); + $this->assertSame([], $users[0]->sites); + } + + /** @test */ + public function it_updates_users_instead_of_creating_new_ones() + { + /** @var Response $response */ + $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'name' => 'Marcel', + 'token' => 'my-token', + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $users = $body->paginated->users; + + $this->assertCount(1, $users); + $this->assertSame('Marcel', $users[0]->name); + $this->assertSame('my-token', $users[0]->auth_token); + $this->assertSame(0, $users[0]->can_specify_subdomains); + $this->assertSame(0, $users[0]->can_specify_domains); + $this->assertSame(0, $users[0]->can_share_tcp_ports); + $this->assertSame([], $users[0]->sites); + + $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'name' => 'Marcel Changed', + 'token' => 'my-token', + 'can_specify_subdomains' => 1, + 'can_specify_domains' => 1, + 'can_share_tcp_ports' => 1, + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $users = $body->paginated->users; + + $this->assertCount(1, $users); + $this->assertSame('Marcel Changed', $users[0]->name); + $this->assertSame('my-token', $users[0]->auth_token); + $this->assertSame(1, $users[0]->can_specify_subdomains); + $this->assertSame(1, $users[0]->can_specify_domains); + $this->assertSame(1, $users[0]->can_share_tcp_ports); + $this->assertSame([], $users[0]->sites); + } + /** @test */ public function it_can_specify_tokens_when_creating_a_user() { @@ -288,6 +379,51 @@ class ApiTest extends TestCase $this->assertCount(0, $subdomains); } + /** @test */ + public function it_can_delete_subdomains_by_name() + { + /** @var Response $response */ + $response = $this->await($this->browser->post('http://127.0.0.1:8080/api/users', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'name' => 'Marcel', + 'can_specify_subdomains' => 1, + ]))); + + $user = json_decode($response->getBody()->getContents())->user; + + $response = $this->await($this->browser->post('http://127.0.0.1:8080/api/subdomains', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'subdomain' => 'reserved', + 'auth_token' => $user->auth_token, + ]))); + + $this->await($this->browser->delete('http://127.0.0.1:8080/api/subdomains/reserved', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ], json_encode([ + 'auth_token' => $user->auth_token, + ]))); + + /** @var Response $response */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/api/users/1', [ + 'Host' => 'expose.localhost', + 'Authorization' => base64_encode('username:secret'), + 'Content-Type' => 'application/json', + ])); + + $body = json_decode($response->getBody()->getContents()); + $subdomains = $body->subdomains; + + $this->assertCount(0, $subdomains); + } + /** @test */ public function it_can_not_reserve_an_already_reserved_subdomain() { diff --git a/tests/Feature/Server/TunnelTest.php b/tests/Feature/Server/TunnelTest.php index 0caefa3..20b2fdc 100644 --- a/tests/Feature/Server/TunnelTest.php +++ b/tests/Feature/Server/TunnelTest.php @@ -64,6 +64,17 @@ class TunnelTest extends TestCase ])); } + /** @test */ + public function it_returns_404_for_non_existing_clients_on_custom_hosts() + { + $this->expectException(ResponseException::class); + $this->expectExceptionMessage(404); + + $this->await($this->browser->get('http://127.0.0.1:8080/', [ + 'Host' => 'tunnel.share.beyondco.de', + ])); + } + /** @test */ public function it_sends_incoming_requests_to_the_connected_client() { @@ -91,6 +102,33 @@ class TunnelTest extends TestCase $this->assertSame('Hello World!', $response->getBody()->getContents()); } + /** @test */ + public function it_sends_incoming_requests_to_the_connected_client_on_custom_hosts() + { + $this->app['config']['expose.admin.validate_auth_tokens'] = false; + + $this->createTestHttpServer(); + + $this->app['config']['expose.admin.validate_auth_tokens'] = false; + + /** + * We create an expose client that connects to our server and shares + * the created test HTTP server. + */ + $client = $this->createClient(); + $this->await($client->connectToServer('127.0.0.1:8085', 'tunnel', 'share.beyondco.de')); + + /** + * Once the client is connected, we perform a GET request on the + * created tunnel. + */ + $response = $this->await($this->browser->get('http://127.0.0.1:8080/', [ + 'Host' => 'tunnel.share.beyondco.de', + ])); + + $this->assertSame('Hello World!', $response->getBody()->getContents()); + } + /** @test */ public function it_sends_incoming_requests_to_the_connected_client_via_tcp() {