diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f1051497a8..62b4953e27 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -64,8 +64,9 @@ jobs: sudo wget -O /usr/share/keyrings/azlux-archive-keyring.gpg https://azlux.fr/repo.gpg sudo apt update sudo apt install oha + oha --version - name: Benchmark PR - run: 'oha -z 180s http://localhost/v1/health/version --output-format json > benchmark.json' + run: 'oha -z 180s http://localhost/v1/health/version --output-format json > benchmark.json' - name: Cleaning run: docker compose down -v - name: Installing latest version @@ -78,7 +79,7 @@ jobs: docker compose up -d sleep 10 - name: Benchmark Latest - run: oha -z 180s http://localhost/v1/health/version --output-format json > benchmark-latest.json + run: oha -z 180s http://localhost/v1/health/version --output-format json > benchmark-latest.json - name: Prepare comment run: | echo '## :sparkles: Benchmark results' > benchmark.txt diff --git a/app/config/platforms.php b/app/config/platforms.php index 63ad307ea2..96d5426515 100644 --- a/app/config/platforms.php +++ b/app/config/platforms.php @@ -217,7 +217,7 @@ return [ [ 'key' => 'cli', 'name' => 'Command Line', - 'version' => '8.0.2', + 'version' => '8.1.0', 'url' => 'https://github.com/appwrite/sdk-for-cli', 'package' => 'https://www.npmjs.com/package/appwrite-cli', 'enabled' => true, diff --git a/app/controllers/general.php b/app/controllers/general.php index c18f729c44..225762ce7e 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -799,7 +799,6 @@ App::init() ->inject('getProjectDB') ->inject('locale') ->inject('localeCodes') - ->inject('clients') ->inject('geodb') ->inject('queueForStatsUsage') ->inject('queueForEvents') @@ -810,7 +809,9 @@ App::init() ->inject('previewHostname') ->inject('devKey') ->inject('apiKey') - ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $clients, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey) { + ->inject('httpReferrer') + ->inject('httpReferrerSafe') + ->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) { /* * Appwrite Router */ @@ -942,42 +943,9 @@ App::init() $locale->setDefault($localeParam); } - $referrer = $request->getReferer(); - $origin = \parse_url($request->getOrigin($referrer), PHP_URL_HOST); - $protocol = \parse_url($request->getOrigin($referrer), PHP_URL_SCHEME); - $port = \parse_url($request->getOrigin($referrer), PHP_URL_PORT); - - $refDomainOrigin = 'localhost'; - $validator = new Hostname($clients); - if ($validator->isValid($origin)) { - $refDomainOrigin = $origin; - } elseif (!empty($origin)) { - // Auto-allow domains with linked rule - if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { - $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? ''))); - } else { - $rule = Authorization::skip( - fn () => $dbForPlatform->find('rules', [ - Query::equal('domain', [$origin]), - Query::limit(1) - ]) - )[0] ?? new Document(); - } - - if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) { - $refDomainOrigin = $origin; - } - } - - $refDomain = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $refDomainOrigin . (!empty($port) ? ':' . $port : ''); - - $refDomain = (!$route->getLabel('origin', false)) // This route is publicly accessible - ? $refDomain - : (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : ''); - + $origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST); $selfDomain = new Domain($request->getHostname()); $endDomain = new Domain((string)$origin); - Config::setParam( 'domainVerification', ($selfDomain->getRegisterable() === $endDomain->getRegisterable()) && @@ -1049,7 +1017,7 @@ App::init() ->addHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE') ->addHeader('Access-Control-Allow-Headers', 'Origin, Cookie, Set-Cookie, X-Requested-With, Content-Type, Access-Control-Allow-Origin, Access-Control-Request-Headers, Accept, X-Appwrite-Project, X-Appwrite-Key, X-Appwrite-Dev-Key, X-Appwrite-Locale, X-Appwrite-Mode, X-Appwrite-JWT, X-Appwrite-Response-Format, X-Appwrite-Timeout, X-SDK-Version, X-SDK-Name, X-SDK-Language, X-SDK-Platform, X-SDK-GraphQL, X-Appwrite-ID, X-Appwrite-Timestamp, Content-Range, Range, Cache-Control, Expires, Pragma, X-Forwarded-For, X-Forwarded-User-Agent') ->addHeader('Access-Control-Expose-Headers', 'X-Appwrite-Session, X-Fallback-Cookies') - ->addHeader('Access-Control-Allow-Origin', $refDomain) + ->addHeader('Access-Control-Allow-Origin', $httpReferrerSafe) ->addHeader('Access-Control-Allow-Credentials', 'true'); if (!$devKey->isEmpty()) { @@ -1070,6 +1038,7 @@ App::init() && \in_array($request->getMethod(), [Request::METHOD_POST, Request::METHOD_PUT, Request::METHOD_PATCH, Request::METHOD_DELETE]) && $route->getLabel('origin', false) !== '*' && empty($request->getHeader('x-appwrite-key', '')) + && \parse_url($httpReferrerSafe, PHP_URL_HOST) === 'localhost' ) { throw new AppwriteException(AppwriteException::GENERAL_UNKNOWN_ORIGIN, $originValidator->getDescription()); } diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 735487339f..76fe177b0b 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -564,7 +564,7 @@ App::init() $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) ); - $timestamp = 60 * 60 * 24 * 30; + $timestamp = 60 * 60 * 24 * 180; // Temporarily increase the TTL to 180 day to ensure files in the cache are still fetched. $data = $cache->load($key, $timestamp); if (!empty($data) && !$cacheLog->isEmpty()) { @@ -831,6 +831,10 @@ App::shutdown() $resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user); } + $cache = new Cache( + new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) + ); + $key = $request->cacheIdentifier(); $signature = md5($data['payload']); $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); @@ -848,12 +852,11 @@ App::shutdown() } elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) { $cacheLog->setAttribute('accessedAt', $now); Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog)); + // Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load() + $cache->save($key, $data['payload']); } if ($signature !== $cacheLog->getAttribute('signature')) { - $cache = new Cache( - new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) - ); $cache->save($key, $data['payload']); } } diff --git a/app/init/resources.php b/app/init/resources.php index 5f65fe683f..aa04b46e1f 100644 --- a/app/init/resources.php +++ b/app/init/resources.php @@ -944,3 +944,52 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) { } return new Document([]); }, ['project', 'dbForProject', 'request']); + +App::setResource('httpReferrer', function (Request $request): string { + $referrer = $request->getReferer(); + return $referrer; +}, ['request']); + +App::setResource('httpReferrerSafe', function (Request $request, string $httpReferrer, array $clients, Database $dbForPlatform, Document $project, App $utopia): string { + $origin = \parse_url($request->getOrigin($httpReferrer), PHP_URL_HOST); + $protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME); + $port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT); + $referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : ''); + + // Safe if route is publicly accessible + $route = $utopia->getRoute(); + if ($route->getLabel('origin', false)) { + return $referrer; + } + + // Safe if added as web platform + $validator = new Hostname($clients); + if ($validator->isValid($origin)) { + return $referrer; + } + + // Safe if rule with same project ID exists + if (!empty($origin)) { + if (System::getEnv('_APP_RULES_FORMAT') === 'md5') { + $rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? ''))); + } else { + $rule = Authorization::skip( + fn () => $dbForPlatform->find('rules', [ + Query::equal('domain', [$origin]), + Query::limit(1) + ]) + )[0] ?? new Document(); + } + + if (!$rule->isEmpty() && $rule->getAttribute('projectInternalId') === $project->getSequence()) { + return $referrer; + } + } + + // Unsafe; Localhost is always safe for ease of local development + $origin = 'localhost'; + $protocol = \parse_url($request->getOrigin($httpReferrer), PHP_URL_SCHEME); + $port = \parse_url($request->getOrigin($httpReferrer), PHP_URL_PORT); + $referrer = (!empty($protocol) ? $protocol : $request->getProtocol()) . '://' . $origin . (!empty($port) ? ':' . $port : ''); + return $referrer; +}, ['request', 'httpReferrer', 'clients', 'dbForPlatform', 'project', 'utopia']); diff --git a/composer.lock b/composer.lock index 5b17ab4da2..2ac0062d6d 100644 --- a/composer.lock +++ b/composer.lock @@ -4822,16 +4822,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "0.41.8", + "version": "0.41.9", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "93ffb24b25b376ca4423e3a5caf6f916673af4b2" + "reference": "61037c1ed9262308cab49c1d760f3278036ab694" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/93ffb24b25b376ca4423e3a5caf6f916673af4b2", - "reference": "93ffb24b25b376ca4423e3a5caf6f916673af4b2", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/61037c1ed9262308cab49c1d760f3278036ab694", + "reference": "61037c1ed9262308cab49c1d760f3278036ab694", "shasum": "" }, "require": { @@ -4867,9 +4867,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/0.41.8" + "source": "https://github.com/appwrite/sdk-generator/tree/0.41.9" }, - "time": "2025-06-18T13:20:45+00:00" + "time": "2025-06-27T10:16:17+00:00" }, { "name": "doctrine/annotations", @@ -8248,7 +8248,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -8272,5 +8272,5 @@ "platform-overrides": { "php": "8.3" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/docs/sdks/cli/CHANGELOG.md b/docs/sdks/cli/CHANGELOG.md index fa4d35e687..c2c93b0c09 100644 --- a/docs/sdks/cli/CHANGELOG.md +++ b/docs/sdks/cli/CHANGELOG.md @@ -1 +1,61 @@ -# Change Log \ No newline at end of file +# Change Log + +## 8.1.0 + +* Add multi-region support to `init` command +* Update `init` command to clear previous configuration in `appwrite.json` +* Update localConfig to store multi-region endpoint +* Fix throw error when creating unknown attribute instead of timing out +* Fix equal comparison of large numbers and BigNumber instances using proper equality checks +* Fix duplication of reasons when comparing localConfig with remoteConfig +* Fix `firstOrNull()` to `firstOrNull` in types generation for dart +* Refactor to use `isCloud()` method consistently + +## 8.0.2 + +* Add Type generation fixes: + * Properly handle enum attributes in dart, java and kotlin + * Fix initialisation of null attributes in dart's fromMap method + * Fix relationships and enums in swift + +## 8.0.1 + +* Add `resourceId` and `resourceType` attributes to `createRedirectRule` +* Add `providerReference` to vcs command for getting repository contents +* Add warning comment to `bulk updateDocuments` method +* Fix type generation for enums in Typescript and PHP language + +## 8.0.0 + +* Add `types` command to generate language specific typings for collections. Currently supports - `php`, `swift`, `dart`, `js`, `ts`, `kotlin` and `java` +* Update bulk operation docs to include experiment feature warnings +* Remove assistant service and commands + +## 7.0.0 + +* Add `sites` command +* Add `tokens` command +* Add `devKeys` support to `projects` command +* Add `init site`, `pull site` and `push site` commands +* Add bulk operation methods like `createDocuments`, `deleteDocuments` etc. +* Add new upsert methods: `upsertDocument` and `upsertDocuments` +* Update GET requests to not include content-type header + +## 6.2.3 + +* Fix hot swapping error in `python-ml` function + +## 6.2.2 + +* Fix GitHub builds by adding `qemu-system` package +* Fix attribute creation timed out + +## 6.2.1 + +* Add `listOrganizations` method to `organizations` service and fix init project command + +## 6.2.0 + +* Add specifications support to CLI +* Update package version +* Fix: Missed specifications param when updating a function \ No newline at end of file