From 227d61e19420117d9155a95cf52ca46eb69697a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 4 Feb 2026 14:45:38 +0100 Subject: [PATCH 1/3] Support trusted console projects --- .env | 1 + app/init/resources.php | 18 +++++- docker-compose.yml | 2 + .../Projects/ProjectsConsoleClientTest.php | 62 +++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/.env b/.env index 1ca5d5fc4e..746d6677ce 100644 --- a/.env +++ b/.env @@ -25,6 +25,7 @@ _APP_OPENSSL_KEY_V1=your-secret-key _APP_DNS=172.16.238.100 # CoreDNS _APP_DOMAIN=appwrite.test _APP_CONSOLE_DOMAIN=localhost +_APP_CONSOLE_TRUSTED_PROJECTS=trusted-project,another-trusted-project _APP_DOMAIN_FUNCTIONS=functions.localhost _APP_DOMAIN_SITES=sites.localhost,rebranded.localhost _APP_DOMAIN_TARGET_CNAME=cname.localhost diff --git a/app/init/resources.php b/app/init/resources.php index 163a26c876..e0a4f34ac1 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -253,7 +253,23 @@ Http::setResource('rule', function (Request $request, Database $dbForPlatform, D ]) ?? new Document(); }); - if ($rule->getAttribute('projectInternalId') !== $project->getSequence()) { + $permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence(); + + // Allow trusted projects; Used for Console (website) previews + if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) { + $trustedProjects = []; + foreach (\explode(',', System::getEnv('_APP_CONSOLE_TRUSTED_PROJECTS', '')) as $trustedProject) { + if (empty($trustedProject)) { + continue; + } + $trustedProjects[] = $trustedProject; + } + if (\in_array($rule->getAttribute('projectId', ''), $trustedProjects)) { + $permitsCurrentProject = true; + } + } + + if (!$permitsCurrentProject) { return new Document(); } diff --git a/docker-compose.yml b/docker-compose.yml index 0eee94a999..cc761f20c3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -133,6 +133,7 @@ services: - _APP_OPENSSL_KEY_V1 - _APP_DOMAIN - _APP_CONSOLE_DOMAIN + - _APP_CONSOLE_TRUSTED_PROJECTS - _APP_DOMAIN_TARGET_CNAME - _APP_DOMAIN_TARGET_AAAA - _APP_DOMAIN_TARGET_A @@ -510,6 +511,7 @@ services: - _APP_OPTIONS_ROUTER_FORCE_HTTPS - _APP_DOMAIN - _APP_CONSOLE_DOMAIN + - _APP_CONSOLE_TRUSTED_PROJECTS - _APP_STORAGE_DEVICE - _APP_STORAGE_S3_ACCESS_KEY - _APP_STORAGE_S3_SECRET diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index ff917dea5c..65f65c7220 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -5173,6 +5173,68 @@ class ProjectsConsoleClientTest extends Scope $this->assertEquals($origin, $response['headers']['access-control-allow-origin'] ?? null); } + public function testConsoleCorsWithTrustedProject(): void + { + $trustedProjectIds = ['trusted-project', 'another-trusted-project']; // Set in env variable + + $projectIds = \array_merge($trustedProjectIds, ['untrusted-project-id']); + + foreach ($projectIds as $projectId) { + try { + // Create project + $this->setupProject([ + 'projectId' => $projectId, + 'name' => 'Trusted project', + 'region' => System::getEnv('_APP_REGION', 'default') + ]); + + // Add domain to trusted project; API for simplicity, in real work this will be site + $domain = \uniqid() . '.custom.localhost'; + $rule = $this->client->call(Client::METHOD_POST, '/proxy/rules/api', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $projectId, + 'x-appwrite-mode' => 'admin', + ], $this->getHeaders()), [ + 'domain' => $domain + ]); + + $this->assertEquals(201, $rule['headers']['status-code']); + + // Talk to Console APIs from trusted project domain + $currencies = $this->client->call( + Client::METHOD_GET, + '/locale/currencies', + array_merge( + $this->getHeaders(), + [ + 'content-type' => 'application/json', + 'x-appwrite-project' => 'console', + 'origin' => 'http://' . $domain + ] + ) + ); + + if (\in_array($projectId, $trustedProjectIds)) { + // Trusted projects can + $this->assertSame('http://' . $domain, $currencies['headers']['access-control-allow-origin']); + } else { + // Untrusted projects cannot + $this->assertArrayNotHasKey('access-control-allow-origin', $currencies['headers']); + } + } catch (\Throwable $th) { + throw $th; + } finally { + // Cleanup + $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), []); + + $this->assertEquals(204, $response['headers']['status-code']); + } + } + } + /** * @group abuseEnabled */ From ce99d80c2fc9b165aaf50a6383dfebb871188909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 4 Feb 2026 15:09:02 +0100 Subject: [PATCH 2/3] ai review fixes --- tests/e2e/Services/Projects/ProjectsConsoleClientTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php index 65f65c7220..559ffe9f1d 100644 --- a/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php +++ b/tests/e2e/Services/Projects/ProjectsConsoleClientTest.php @@ -5216,13 +5216,13 @@ class ProjectsConsoleClientTest extends Scope if (\in_array($projectId, $trustedProjectIds)) { // Trusted projects can + $this->assertEquals(200, $currencies['headers']['status-code']); $this->assertSame('http://' . $domain, $currencies['headers']['access-control-allow-origin']); } else { // Untrusted projects cannot + $this->assertEquals(403, $currencies['headers']['status-code']); $this->assertArrayNotHasKey('access-control-allow-origin', $currencies['headers']); } - } catch (\Throwable $th) { - throw $th; } finally { // Cleanup $response = $this->client->call(Client::METHOD_DELETE, '/projects/' . $projectId, array_merge([ From a860975b0e172cd545219ea6bde9cf0febced039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Wed, 4 Feb 2026 15:23:10 +0100 Subject: [PATCH 3/3] update docs --- app/init/resources.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/init/resources.php b/app/init/resources.php index e0a4f34ac1..ccbb703f50 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -255,6 +255,7 @@ Http::setResource('rule', function (Request $request, Database $dbForPlatform, D $permitsCurrentProject = $rule->getAttribute('projectInternalId', '') === $project->getSequence(); + // Temporary implementation until custom wildcard domains are an official feature // Allow trusted projects; Used for Console (website) previews if (!$permitsCurrentProject && !$rule->isEmpty() && !empty($rule->getAttribute('projectId', ''))) { $trustedProjects = [];