diff --git a/app/config/roles.php b/app/config/roles.php index a4f05808f..41c313ff3 100644 --- a/app/config/roles.php +++ b/app/config/roles.php @@ -3,7 +3,6 @@ use Appwrite\Auth\Auth; $member = [ - 'global', 'public', 'home', 'console', @@ -25,7 +24,6 @@ $member = [ ]; $admins = [ - 'global', 'graphql', 'teams.read', 'teams.write', diff --git a/app/controllers/general.php b/app/controllers/general.php index e3b47098b..21260e050 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -47,8 +47,6 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleRequest, Request $request, Response $response) { - $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); - $host = $request->getHostname() ?? ''; $route = Authorization::skip( @@ -59,15 +57,12 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques )[0] ?? null; if ($route === null) { - $mainDomain = App::getEnv('_APP_DOMAIN', ''); - - if ($mainDomain === 'localhost') { - throw new AppwriteException(AppwriteException::ROUTER_DOMAIN_NOT_CONFIGURED); - } else { - throw new AppwriteException(AppwriteException::ROUTER_HOST_NOT_FOUND); - } + // Act as API - no Proxy logic + return false; } + $utopia->getRoute()?->label('error', __DIR__ . '/../views/general/error.phtml'); + $projectId = $route->getAttribute('projectId'); $project = Authorization::skip( fn () => $dbForConsole->getDocument('projects', $projectId) @@ -163,6 +158,7 @@ function router(App $utopia, Database $dbForConsole, SwooleRequest $swooleReques return true; } elseif ($type === 'api') { + $utopia->getRoute()?->label('error', ''); return false; } else { throw new AppwriteException(AppwriteException::GENERAL_SERVER_ERROR, 'Unknown resource type ' . $type); @@ -193,7 +189,7 @@ App::init() $host = $request->getHostname() ?? ''; $mainDomain = App::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain - if ($host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { + if ($host !== $mainDomain) { if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) { return; } @@ -234,6 +230,61 @@ App::init() Request::setFilter(null); } + $domain = $request->getHostname(); + $domains = Config::getParam('domains', []); + if (!array_key_exists($domain, $domains)) { + $domain = new Domain(!empty($domain) ? $domain : ''); + + if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { + $domains[$domain->get()] = false; + Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.'); + } elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) { + Console::warning('Skipping SSL certificates generation on ACME challenge.'); + } else { + Authorization::disable(); + + $envDomain = App::getEnv('_APP_DOMAIN', ''); + $mainDomain = null; + if (!empty($envDomain) && $envDomain !== 'localhost') { + $mainDomain = $envDomain; + } else { + $domainDocument = $dbForConsole->findOne('rules', [Query::orderAsc('$id')]); + $mainDomain = $domainDocument ? $domainDocument->getAttribute('domain') : $domain->get(); + } + + if ($mainDomain !== $domain->get()) { + Console::warning($domain->get() . ' is not a main domain. Skipping SSL certificate generation.'); + } else { + $domainDocument = $dbForConsole->findOne('rules', [ + Query::equal('domain', [$domain->get()]) + ]); + + if (!$domainDocument) { + $domainDocument = new Document([ + 'domain' => $domain->get(), + 'resourceType' => 'api', + 'status' => 'verifying', + 'projectId' => 'console', + 'projectInternalId' => 'console' + ]); + + $domainDocument = $dbForConsole->createDocument('rules', $domainDocument); + + Console::info('Issuing a TLS certificate for the main domain (' . $domain->get() . ') in a few seconds...'); + + (new Certificate()) + ->setDomain($domainDocument) + ->setSkipRenewCheck(true) + ->trigger(); + } + } + $domains[$domain->get()] = true; + + Authorization::reset(); // ensure authorization is re-enabled + } + Config::setParam('domains', $domains); + } + $localeParam = (string) $request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); if (\in_array($localeParam, $localeCodes)) { $locale->setDefault($localeParam); @@ -327,7 +378,7 @@ App::init() * @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers */ if (App::getEnv('_APP_OPTIONS_FORCE_HTTPS', 'disabled') === 'enabled') { // Force HTTPS - if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== 'localhost' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // Localhost allowed for proxy, APP_HOSTNAME_INTERNAL allowed for migrations + if ($request->getProtocol() !== 'https' && ($swooleRequest->header['host'] ?? '') !== APP_HOSTNAME_INTERNAL) { // APP_HOSTNAME_INTERNAL allowed for migrations if ($request->getMethod() !== Request::METHOD_GET) { throw new AppwriteException(AppwriteException::GENERAL_PROTOCOL_UNSUPPORTED, 'Method unsupported over HTTP.'); } @@ -497,7 +548,7 @@ App::options() $host = $request->getHostname() ?? ''; $mainDomain = App::getEnv('_APP_DOMAIN', ''); // Only run Router when external domain - if ($host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { + if ($host !== $mainDomain) { if (router($utopia, $dbForConsole, $swooleRequest, $request, $response)) { return; } @@ -763,7 +814,7 @@ include_once __DIR__ . '/shared/api/auth.php'; App::wildcard() ->groups(['api']) - ->label('scope', 'global') + ->label('scope', 'public') ->action(function () { throw new AppwriteException(AppwriteException::GENERAL_ROUTE_NOT_FOUND); }); diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index d7496b03b..4a6f15df3 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -31,13 +31,6 @@ App::get('/console/*') ->inject('request') ->inject('response') ->action(function (Request $request, Response $response) { - // Serve static files (console) only for main domain - $host = $request->getHostname() ?? ''; - $mainDomain = App::getEnv('_APP_DOMAIN', ''); - if ($host !== $mainDomain && $host !== 'localhost' && $host !== APP_HOSTNAME_INTERNAL) { - throw new Exception(Exception::GENERAL_ROUTE_NOT_FOUND); - } - $fallback = file_get_contents(__DIR__ . '/../../../console/index.html'); // Card SSR diff --git a/app/http.php b/app/http.php index 053db20ff..fe1ed4872 100644 --- a/app/http.php +++ b/app/http.php @@ -229,21 +229,16 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo $request = new Request($swooleRequest); $response = new Response($swooleResponse); - // Serve static files (console) only for main domain - $host = $request->getHostname() ?? ''; - $mainDomain = App::getEnv('_APP_DOMAIN', ''); - if ($host === $mainDomain || $host === 'localhost') { - if (Files::isFileLoaded($request->getURI())) { - $time = (60 * 60 * 24 * 365 * 2); // 45 days cache + if (Files::isFileLoaded($request->getURI())) { + $time = (60 * 60 * 24 * 365 * 2); // 45 days cache - $response - ->setContentType(Files::getFileMimeType($request->getURI())) - ->addHeader('Cache-Control', 'public, max-age=' . $time) - ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache - ->send(Files::getFileContents($request->getURI())); + $response + ->setContentType(Files::getFileMimeType($request->getURI())) + ->addHeader('Cache-Control', 'public, max-age=' . $time) + ->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache + ->send(Files::getFileContents($request->getURI())); - return; - } + return; } $app = new App('UTC'); diff --git a/app/workers/certificates.php b/app/workers/certificates.php index 5a1e173b3..d390ed442 100644 --- a/app/workers/certificates.php +++ b/app/workers/certificates.php @@ -438,6 +438,12 @@ class CertificatesV1 extends Worker $this->dbForConsole->updateDocument('rules', $rule->getId(), $rule); $projectId = $rule->getAttribute('projectId'); + + // Skip events for console project (triggered by auto-ssl generation for 1 click setups) + if ($projectId === 'console') { + return; + } + $project = $this->dbForConsole->getDocument('projects', $projectId); /** Trigger Webhook */