diff --git a/.env b/.env index c10c12613b..5ea2ba2852 100644 --- a/.env +++ b/.env @@ -15,6 +15,7 @@ _APP_SYSTEM_TEAM_EMAIL=team@appwrite.io _APP_EMAIL_SECURITY=security@appwrite.io _APP_EMAIL_CERTIFICATES=certificates@appwrite.io _APP_SYSTEM_RESPONSE_FORMAT= +_APP_CUSTOM_DOMAIN_DENY_LIST= _APP_OPTIONS_ABUSE=disabled _APP_OPTIONS_ROUTER_PROTECTION=disabled _APP_OPTIONS_FORCE_HTTPS=disabled diff --git a/app/config/variables.php b/app/config/variables.php index 27463d2fee..a828ceda61 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -79,6 +79,15 @@ return [ 'question' => 'Enter your Appwrite hostname', 'filter' => '' ], + [ + 'name' => '_APP_CUSTOM_DOMAIN_DENY_LIST', + 'description' => 'List of reserved or prohibited domains when configuring custom domains.', + 'introduction' => '', + 'default' => 'example.com,test.com,app.example.com', + 'required' => false, + 'question' => '', + 'filter' => '' + ], [ 'name' => '_APP_DOMAIN_FUNCTIONS', 'description' => 'A domain to use for function preview URLs. Setting to empty turns off function preview URLs.', diff --git a/app/controllers/api/migrations.php b/app/controllers/api/migrations.php index 75afc7ed2c..4061f2c2c4 100644 --- a/app/controllers/api/migrations.php +++ b/app/controllers/api/migrations.php @@ -408,6 +408,7 @@ App::get('/v1/migrations/appwrite/report') ->inject('project') ->inject('user') ->action(function (array $resources, string $endpoint, string $projectID, string $key, Response $response) { + $appwrite = new Appwrite($projectID, $endpoint, $key); try { diff --git a/app/controllers/api/projects.php b/app/controllers/api/projects.php index 48d20cd17f..b29bd227aa 100644 --- a/app/controllers/api/projects.php +++ b/app/controllers/api/projects.php @@ -138,6 +138,14 @@ App::post('/v1/projects') $databases = Config::getParam('pools-database', []); + if ($region !== 'default') { + $databaseKeys = System::getEnv('_APP_DATABASE_KEYS', ''); + $keys = explode(',', $databaseKeys); + $databases = array_filter($keys, function ($value) use ($region) { + return str_contains($value, $region); + }); + } + $databaseOverride = System::getEnv('_APP_DATABASE_OVERRIDE'); $index = \array_search($databaseOverride, $databases); if ($index !== false) { @@ -205,17 +213,17 @@ App::post('/v1/projects') $dsn = new DSN('mysql://' . $dsn); } - $adapter = $pools->get($dsn->getHost())->pop()->getResource(); - $dbForProject = new Database($adapter, $cache); $sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', '')); $sharedTablesV1 = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES_V1', '')); - $projectTables = !\in_array($dsn->getHost(), $sharedTables); $sharedTablesV1 = \in_array($dsn->getHost(), $sharedTablesV1); $sharedTablesV2 = !$projectTables && !$sharedTablesV1; $sharedTables = $sharedTablesV1 || $sharedTablesV2; if (!$sharedTablesV2) { + $adapter = $pools->get($dsn->getHost())->pop()->getResource(); + $dbForProject = new Database($adapter, $cache); + if ($sharedTables) { $dbForProject ->setSharedTables(true) diff --git a/app/controllers/api/proxy.php b/app/controllers/api/proxy.php index 393caad93d..80f3df2605 100644 --- a/app/controllers/api/proxy.php +++ b/app/controllers/api/proxy.php @@ -55,14 +55,25 @@ App::post('/v1/proxy/rules') ->inject('dbForPlatform') ->inject('dbForProject') ->action(function (string $domain, string $resourceType, string $resourceId, Response $response, Document $project, Certificate $queueForCertificates, Event $queueForEvents, Database $dbForPlatform, Database $dbForProject) { + $mainDomain = System::getEnv('_APP_DOMAIN', ''); if ($domain === $mainDomain) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your main domain to specific resource. Please use subdomain or a different domain.'); } - $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); - if ($functionsDomain != '' && 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.'); + $functionsDomain = System::getEnv('_APP_DOMAIN_FUNCTIONS'); + $denyListDomains = System::getEnv('_APP_CUSTOM_DOMAIN_DENY_LIST'); + + if (!empty($denyListDomains)) { + $functionsDomain .= ',' . $denyListDomains; + } + + $deniedDomains = array_map('trim', explode(',', $functionsDomain)); + + foreach ($deniedDomains as $deniedDomain) { + if (str_ends_with($domain, $deniedDomain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'You cannot assign your functions domain or its subdomain to a specific resource. Please use a different domain.'); + } } if ($domain === 'localhost' || $domain === APP_HOSTNAME_INTERNAL) { diff --git a/app/controllers/general.php b/app/controllers/general.php index 6c1167cf76..b28718e514 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -72,11 +72,19 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw } if ($route->isEmpty()) { - if ($host === System::getEnv('_APP_DOMAIN_FUNCTIONS', '')) { + + $appDomainFunctionsFallback = System::getEnv('_APP_DOMAIN_FUNCTIONS_FALLBACK', ''); + $appDomainFunctions = System::getEnv('_APP_DOMAIN_FUNCTIONS', ''); + + if (!empty($appDomainFunctionsFallback) && \str_ends_with($host, $appDomainFunctionsFallback)) { + $appDomainFunctions = $appDomainFunctionsFallback; + } + + if ($host === $appDomainFunctions) { throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain cannot be used for security reasons. Please use any subdomain instead.'); } - if (\str_ends_with($host, System::getEnv('_APP_DOMAIN_FUNCTIONS', ''))) { + if (\str_ends_with($host, $appDomainFunctions)) { throw new AppwriteException(AppwriteException::GENERAL_ACCESS_FORBIDDEN, 'This domain is not connected to any Appwrite resource yet. Please configure custom domain or function domain to allow this request.'); } diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 87382bbfe2..bf35f9073b 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -534,7 +534,7 @@ App::init() $data = $cache->load($key, $timestamp); if (!empty($data) && !$cacheLog->isEmpty()) { - $parts = explode('/', $cacheLog->getAttribute('resourceType')); + $parts = explode('/', $cacheLog->getAttribute('resourceType', '')); $type = $parts[0] ?? null; if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) { diff --git a/composer.lock b/composer.lock index 7469f63252..5409db78ec 100644 --- a/composer.lock +++ b/composer.lock @@ -3497,16 +3497,16 @@ }, { "name": "utopia-php/database", - "version": "0.64.1", + "version": "0.64.2", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b" + "reference": "dc9c4a68c93e8bea2dfaa76d1ba308be539998bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", - "reference": "6530a8a6d3c1fe92d0f9a92f0f05eda698d92e0b", + "url": "https://api.github.com/repos/utopia-php/database/zipball/dc9c4a68c93e8bea2dfaa76d1ba308be539998bd", + "reference": "dc9c4a68c93e8bea2dfaa76d1ba308be539998bd", "shasum": "" }, "require": { @@ -3547,9 +3547,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/0.64.1" + "source": "https://github.com/utopia-php/database/tree/0.64.2" }, - "time": "2025-04-02T00:35:29+00:00" + "time": "2025-04-09T07:53:05+00:00" }, { "name": "utopia-php/domains", @@ -8126,7 +8126,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8150,5 +8150,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } diff --git a/docker-compose.yml b/docker-compose.yml index ccdcf0972a..d1e0fb1af7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -198,6 +198,7 @@ services: - _APP_DATABASE_SHARED_TABLES_V1 - _APP_DATABASE_SHARED_NAMESPACE - _APP_FUNCTIONS_CREATION_ABUSE_LIMIT + - _APP_CUSTOM_DOMAIN_DENY_LIST extra_hosts: - "host.docker.internal:host-gateway" diff --git a/src/Appwrite/Event/Event.php b/src/Appwrite/Event/Event.php index 0edffdf4dc..a00cc45159 100644 --- a/src/Appwrite/Event/Event.php +++ b/src/Appwrite/Event/Event.php @@ -351,6 +351,7 @@ class Event */ public function trigger(): string|bool { + if ($this->paused) { return false; } @@ -360,6 +361,7 @@ class Event // Merge the base payload with any trimmed values $payload = array_merge($this->preparePayload(), $this->trimPayload()); + return $this->publisher->enqueue($queue, $payload); } diff --git a/src/Appwrite/Platform/Tasks/Maintenance.php b/src/Appwrite/Platform/Tasks/Maintenance.php index b9e312a3fb..62f504acf0 100644 --- a/src/Appwrite/Platform/Tasks/Maintenance.php +++ b/src/Appwrite/Platform/Tasks/Maintenance.php @@ -47,15 +47,22 @@ class Maintenance extends Action Console::info("[{$time}] Notifying workers with maintenance tasks every {$interval} seconds"); - $dbForPlatform->foreach('projects', function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) { - $queueForDeletes - ->setType(DELETE_TYPE_MAINTENANCE) - ->setProject($project) - ->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) - ->trigger(); - }, [ - Query::limit(100), - ]); + $dbForPlatform->foreach( + 'projects', + [ + Query::equal('region', System::getEnv('_APP_REGION', 'default')) + ], + function (Document $project) use ($queueForDeletes, $usageStatsRetentionHourly) { + $queueForDeletes + ->setType(DELETE_TYPE_MAINTENANCE) + ->setProject($project) + ->setUsageRetentionHourlyDateTime(DateTime::addSeconds(new \DateTime(), -1 * $usageStatsRetentionHourly)) + ->trigger(); + }, + [ + Query::limit(100), + ] + ); $queueForDeletes ->setType(DELETE_TYPE_MAINTENANCE) diff --git a/src/Appwrite/Platform/Tasks/StatsResources.php b/src/Appwrite/Platform/Tasks/StatsResources.php index ac3b9ead73..88969ee2a4 100644 --- a/src/Appwrite/Platform/Tasks/StatsResources.php +++ b/src/Appwrite/Platform/Tasks/StatsResources.php @@ -67,7 +67,8 @@ class StatsResources extends Action * For each project that were accessed in last 24 hours */ $this->foreachDocument($this->dbForPlatform, 'projects', [ - Query::greaterThanEqual('accessedAt', DateTime::format($last24Hours)) + Query::greaterThanEqual('accessedAt', DateTime::format($last24Hours)), + Query::equal('region', System::getEnv('_APP_REGION', 'default')) ], function ($project) use ($queue) { $queue ->setProject($project) diff --git a/src/Appwrite/Platform/Workers/Deletes.php b/src/Appwrite/Platform/Workers/Deletes.php index 5092599d67..5be865f42b 100644 --- a/src/Appwrite/Platform/Workers/Deletes.php +++ b/src/Appwrite/Platform/Workers/Deletes.php @@ -494,21 +494,22 @@ class Deletes extends Action } /** - * @param Database $dbForPlatform - * @param Document $document - * @return void - * @throws Authorization - * @throws DatabaseException - * @throws Conflict - * @throws Restricted - * @throws Structure - * @throws Exception - */ - private function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void + * @param Database $dbForPlatform + * @param Document $document + * @return void + * @throws Authorization + * @throws DatabaseException + * @throws Conflict + * @throws Restricted + * @throws Structure + * @throws Exception + */ + protected function deleteProjectsByTeam(Database $dbForPlatform, callable $getProjectDB, CertificatesAdapter $certificates, Document $document): void { $projects = $dbForPlatform->find('projects', [ - Query::equal('teamInternalId', [$document->getInternalId()]) + Query::equal('teamInternalId', [$document->getInternalId()]), + Query::equal('region', [System::getEnv('_APP_REGION', 'default')]) ]); foreach ($projects as $project) { diff --git a/src/Appwrite/Platform/Workers/StatsResources.php b/src/Appwrite/Platform/Workers/StatsResources.php index 3c0e772bd4..0144020d38 100644 --- a/src/Appwrite/Platform/Workers/StatsResources.php +++ b/src/Appwrite/Platform/Workers/StatsResources.php @@ -70,7 +70,6 @@ class StatsResources extends Action } if (empty($project->getAttribute('database'))) { - var_dump($payload); return; } diff --git a/src/Appwrite/Platform/Workers/Webhooks.php b/src/Appwrite/Platform/Workers/Webhooks.php index c903dafdae..44e8bd3118 100644 --- a/src/Appwrite/Platform/Workers/Webhooks.php +++ b/src/Appwrite/Platform/Workers/Webhooks.php @@ -54,6 +54,8 @@ class Webhooks extends Action $this->errors = []; $payload = $message->getPayload() ?? []; + + if (empty($payload)) { throw new Exception('Missing payload'); }