diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index d749946826..3b061a85c2 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -54,20 +54,14 @@ App::post('/v1/proxy/rules') $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - switch ($resourceType) { - case 'function': - if (str_ends_with($domain, $functionsDomain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions domain or it\'s subdomain to specific resource. Please use different domain.'); - } - break; - case 'site': - if (str_ends_with($domain, $sitesDomain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions domain or it\'s subdomain to specific resource. Please use different domain.'); - } - break; + if ( + ($functionsDomain !== '' && str_ends_with($domain, $functionsDomain)) || + ($sitesDomain !== '' && str_ends_with($domain, $sitesDomain)) + ) { + // TODO: Refactor later + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions or sites domain or their subdomains to a specific resource. Please use a different domain.'); } - if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'This domain name is not allowed. Please pick another one.'); } @@ -111,8 +105,7 @@ App::post('/v1/proxy/rules') break; case 'site': if (empty($resourceId)) { - // todo: use site relecant exception - throw new Exception(Exception::FUNCTION_NOT_FOUND); + throw new Exception(Exception::SITE_NOT_FOUND); } $site = $dbForProject->getDocument('sites', $resourceId); @@ -378,3 +371,40 @@ App::patch('/v1/proxy/rules/:ruleId/verification') $response->dynamic($rule, Response::MODEL_PROXY_RULE); }); + +App::get('/v1/proxy/subdomains') + ->desc('Check if subdomain is available') + ->groups(['api', 'proxy']) + ->label('scope', 'rules.read') + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.namespace', 'proxy') + ->label('sdk.method', 'checkSubdomain') + ->label('sdk.description', '/docs/references/proxy/check-subdomain.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_NONE) + ->param('resourceType', null, new WhiteList(['function', 'site']), 'Action definition for the rule. Possible values are "function" and "site"') + ->param('subdomain', '', new Text(256), 'Subdomain name.') + ->inject('response') + ->inject('dbForConsole') + ->action(function (string $resourceType, string $subdomain, Response $response, Database $dbForConsole) { + //TODO: Add tests for this endpoint + $resourceDomain = $resourceType === 'site' ? System::getEnv('_APP_DOMAIN_SITES', '') : System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + $domain = $subdomain . '.' . $resourceDomain; + + $document = $dbForConsole->findOne('rules', [ + Query::equal('domain', [$domain]), + ]); + + if ($document && !$document->isEmpty()) { + return $response->json([ + 'success' => false, + 'message' => 'Subdomain is already taken.' + ], Response::STATUS_CODE_CONFLICT); + } + + return $response->json([ + 'success' => true, + 'message' => 'Subdomain is available.' + ], Response::STATUS_CODE_OK); + }); diff --git a/app/controllers/api/vcs.php b/app/controllers/api/vcs.php index 1a3b891b4c..9021c6c518 100644 --- a/app/controllers/api/vcs.php +++ b/app/controllers/api/vcs.php @@ -200,7 +200,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId 'resourceInternalId' => $resourceInternalId, 'resourceType' => $resourceCollection, 'entrypoint' => $resource->getAttribute('entrypoint', ''), - 'commands' => $resource->getAttribute('commands', []), + 'commands' => $resource->getAttribute('commands', ''), 'installCommand' => $resource->getAttribute('installCommand', ''), 'buildCommand' => $resource->getAttribute('buildCommand', ''), 'outputDirectory' => $resource->getAttribute('outputDirectory', ''), diff --git a/src/Appwrite/Platform/Modules/Sites/Http/Sites/CreateSite.php b/src/Appwrite/Platform/Modules/Sites/Http/Sites/CreateSite.php index cec416dab0..9243e8574b 100644 --- a/src/Appwrite/Platform/Modules/Sites/Http/Sites/CreateSite.php +++ b/src/Appwrite/Platform/Modules/Sites/Http/Sites/CreateSite.php @@ -18,6 +18,7 @@ use Utopia\Database\Document; use Utopia\Database\Helpers\ID; use Utopia\Database\Helpers\Permission; use Utopia\Database\Helpers\Role; +use Utopia\Database\Query; use Utopia\Database\Validator\Authorization; use Utopia\Platform\Action; use Utopia\Platform\Scope\HTTP; @@ -62,6 +63,7 @@ class CreateSite extends Base ->param('installCommand', '', new Text(8192, 0), 'Install Command.', true) ->param('buildCommand', '', new Text(8192, 0), 'Build Command.', true) ->param('outputDirectory', '', new Text(8192, 0), 'Output Directory for site.', true) + ->param('subdomain', '', new CustomId(), 'Unique custom sub-domain. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.', true) ->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true) ->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the site.', true) ->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the site.', true) @@ -89,8 +91,27 @@ class CreateSite extends Base ->callback([$this, 'action']); } - public function action(string $siteId, string $name, string $framework, bool $enabled, string $installCommand, string $buildCommand, string $outputDirectory, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) + public function action(string $siteId, string $name, string $framework, bool $enabled, string $installCommand, string $buildCommand, string $outputDirectory, string $subdomain, string $installationId, string $providerRepositoryId, string $providerBranch, bool $providerSilentMode, string $providerRootDirectory, string $templateRepository, string $templateOwner, string $templateRootDirectory, string $templateVersion, string $specification, Request $request, Response $response, Database $dbForProject, Document $project, Document $user, Event $queueForEvents, Build $queueForBuilds, Database $dbForConsole, GitHub $github) { + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + $ruleId = ''; + $routeSubdomain = ''; + $domain = ''; + + if (!empty($sitesDomain)) { + $ruleId = ID::unique(); + $routeSubdomain = $subdomain ?? ID::unique(); + $domain = "{$routeSubdomain}.{$sitesDomain}"; + + $subdomain = Authorization::skip(fn () => $dbForConsole->findOne('rules', [ + Query::equal('domain', [$domain]) + ])); + + if (!empty($subdomain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Subdomain already exists. Please choose a different subdomain.'); + } + } + $siteId = ($siteId == 'unique()') ? ID::unique() : $siteId; $allowList = \array_filter(\explode(',', System::getEnv('_APP_SITES_FRAMEWORKS', ''))); @@ -209,12 +230,7 @@ class CreateSite extends Base ->setTemplate($template); } - $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); if (!empty($sitesDomain)) { - $ruleId = ID::unique(); - $routeSubdomain = ID::unique(); - $domain = "{$routeSubdomain}.{$sitesDomain}"; - $rule = Authorization::skip( fn () => $dbForConsole->createDocument('rules', new Document([ '$id' => $ruleId, diff --git a/tests/e2e/Services/Health/HealthCustomServerTest.php b/tests/e2e/Services/Health/HealthCustomServerTest.php index 8360af542e..7a8b3ab7cb 100644 --- a/tests/e2e/Services/Health/HealthCustomServerTest.php +++ b/tests/e2e/Services/Health/HealthCustomServerTest.php @@ -467,7 +467,7 @@ class HealthCustomServerTest extends Scope $this->assertEquals(200, $response['headers']['status-code']); $this->assertEquals('/CN=appwrite.io', $response['body']['name']); $this->assertEquals('appwrite.io', $response['body']['subjectSN']); - $this->assertEquals("Let's Encrypt", $response['body']['issuerOrganisation']); + $this->assertEquals('Google Trust Services', $response['body']['issuerOrganisation']); $this->assertIsInt($response['body']['validFrom']); $this->assertIsInt($response['body']['validTo']); diff --git a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php index cc976b78f6..f81290e707 100644 --- a/tests/e2e/Services/Projects/ProjectsCustomServerTest.php +++ b/tests/e2e/Services/Projects/ProjectsCustomServerTest.php @@ -44,5 +44,15 @@ class ProjectsCustomServerTest extends Scope ]); $this->assertEquals(400, $response['headers']['status-code']); + + // prevent sites domain + $sitesDomain = System::getEnv('_APP_DOMAIN_SITES', ''); + + $response = $this->client->call(Client::METHOD_POST, '/proxy/rules', $headers, [ + 'resourceType' => 'api', + 'domain' => $sitesDomain, + ]); + + $this->assertEquals(400, $response['headers']['status-code']); } }