Merge branch 'refactor-auth-single-instance' of github.com:appwrite/appwrite into cached-documents-list

# Conflicts:
#	composer.json
#	composer.lock
This commit is contained in:
shimon
2025-12-08 09:56:48 +02:00
381 changed files with 12687 additions and 6719 deletions
+1
View File
@@ -103,6 +103,7 @@ _APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000
_APP_MAINTENANCE_RETENTION_SCHEDULES=86400
_APP_USAGE_STATS=enabled
_APP_LOGGING_CONFIG=
_APP_LOGGING_CONFIG_REALTIME=
_APP_GRAPHQL_MAX_BATCH_SIZE=10
_APP_GRAPHQL_MAX_COMPLEXITY=250
_APP_GRAPHQL_MAX_DEPTH=4
+2 -2
View File
@@ -24,9 +24,9 @@ ENV _APP_VERSION=$VERSION \
_APP_HOME=https://appwrite.io
RUN \
if [ "$DEBUG" == "true" ]; then \
if [ "$DEBUG" == "true" ]; then \
apk add boost boost-dev; \
fi
fi
WORKDIR /usr/src/code
Binary file not shown.
Binary file not shown.
+2
View File
@@ -9,6 +9,7 @@ use Appwrite\Event\StatsResources;
use Appwrite\Event\StatsUsage;
use Appwrite\Platform\Appwrite;
use Appwrite\Runtimes\Runtimes;
use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Swoole\Timer;
@@ -81,6 +82,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', 'console');
$dbForPlatform->setDocumentType('users', User::class);
// Ensure tables exist
$collections = Config::getParam('collections', [])['console'];
+3 -3
View File
@@ -1,6 +1,6 @@
<?php
use Appwrite\Auth\Auth;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Database\Database;
use Utopia\Database\Helpers\ID;
@@ -173,7 +173,7 @@ return [
'size' => 256,
'signed' => true,
'required' => false,
'default' => Auth::DEFAULT_ALGO,
'default' => (new Argon2())->getName(),
'array' => false,
'filters' => [],
],
@@ -184,7 +184,7 @@ return [
'size' => 65535,
'signed' => true,
'required' => false,
'default' => Auth::DEFAULT_ALGO_OPTIONS,
'default' => (new Argon2())->getOptions(),
'array' => false,
'filters' => ['json'],
],
+1 -2
View File
@@ -4,7 +4,6 @@
* Initializes console project document.
*/
use Appwrite\Auth\Auth;
use Appwrite\Network\Platform;
use Utopia\Database\Helpers\ID;
use Utopia\System\System;
@@ -38,7 +37,7 @@ $console = [
'mockNumbers' => [],
'invites' => System::getEnv('_APP_CONSOLE_INVITES', 'enabled') === 'enabled',
'limit' => (System::getEnv('_APP_CONSOLE_WHITELIST_ROOT', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'duration' => TOKEN_EXPIRATION_LOGIN_LONG, // 1 Year in seconds
'sessionAlerts' => System::getEnv('_APP_CONSOLE_SESSION_ALERTS', 'disabled') === 'enabled',
'invalidateSessions' => true
],
+16 -23
View File
@@ -8,20 +8,13 @@ use Utopia\Config\Config;
$templateRuntimes = Config::getParam('template-runtimes');
function getVersions(array $versions, string $prefix)
{
return array_map(function ($version) use ($prefix) {
return $prefix . '-' . $version;
}, $versions);
}
return [
'analog' => [
'key' => 'analog',
'name' => 'Analog',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/analog/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/analog/env.sh',
'adapters' => [
@@ -47,7 +40,7 @@ return [
'name' => 'Angular',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/angular/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/angular/env.sh',
'adapters' => [
@@ -73,7 +66,7 @@ return [
'name' => 'Next.js',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/next-js/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/next-js/env.sh',
'adapters' => [
@@ -98,7 +91,7 @@ return [
'name' => 'React',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'adapters' => [
'static' => [
'key' => 'static',
@@ -115,7 +108,7 @@ return [
'name' => 'Nuxt',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/nuxt/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/nuxt/env.sh',
'adapters' => [
@@ -140,7 +133,7 @@ return [
'name' => 'Vue.js',
'screenshotSleep' => 5000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'adapters' => [
'static' => [
'key' => 'static',
@@ -157,7 +150,7 @@ return [
'name' => 'SvelteKit',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/sveltekit/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/sveltekit/env.sh',
'adapters' => [
@@ -182,7 +175,7 @@ return [
'name' => 'Astro',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/astro/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/astro/env.sh',
'adapters' => [
@@ -207,7 +200,7 @@ return [
'name' => 'TanStack Start',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/tanstack-start/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/tanstack-start/env.sh',
'adapters' => [
@@ -215,7 +208,7 @@ return [
'key' => 'ssr',
'buildCommand' => 'npm run build',
'installCommand' => 'npm install',
'outputDirectory' => './dist',
'outputDirectory' => './.output',
'startCommand' => 'bash helpers/tanstack-start/server.sh',
],
'static' => [
@@ -232,7 +225,7 @@ return [
'name' => 'Remix',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'bundleCommand' => 'bash /usr/local/server/helpers/remix/bundle.sh',
'envCommand' => 'source /usr/local/server/helpers/remix/env.sh',
'adapters' => [
@@ -257,7 +250,7 @@ return [
'name' => 'Lynx',
'screenshotSleep' => 5000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'adapters' => [
'static' => [
'key' => 'static',
@@ -274,7 +267,7 @@ return [
'name' => 'Flutter',
'screenshotSleep' => 5000,
'buildRuntime' => 'flutter-3.35',
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
'runtimes' => $templateRuntimes['FLUTTER'],
'adapters' => [
'static' => [
'key' => 'static',
@@ -291,7 +284,7 @@ return [
'name' => 'React Native',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'adapters' => [
'static' => [
'key' => 'static',
@@ -308,7 +301,7 @@ return [
'name' => 'Vite',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'adapters' => [
'static' => [
'key' => 'static',
@@ -324,7 +317,7 @@ return [
'name' => 'Other',
'screenshotSleep' => 3000,
'buildRuntime' => 'node-22',
'runtimes' => getVersions($templateRuntimes['NODE']['versions'], 'node'),
'runtimes' => $templateRuntimes['NODE'],
'adapters' => [
'static' => [
'key' => 'static',
+15 -15
View File
@@ -11,7 +11,7 @@ return [
[
'key' => 'web',
'name' => 'Web',
'version' => '21.4.0',
'version' => '21.5.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
@@ -60,7 +60,7 @@ return [
[
'key' => 'flutter',
'name' => 'Flutter',
'version' => '20.3.1',
'version' => '20.3.2',
'url' => 'https://github.com/appwrite/sdk-for-flutter',
'package' => 'https://pub.dev/packages/appwrite',
'enabled' => true,
@@ -79,7 +79,7 @@ return [
[
'key' => 'apple',
'name' => 'Apple',
'version' => '13.4.0',
'version' => '13.5.0',
'url' => 'https://github.com/appwrite/sdk-for-apple',
'package' => 'https://github.com/appwrite/sdk-for-apple',
'enabled' => true,
@@ -116,7 +116,7 @@ return [
[
'key' => 'android',
'name' => 'Android',
'version' => '11.3.0',
'version' => '11.4.0',
'url' => 'https://github.com/appwrite/sdk-for-android',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-android',
'enabled' => true,
@@ -139,7 +139,7 @@ return [
[
'key' => 'react-native',
'name' => 'React Native',
'version' => '0.18.0',
'version' => '0.19.0',
'url' => 'https://github.com/appwrite/sdk-for-react-native',
'package' => 'https://npmjs.com/package/react-native-appwrite',
'enabled' => true,
@@ -226,7 +226,7 @@ return [
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '11.1.1',
'version' => '12.0.1',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -262,7 +262,7 @@ return [
[
'key' => 'nodejs',
'name' => 'Node.js',
'version' => '20.3.0',
'version' => '21.0.0',
'url' => 'https://github.com/appwrite/sdk-for-node',
'package' => 'https://www.npmjs.com/package/node-appwrite',
'enabled' => true,
@@ -281,7 +281,7 @@ return [
[
'key' => 'php',
'name' => 'PHP',
'version' => '18.0.1',
'version' => '19.0.0',
'url' => 'https://github.com/appwrite/sdk-for-php',
'package' => 'https://packagist.org/packages/appwrite/appwrite',
'enabled' => true,
@@ -300,7 +300,7 @@ return [
[
'key' => 'python',
'name' => 'Python',
'version' => '13.6.1',
'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-python',
'package' => 'https://pypi.org/project/appwrite/',
'enabled' => true,
@@ -319,7 +319,7 @@ return [
[
'key' => 'ruby',
'name' => 'Ruby',
'version' => '19.3.0',
'version' => '20.0.0',
'url' => 'https://github.com/appwrite/sdk-for-ruby',
'package' => 'https://rubygems.org/gems/appwrite',
'enabled' => true,
@@ -338,7 +338,7 @@ return [
[
'key' => 'go',
'name' => 'Go',
'version' => 'v0.14.0',
'version' => 'v0.15.0',
'url' => 'https://github.com/appwrite/sdk-for-go',
'package' => 'https://github.com/appwrite/sdk-for-go',
'enabled' => true,
@@ -357,7 +357,7 @@ return [
[
'key' => 'dotnet',
'name' => '.NET',
'version' => '0.22.0',
'version' => '0.23.0',
'url' => 'https://github.com/appwrite/sdk-for-dotnet',
'package' => 'https://www.nuget.org/packages/Appwrite',
'enabled' => true,
@@ -376,7 +376,7 @@ return [
[
'key' => 'dart',
'name' => 'Dart',
'version' => '19.4.0',
'version' => '20.0.0',
'url' => 'https://github.com/appwrite/sdk-for-dart',
'package' => 'https://pub.dev/packages/dart_appwrite',
'enabled' => true,
@@ -395,7 +395,7 @@ return [
[
'key' => 'kotlin',
'name' => 'Kotlin',
'version' => '12.3.0',
'version' => '13.0.0',
'url' => 'https://github.com/appwrite/sdk-for-kotlin',
'package' => 'https://search.maven.org/artifact/io.appwrite/sdk-for-kotlin',
'enabled' => true,
@@ -418,7 +418,7 @@ return [
[
'key' => 'swift',
'name' => 'Swift',
'version' => '13.3.0',
'version' => '14.0.0',
'url' => 'https://github.com/appwrite/sdk-for-swift',
'package' => 'https://github.com/appwrite/sdk-for-swift',
'enabled' => true,
+7 -7
View File
@@ -1,6 +1,6 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Utopia\Database\Documents\User;
$member = [
'global',
@@ -92,7 +92,7 @@ $admins = [
];
return [
Auth::USER_ROLE_GUESTS => [
User::ROLE_GUESTS => [
'label' => 'Guests',
'scopes' => [
'global',
@@ -112,23 +112,23 @@ return [
'execution.write',
],
],
Auth::USER_ROLE_USERS => [
User::ROLE_USERS => [
'label' => 'Users',
'scopes' => \array_merge($member),
],
Auth::USER_ROLE_ADMIN => [
User::ROLE_ADMIN => [
'label' => 'Admin',
'scopes' => \array_merge($admins),
],
Auth::USER_ROLE_DEVELOPER => [
User::ROLE_DEVELOPER => [
'label' => 'Developer',
'scopes' => \array_merge($admins),
],
Auth::USER_ROLE_OWNER => [
User::ROLE_OWNER => [
'label' => 'Owner',
'scopes' => \array_merge($member, $admins),
],
Auth::USER_ROLE_APPS => [
User::ROLE_APPS => [
'label' => 'Applications',
'scopes' => ['global', 'health.read', 'graphql'],
],
+2 -2
View File
@@ -47,10 +47,10 @@ return [ // List of publicly visible scopes
'description' => 'Access to create, update, and delete your project\'s database table\'s columns',
],
'indexes.read' => [
'description' => 'Access to read your project\'s database collection\'s indexes',
'description' => 'Access to read your project\'s database table\'s indexes',
],
'indexes.write' => [
'description' => 'Access to create, update, and delete your project\'s database collection\'s indexes',
'description' => 'Access to create, update, and delete your project\'s database table\'s indexes',
],
'documents.read' => [
'description' => 'Access to read your project\'s database documents',
+117 -78
View File
@@ -258,7 +258,7 @@
"x-appwrite": {
"method": "listIdentities",
"group": "identities",
"weight": 58,
"weight": 48,
"cookies": false,
"type": "",
"demo": "account\/list-identities.md",
@@ -328,7 +328,7 @@
"x-appwrite": {
"method": "deleteIdentity",
"group": "identities",
"weight": 59,
"weight": 49,
"cookies": false,
"type": "",
"demo": "account\/delete-identity.md",
@@ -517,7 +517,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 45,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -587,7 +587,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 47,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -707,7 +707,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 48,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -843,7 +843,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 52,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -939,7 +939,7 @@
]
}
},
"\/account\/mfa\/challenge": {
"\/account\/mfa\/challenges": {
"post": {
"summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
@@ -963,7 +963,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 53,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1091,7 +1091,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 54,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1225,7 +1225,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 46,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1322,7 +1322,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 51,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1417,7 +1417,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 49,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1512,7 +1512,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 50,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -2918,7 +2918,7 @@
"x-appwrite": {
"method": "createPushTarget",
"group": "pushTargets",
"weight": 55,
"weight": 45,
"cookies": false,
"type": "",
"demo": "account\/create-push-target.md",
@@ -2997,7 +2997,7 @@
"x-appwrite": {
"method": "updatePushTarget",
"group": "pushTargets",
"weight": 56,
"weight": 46,
"cookies": false,
"type": "",
"demo": "account\/update-push-target.md",
@@ -3068,7 +3068,7 @@
"x-appwrite": {
"method": "deletePushTarget",
"group": "pushTargets",
"weight": 57,
"weight": 47,
"cookies": false,
"type": "",
"demo": "account\/delete-push-target.md",
@@ -3879,7 +3879,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 61,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4005,7 +4005,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 60,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4137,7 +4137,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 64,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4195,7 +4195,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 62,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4683,7 +4683,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 63,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4765,7 +4765,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 66,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -4857,7 +4857,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 65,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -4949,7 +4949,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 67,
"weight": 57,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -4993,7 +4993,7 @@
"required": false,
"schema": {
"type": "object",
"x-example": "{}",
"x-example": "{\"Authorization\":\"Bearer token123\",\"X-Custom-Header\":\"value\"}",
"default": {}
},
"in": "query"
@@ -5005,7 +5005,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1920",
"default": 1280
},
"in": "query"
@@ -5017,7 +5017,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1080",
"default": 720
},
"in": "query"
@@ -5029,7 +5029,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": 0.1,
"x-example": "2",
"default": 1
},
"in": "query"
@@ -5040,7 +5040,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "light",
"x-example": "dark",
"enum": [
"light",
"dark"
@@ -5057,7 +5057,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "<USER_AGENT>",
"x-example": "Mozilla\/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit\/605.1.15",
"default": ""
},
"in": "query"
@@ -5068,7 +5068,7 @@
"required": false,
"schema": {
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false
},
"in": "query"
@@ -5079,7 +5079,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "<LOCALE>",
"x-example": "en-US",
"default": ""
},
"in": "query"
@@ -5090,7 +5090,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "africa\/abidjan",
"x-example": "america\/new_york",
"enum": [
"africa\/abidjan",
"africa\/accra",
@@ -5525,7 +5525,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": -90,
"x-example": "37.7749",
"default": 0
},
"in": "query"
@@ -5537,7 +5537,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": -180,
"x-example": "-122.4194",
"default": 0
},
"in": "query"
@@ -5549,7 +5549,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": 0,
"x-example": "100",
"default": 0
},
"in": "query"
@@ -5560,7 +5560,7 @@
"required": false,
"schema": {
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false
},
"in": "query"
@@ -5572,8 +5572,33 @@
"schema": {
"type": "array",
"items": {
"type": "string"
"type": "string",
"enum": [
"geolocation",
"camera",
"microphone",
"notifications",
"midi",
"push",
"clipboard-read",
"clipboard-write",
"payment-handler",
"usb",
"bluetooth",
"accelerometer",
"gyroscope",
"magnetometer",
"ambient-light-sensor",
"background-sync",
"persistent-storage",
"screen-wake-lock",
"web-share",
"xr-spatial-tracking"
],
"x-enum-name": "BrowserPermission",
"x-enum-keys": []
},
"x-example": "[\"geolocation\",\"notifications\"]",
"default": []
},
"in": "query"
@@ -5585,7 +5610,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "3",
"default": 0
},
"in": "query"
@@ -5597,7 +5622,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "800",
"default": 0
},
"in": "query"
@@ -5609,7 +5634,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "600",
"default": 0
},
"in": "query"
@@ -5621,7 +5646,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": -1,
"x-example": "85",
"default": -1
},
"in": "query"
@@ -5632,7 +5657,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "jpg",
"x-example": "jpeg",
"enum": [
"jpg",
"jpeg",
@@ -7370,7 +7395,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 251,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7422,7 +7447,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 250,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7474,7 +7499,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 71,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7526,7 +7551,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 72,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7578,7 +7603,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 76,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7630,7 +7655,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 73,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -7682,7 +7707,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 74,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -7734,7 +7759,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 75,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -7786,7 +7811,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 77,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -7838,7 +7863,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 78,
"weight": 68,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -7890,7 +7915,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 300,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -7973,7 +7998,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 304,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8048,7 +8073,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 161,
"weight": 151,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8145,7 +8170,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 160,
"weight": 150,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8244,7 +8269,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 162,
"weight": 152,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8316,7 +8341,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 167,
"weight": 157,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8407,7 +8432,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 168,
"weight": 158,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8474,7 +8499,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 164,
"weight": 154,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8552,7 +8577,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 163,
"weight": 153,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -8780,7 +8805,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 165,
"weight": 155,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -10281,7 +10306,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 172,
"weight": 162,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10368,7 +10393,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 171,
"weight": 161,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10453,7 +10478,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 173,
"weight": 163,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10515,7 +10540,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 175,
"weight": 165,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10589,7 +10614,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 177,
"weight": 167,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10653,7 +10678,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 179,
"weight": 169,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -10750,7 +10775,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 178,
"weight": 168,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -10814,7 +10839,14 @@
"description": "Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
},
"url": {
@@ -10861,7 +10893,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 180,
"weight": 170,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -10933,7 +10965,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 181,
"weight": 171,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -10992,7 +11024,14 @@
"description": "An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
}
},
@@ -11020,7 +11059,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 183,
"weight": 173,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11094,7 +11133,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 182,
"weight": 172,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11192,7 +11231,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 174,
"weight": 164,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11253,7 +11292,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 176,
"weight": 166,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+117 -78
View File
@@ -258,7 +258,7 @@
"x-appwrite": {
"method": "listIdentities",
"group": "identities",
"weight": 58,
"weight": 48,
"cookies": false,
"type": "",
"demo": "account\/list-identities.md",
@@ -328,7 +328,7 @@
"x-appwrite": {
"method": "deleteIdentity",
"group": "identities",
"weight": 59,
"weight": 49,
"cookies": false,
"type": "",
"demo": "account\/delete-identity.md",
@@ -517,7 +517,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 45,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -587,7 +587,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 47,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -707,7 +707,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 48,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -843,7 +843,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 52,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -939,7 +939,7 @@
]
}
},
"\/account\/mfa\/challenge": {
"\/account\/mfa\/challenges": {
"post": {
"summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
@@ -963,7 +963,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 53,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1091,7 +1091,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 54,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1225,7 +1225,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 46,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1322,7 +1322,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 51,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1417,7 +1417,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 49,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1512,7 +1512,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 50,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -2918,7 +2918,7 @@
"x-appwrite": {
"method": "createPushTarget",
"group": "pushTargets",
"weight": 55,
"weight": 45,
"cookies": false,
"type": "",
"demo": "account\/create-push-target.md",
@@ -2997,7 +2997,7 @@
"x-appwrite": {
"method": "updatePushTarget",
"group": "pushTargets",
"weight": 56,
"weight": 46,
"cookies": false,
"type": "",
"demo": "account\/update-push-target.md",
@@ -3068,7 +3068,7 @@
"x-appwrite": {
"method": "deletePushTarget",
"group": "pushTargets",
"weight": 57,
"weight": 47,
"cookies": false,
"type": "",
"demo": "account\/delete-push-target.md",
@@ -3879,7 +3879,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 61,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4005,7 +4005,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 60,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4137,7 +4137,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 64,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4195,7 +4195,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 62,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4683,7 +4683,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 63,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4765,7 +4765,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 66,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -4857,7 +4857,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 65,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -4949,7 +4949,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 67,
"weight": 57,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -4993,7 +4993,7 @@
"required": false,
"schema": {
"type": "object",
"x-example": "{}",
"x-example": "{\"Authorization\":\"Bearer token123\",\"X-Custom-Header\":\"value\"}",
"default": {}
},
"in": "query"
@@ -5005,7 +5005,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1920",
"default": 1280
},
"in": "query"
@@ -5017,7 +5017,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1080",
"default": 720
},
"in": "query"
@@ -5029,7 +5029,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": 0.1,
"x-example": "2",
"default": 1
},
"in": "query"
@@ -5040,7 +5040,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "light",
"x-example": "dark",
"enum": [
"light",
"dark"
@@ -5057,7 +5057,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "<USER_AGENT>",
"x-example": "Mozilla\/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit\/605.1.15",
"default": ""
},
"in": "query"
@@ -5068,7 +5068,7 @@
"required": false,
"schema": {
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false
},
"in": "query"
@@ -5079,7 +5079,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "<LOCALE>",
"x-example": "en-US",
"default": ""
},
"in": "query"
@@ -5090,7 +5090,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "africa\/abidjan",
"x-example": "america\/new_york",
"enum": [
"africa\/abidjan",
"africa\/accra",
@@ -5525,7 +5525,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": -90,
"x-example": "37.7749",
"default": 0
},
"in": "query"
@@ -5537,7 +5537,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": -180,
"x-example": "-122.4194",
"default": 0
},
"in": "query"
@@ -5549,7 +5549,7 @@
"schema": {
"type": "number",
"format": "float",
"x-example": 0,
"x-example": "100",
"default": 0
},
"in": "query"
@@ -5560,7 +5560,7 @@
"required": false,
"schema": {
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false
},
"in": "query"
@@ -5572,8 +5572,33 @@
"schema": {
"type": "array",
"items": {
"type": "string"
"type": "string",
"enum": [
"geolocation",
"camera",
"microphone",
"notifications",
"midi",
"push",
"clipboard-read",
"clipboard-write",
"payment-handler",
"usb",
"bluetooth",
"accelerometer",
"gyroscope",
"magnetometer",
"ambient-light-sensor",
"background-sync",
"persistent-storage",
"screen-wake-lock",
"web-share",
"xr-spatial-tracking"
],
"x-enum-name": "BrowserPermission",
"x-enum-keys": []
},
"x-example": "[\"geolocation\",\"notifications\"]",
"default": []
},
"in": "query"
@@ -5585,7 +5610,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "3",
"default": 0
},
"in": "query"
@@ -5597,7 +5622,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "800",
"default": 0
},
"in": "query"
@@ -5609,7 +5634,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "600",
"default": 0
},
"in": "query"
@@ -5621,7 +5646,7 @@
"schema": {
"type": "integer",
"format": "int32",
"x-example": -1,
"x-example": "85",
"default": -1
},
"in": "query"
@@ -5632,7 +5657,7 @@
"required": false,
"schema": {
"type": "string",
"x-example": "jpg",
"x-example": "jpeg",
"enum": [
"jpg",
"jpeg",
@@ -7370,7 +7395,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 251,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7422,7 +7447,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 250,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7474,7 +7499,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 71,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7526,7 +7551,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 72,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7578,7 +7603,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 76,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7630,7 +7655,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 73,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -7682,7 +7707,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 74,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -7734,7 +7759,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 75,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -7786,7 +7811,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 77,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -7838,7 +7863,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 78,
"weight": 68,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -7890,7 +7915,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 300,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -7973,7 +7998,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 304,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8048,7 +8073,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 161,
"weight": 151,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8145,7 +8170,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 160,
"weight": 150,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8244,7 +8269,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 162,
"weight": 152,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8316,7 +8341,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 167,
"weight": 157,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8407,7 +8432,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 168,
"weight": 158,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8474,7 +8499,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 164,
"weight": 154,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8552,7 +8577,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 163,
"weight": 153,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -8780,7 +8805,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 165,
"weight": 155,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -10281,7 +10306,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 172,
"weight": 162,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10368,7 +10393,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 171,
"weight": 161,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10453,7 +10478,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 173,
"weight": 163,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10515,7 +10540,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 175,
"weight": 165,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10589,7 +10614,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 177,
"weight": 167,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10653,7 +10678,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 179,
"weight": 169,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -10750,7 +10775,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 178,
"weight": 168,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -10814,7 +10839,14 @@
"description": "Array of strings. Use this param to set the user roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
},
"url": {
@@ -10861,7 +10893,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 180,
"weight": 170,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -10933,7 +10965,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 181,
"weight": 171,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -10992,7 +11024,14 @@
"description": "An array of strings. Use this param to set the user's roles in the team. A role can be any string. Learn more about [roles and permissions](https:\/\/appwrite.io\/docs\/permissions). Maximum of 100 roles are allowed, each 32 characters long.",
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
}
},
@@ -11020,7 +11059,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 183,
"weight": 173,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11094,7 +11133,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 182,
"weight": 172,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11192,7 +11231,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 174,
"weight": 164,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11253,7 +11292,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 176,
"weight": 166,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+117 -78
View File
@@ -314,7 +314,7 @@
"x-appwrite": {
"method": "listIdentities",
"group": "identities",
"weight": 58,
"weight": 48,
"cookies": false,
"type": "",
"demo": "account\/list-identities.md",
@@ -385,7 +385,7 @@
"x-appwrite": {
"method": "deleteIdentity",
"group": "identities",
"weight": 59,
"weight": 49,
"cookies": false,
"type": "",
"demo": "account\/delete-identity.md",
@@ -573,7 +573,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 45,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -646,7 +646,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 47,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -766,7 +766,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 48,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -903,7 +903,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 52,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -997,7 +997,7 @@
]
}
},
"\/account\/mfa\/challenge": {
"\/account\/mfa\/challenges": {
"post": {
"summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
@@ -1023,7 +1023,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 53,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1154,7 +1154,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 54,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1290,7 +1290,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 46,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1387,7 +1387,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 51,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1484,7 +1484,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 49,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1581,7 +1581,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 50,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -3033,7 +3033,7 @@
"x-appwrite": {
"method": "createPushTarget",
"group": "pushTargets",
"weight": 55,
"weight": 45,
"cookies": false,
"type": "",
"demo": "account\/create-push-target.md",
@@ -3117,7 +3117,7 @@
"x-appwrite": {
"method": "updatePushTarget",
"group": "pushTargets",
"weight": 56,
"weight": 46,
"cookies": false,
"type": "",
"demo": "account\/update-push-target.md",
@@ -3189,7 +3189,7 @@
"x-appwrite": {
"method": "deletePushTarget",
"group": "pushTargets",
"weight": 57,
"weight": 47,
"cookies": false,
"type": "",
"demo": "account\/delete-push-target.md",
@@ -4030,7 +4030,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 61,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4154,7 +4154,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 60,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4284,7 +4284,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 64,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4346,7 +4346,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 62,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4832,7 +4832,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 63,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4914,7 +4914,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 66,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5004,7 +5004,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 65,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5094,7 +5094,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 67,
"weight": 57,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5135,7 +5135,7 @@
"required": false,
"type": "object",
"default": [],
"x-example": "{}",
"x-example": "{\"Authorization\":\"Bearer token123\",\"X-Custom-Header\":\"value\"}",
"in": "query"
},
{
@@ -5144,7 +5144,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1920",
"default": 1280,
"in": "query"
},
@@ -5154,7 +5154,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1080",
"default": 720,
"in": "query"
},
@@ -5164,7 +5164,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": 0.1,
"x-example": "2",
"default": 1,
"in": "query"
},
@@ -5173,7 +5173,7 @@
"description": "Browser theme. Pass \"light\" or \"dark\". Defaults to \"light\".",
"required": false,
"type": "string",
"x-example": "light",
"x-example": "dark",
"enum": [
"light",
"dark"
@@ -5188,7 +5188,7 @@
"description": "Custom user agent string. Defaults to browser default.",
"required": false,
"type": "string",
"x-example": "<USER_AGENT>",
"x-example": "Mozilla\/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit\/605.1.15",
"default": "",
"in": "query"
},
@@ -5197,7 +5197,7 @@
"description": "Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.",
"required": false,
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false,
"in": "query"
},
@@ -5206,7 +5206,7 @@
"description": "Browser locale (e.g., \"en-US\", \"fr-FR\"). Defaults to browser default.",
"required": false,
"type": "string",
"x-example": "<LOCALE>",
"x-example": "en-US",
"default": "",
"in": "query"
},
@@ -5215,7 +5215,7 @@
"description": "IANA timezone identifier (e.g., \"America\/New_York\", \"Europe\/London\"). Defaults to browser default.",
"required": false,
"type": "string",
"x-example": "africa\/abidjan",
"x-example": "america\/new_york",
"enum": [
"africa\/abidjan",
"africa\/accra",
@@ -5648,7 +5648,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": -90,
"x-example": "37.7749",
"default": 0,
"in": "query"
},
@@ -5658,7 +5658,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": -180,
"x-example": "-122.4194",
"default": 0,
"in": "query"
},
@@ -5668,7 +5668,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": 0,
"x-example": "100",
"default": 0,
"in": "query"
},
@@ -5677,7 +5677,7 @@
"description": "Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.",
"required": false,
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false,
"in": "query"
},
@@ -5688,8 +5688,33 @@
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
"type": "string",
"enum": [
"geolocation",
"camera",
"microphone",
"notifications",
"midi",
"push",
"clipboard-read",
"clipboard-write",
"payment-handler",
"usb",
"bluetooth",
"accelerometer",
"gyroscope",
"magnetometer",
"ambient-light-sensor",
"background-sync",
"persistent-storage",
"screen-wake-lock",
"web-share",
"xr-spatial-tracking"
],
"x-enum-name": "BrowserPermission",
"x-enum-keys": []
},
"x-example": "[\"geolocation\",\"notifications\"]",
"default": [],
"in": "query"
},
@@ -5699,7 +5724,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "3",
"default": 0,
"in": "query"
},
@@ -5709,7 +5734,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "800",
"default": 0,
"in": "query"
},
@@ -5719,7 +5744,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "600",
"default": 0,
"in": "query"
},
@@ -5729,7 +5754,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": -1,
"x-example": "85",
"default": -1,
"in": "query"
},
@@ -5738,7 +5763,7 @@
"description": "Output format type (jpeg, jpg, png, gif and webp).",
"required": false,
"type": "string",
"x-example": "jpg",
"x-example": "jpeg",
"enum": [
"jpg",
"jpeg",
@@ -7420,7 +7445,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 251,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7493,7 +7518,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 250,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7564,7 +7589,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 71,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7615,7 +7640,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 72,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7666,7 +7691,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 76,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7717,7 +7742,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 73,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -7768,7 +7793,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 74,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -7819,7 +7844,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 75,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -7870,7 +7895,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 77,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -7921,7 +7946,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 78,
"weight": 68,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -7974,7 +7999,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 300,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8058,7 +8083,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 304,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8128,7 +8153,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 161,
"weight": 151,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8219,7 +8244,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 160,
"weight": 150,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8308,7 +8333,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 162,
"weight": 152,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8377,7 +8402,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 167,
"weight": 157,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8467,7 +8492,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 168,
"weight": 158,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8536,7 +8561,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 164,
"weight": 154,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8614,7 +8639,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 163,
"weight": 153,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -8820,7 +8845,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 165,
"weight": 155,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -10265,7 +10290,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 172,
"weight": 162,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10348,7 +10373,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 171,
"weight": 161,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10437,7 +10462,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 173,
"weight": 163,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10498,7 +10523,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 175,
"weight": 165,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10572,7 +10597,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 177,
"weight": 167,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10633,7 +10658,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 179,
"weight": 169,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -10724,7 +10749,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 178,
"weight": 168,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -10788,7 +10813,14 @@
"default": null,
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
},
"url": {
@@ -10836,7 +10868,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 180,
"weight": 170,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -10905,7 +10937,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 181,
"weight": 171,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -10959,7 +10991,14 @@
"default": null,
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
}
},
@@ -10990,7 +11029,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 183,
"weight": 173,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11061,7 +11100,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 182,
"weight": 172,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11155,7 +11194,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 174,
"weight": 164,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11216,7 +11255,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 176,
"weight": 166,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+117 -78
View File
@@ -314,7 +314,7 @@
"x-appwrite": {
"method": "listIdentities",
"group": "identities",
"weight": 58,
"weight": 48,
"cookies": false,
"type": "",
"demo": "account\/list-identities.md",
@@ -385,7 +385,7 @@
"x-appwrite": {
"method": "deleteIdentity",
"group": "identities",
"weight": 59,
"weight": 49,
"cookies": false,
"type": "",
"demo": "account\/delete-identity.md",
@@ -573,7 +573,7 @@
"x-appwrite": {
"method": "updateMFA",
"group": "mfa",
"weight": 45,
"weight": 306,
"cookies": false,
"type": "",
"demo": "account\/update-mfa.md",
@@ -646,7 +646,7 @@
"x-appwrite": {
"method": "createMfaAuthenticator",
"group": "mfa",
"weight": 47,
"weight": 308,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-authenticator.md",
@@ -766,7 +766,7 @@
"x-appwrite": {
"method": "updateMfaAuthenticator",
"group": "mfa",
"weight": 48,
"weight": 309,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-authenticator.md",
@@ -903,7 +903,7 @@
"x-appwrite": {
"method": "deleteMfaAuthenticator",
"group": "mfa",
"weight": 52,
"weight": 310,
"cookies": false,
"type": "",
"demo": "account\/delete-mfa-authenticator.md",
@@ -997,7 +997,7 @@
]
}
},
"\/account\/mfa\/challenge": {
"\/account\/mfa\/challenges": {
"post": {
"summary": "Create MFA challenge",
"operationId": "accountCreateMfaChallenge",
@@ -1023,7 +1023,7 @@
"x-appwrite": {
"method": "createMfaChallenge",
"group": "mfa",
"weight": 53,
"weight": 314,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-challenge.md",
@@ -1154,7 +1154,7 @@
"x-appwrite": {
"method": "updateMfaChallenge",
"group": "mfa",
"weight": 54,
"weight": 315,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-challenge.md",
@@ -1290,7 +1290,7 @@
"x-appwrite": {
"method": "listMfaFactors",
"group": "mfa",
"weight": 46,
"weight": 307,
"cookies": false,
"type": "",
"demo": "account\/list-mfa-factors.md",
@@ -1387,7 +1387,7 @@
"x-appwrite": {
"method": "getMfaRecoveryCodes",
"group": "mfa",
"weight": 51,
"weight": 313,
"cookies": false,
"type": "",
"demo": "account\/get-mfa-recovery-codes.md",
@@ -1484,7 +1484,7 @@
"x-appwrite": {
"method": "createMfaRecoveryCodes",
"group": "mfa",
"weight": 49,
"weight": 311,
"cookies": false,
"type": "",
"demo": "account\/create-mfa-recovery-codes.md",
@@ -1581,7 +1581,7 @@
"x-appwrite": {
"method": "updateMfaRecoveryCodes",
"group": "mfa",
"weight": 50,
"weight": 312,
"cookies": false,
"type": "",
"demo": "account\/update-mfa-recovery-codes.md",
@@ -3033,7 +3033,7 @@
"x-appwrite": {
"method": "createPushTarget",
"group": "pushTargets",
"weight": 55,
"weight": 45,
"cookies": false,
"type": "",
"demo": "account\/create-push-target.md",
@@ -3117,7 +3117,7 @@
"x-appwrite": {
"method": "updatePushTarget",
"group": "pushTargets",
"weight": 56,
"weight": 46,
"cookies": false,
"type": "",
"demo": "account\/update-push-target.md",
@@ -3189,7 +3189,7 @@
"x-appwrite": {
"method": "deletePushTarget",
"group": "pushTargets",
"weight": 57,
"weight": 47,
"cookies": false,
"type": "",
"demo": "account\/delete-push-target.md",
@@ -4030,7 +4030,7 @@
"x-appwrite": {
"method": "getBrowser",
"group": null,
"weight": 61,
"weight": 51,
"cookies": false,
"type": "location",
"demo": "avatars\/get-browser.md",
@@ -4154,7 +4154,7 @@
"x-appwrite": {
"method": "getCreditCard",
"group": null,
"weight": 60,
"weight": 50,
"cookies": false,
"type": "location",
"demo": "avatars\/get-credit-card.md",
@@ -4284,7 +4284,7 @@
"x-appwrite": {
"method": "getFavicon",
"group": null,
"weight": 64,
"weight": 54,
"cookies": false,
"type": "location",
"demo": "avatars\/get-favicon.md",
@@ -4346,7 +4346,7 @@
"x-appwrite": {
"method": "getFlag",
"group": null,
"weight": 62,
"weight": 52,
"cookies": false,
"type": "location",
"demo": "avatars\/get-flag.md",
@@ -4832,7 +4832,7 @@
"x-appwrite": {
"method": "getImage",
"group": null,
"weight": 63,
"weight": 53,
"cookies": false,
"type": "location",
"demo": "avatars\/get-image.md",
@@ -4914,7 +4914,7 @@
"x-appwrite": {
"method": "getInitials",
"group": null,
"weight": 66,
"weight": 56,
"cookies": false,
"type": "location",
"demo": "avatars\/get-initials.md",
@@ -5004,7 +5004,7 @@
"x-appwrite": {
"method": "getQR",
"group": null,
"weight": 65,
"weight": 55,
"cookies": false,
"type": "location",
"demo": "avatars\/get-qr.md",
@@ -5094,7 +5094,7 @@
"x-appwrite": {
"method": "getScreenshot",
"group": null,
"weight": 67,
"weight": 57,
"cookies": false,
"type": "location",
"demo": "avatars\/get-screenshot.md",
@@ -5135,7 +5135,7 @@
"required": false,
"type": "object",
"default": [],
"x-example": "{}",
"x-example": "{\"Authorization\":\"Bearer token123\",\"X-Custom-Header\":\"value\"}",
"in": "query"
},
{
@@ -5144,7 +5144,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1920",
"default": 1280,
"in": "query"
},
@@ -5154,7 +5154,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 1,
"x-example": "1080",
"default": 720,
"in": "query"
},
@@ -5164,7 +5164,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": 0.1,
"x-example": "2",
"default": 1,
"in": "query"
},
@@ -5173,7 +5173,7 @@
"description": "Browser theme. Pass \"light\" or \"dark\". Defaults to \"light\".",
"required": false,
"type": "string",
"x-example": "light",
"x-example": "dark",
"enum": [
"light",
"dark"
@@ -5188,7 +5188,7 @@
"description": "Custom user agent string. Defaults to browser default.",
"required": false,
"type": "string",
"x-example": "<USER_AGENT>",
"x-example": "Mozilla\/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit\/605.1.15",
"default": "",
"in": "query"
},
@@ -5197,7 +5197,7 @@
"description": "Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.",
"required": false,
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false,
"in": "query"
},
@@ -5206,7 +5206,7 @@
"description": "Browser locale (e.g., \"en-US\", \"fr-FR\"). Defaults to browser default.",
"required": false,
"type": "string",
"x-example": "<LOCALE>",
"x-example": "en-US",
"default": "",
"in": "query"
},
@@ -5215,7 +5215,7 @@
"description": "IANA timezone identifier (e.g., \"America\/New_York\", \"Europe\/London\"). Defaults to browser default.",
"required": false,
"type": "string",
"x-example": "africa\/abidjan",
"x-example": "america\/new_york",
"enum": [
"africa\/abidjan",
"africa\/accra",
@@ -5648,7 +5648,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": -90,
"x-example": "37.7749",
"default": 0,
"in": "query"
},
@@ -5658,7 +5658,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": -180,
"x-example": "-122.4194",
"default": 0,
"in": "query"
},
@@ -5668,7 +5668,7 @@
"required": false,
"type": "number",
"format": "float",
"x-example": 0,
"x-example": "100",
"default": 0,
"in": "query"
},
@@ -5677,7 +5677,7 @@
"description": "Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.",
"required": false,
"type": "boolean",
"x-example": false,
"x-example": "true",
"default": false,
"in": "query"
},
@@ -5688,8 +5688,33 @@
"type": "array",
"collectionFormat": "multi",
"items": {
"type": "string"
"type": "string",
"enum": [
"geolocation",
"camera",
"microphone",
"notifications",
"midi",
"push",
"clipboard-read",
"clipboard-write",
"payment-handler",
"usb",
"bluetooth",
"accelerometer",
"gyroscope",
"magnetometer",
"ambient-light-sensor",
"background-sync",
"persistent-storage",
"screen-wake-lock",
"web-share",
"xr-spatial-tracking"
],
"x-enum-name": "BrowserPermission",
"x-enum-keys": []
},
"x-example": "[\"geolocation\",\"notifications\"]",
"default": [],
"in": "query"
},
@@ -5699,7 +5724,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "3",
"default": 0,
"in": "query"
},
@@ -5709,7 +5734,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "800",
"default": 0,
"in": "query"
},
@@ -5719,7 +5744,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": 0,
"x-example": "600",
"default": 0,
"in": "query"
},
@@ -5729,7 +5754,7 @@
"required": false,
"type": "integer",
"format": "int32",
"x-example": -1,
"x-example": "85",
"default": -1,
"in": "query"
},
@@ -5738,7 +5763,7 @@
"description": "Output format type (jpeg, jpg, png, gif and webp).",
"required": false,
"type": "string",
"x-example": "jpg",
"x-example": "jpeg",
"enum": [
"jpg",
"jpeg",
@@ -7420,7 +7445,7 @@
"x-appwrite": {
"method": "query",
"group": "graphql",
"weight": 251,
"weight": 241,
"cookies": false,
"type": "graphql",
"demo": "graphql\/query.md",
@@ -7493,7 +7518,7 @@
"x-appwrite": {
"method": "mutation",
"group": "graphql",
"weight": 250,
"weight": 240,
"cookies": false,
"type": "graphql",
"demo": "graphql\/mutation.md",
@@ -7564,7 +7589,7 @@
"x-appwrite": {
"method": "get",
"group": null,
"weight": 71,
"weight": 61,
"cookies": false,
"type": "",
"demo": "locale\/get.md",
@@ -7615,7 +7640,7 @@
"x-appwrite": {
"method": "listCodes",
"group": null,
"weight": 72,
"weight": 62,
"cookies": false,
"type": "",
"demo": "locale\/list-codes.md",
@@ -7666,7 +7691,7 @@
"x-appwrite": {
"method": "listContinents",
"group": null,
"weight": 76,
"weight": 66,
"cookies": false,
"type": "",
"demo": "locale\/list-continents.md",
@@ -7717,7 +7742,7 @@
"x-appwrite": {
"method": "listCountries",
"group": null,
"weight": 73,
"weight": 63,
"cookies": false,
"type": "",
"demo": "locale\/list-countries.md",
@@ -7768,7 +7793,7 @@
"x-appwrite": {
"method": "listCountriesEU",
"group": null,
"weight": 74,
"weight": 64,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-eu.md",
@@ -7819,7 +7844,7 @@
"x-appwrite": {
"method": "listCountriesPhones",
"group": null,
"weight": 75,
"weight": 65,
"cookies": false,
"type": "",
"demo": "locale\/list-countries-phones.md",
@@ -7870,7 +7895,7 @@
"x-appwrite": {
"method": "listCurrencies",
"group": null,
"weight": 77,
"weight": 67,
"cookies": false,
"type": "",
"demo": "locale\/list-currencies.md",
@@ -7921,7 +7946,7 @@
"x-appwrite": {
"method": "listLanguages",
"group": null,
"weight": 78,
"weight": 68,
"cookies": false,
"type": "",
"demo": "locale\/list-languages.md",
@@ -7974,7 +7999,7 @@
"x-appwrite": {
"method": "createSubscriber",
"group": "subscribers",
"weight": 300,
"weight": 290,
"cookies": false,
"type": "",
"demo": "messaging\/create-subscriber.md",
@@ -8058,7 +8083,7 @@
"x-appwrite": {
"method": "deleteSubscriber",
"group": "subscribers",
"weight": 304,
"weight": 294,
"cookies": false,
"type": "",
"demo": "messaging\/delete-subscriber.md",
@@ -8128,7 +8153,7 @@
"x-appwrite": {
"method": "listFiles",
"group": "files",
"weight": 161,
"weight": 151,
"cookies": false,
"type": "",
"demo": "storage\/list-files.md",
@@ -8219,7 +8244,7 @@
"x-appwrite": {
"method": "createFile",
"group": "files",
"weight": 160,
"weight": 150,
"cookies": false,
"type": "upload",
"demo": "storage\/create-file.md",
@@ -8308,7 +8333,7 @@
"x-appwrite": {
"method": "getFile",
"group": "files",
"weight": 162,
"weight": 152,
"cookies": false,
"type": "",
"demo": "storage\/get-file.md",
@@ -8377,7 +8402,7 @@
"x-appwrite": {
"method": "updateFile",
"group": "files",
"weight": 167,
"weight": 157,
"cookies": false,
"type": "",
"demo": "storage\/update-file.md",
@@ -8467,7 +8492,7 @@
"x-appwrite": {
"method": "deleteFile",
"group": "files",
"weight": 168,
"weight": 158,
"cookies": false,
"type": "",
"demo": "storage\/delete-file.md",
@@ -8536,7 +8561,7 @@
"x-appwrite": {
"method": "getFileDownload",
"group": "files",
"weight": 164,
"weight": 154,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-download.md",
@@ -8614,7 +8639,7 @@
"x-appwrite": {
"method": "getFilePreview",
"group": "files",
"weight": 163,
"weight": 153,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-preview.md",
@@ -8820,7 +8845,7 @@
"x-appwrite": {
"method": "getFileView",
"group": "files",
"weight": 165,
"weight": 155,
"cookies": false,
"type": "location",
"demo": "storage\/get-file-view.md",
@@ -10265,7 +10290,7 @@
"x-appwrite": {
"method": "list",
"group": "teams",
"weight": 172,
"weight": 162,
"cookies": false,
"type": "",
"demo": "teams\/list.md",
@@ -10348,7 +10373,7 @@
"x-appwrite": {
"method": "create",
"group": "teams",
"weight": 171,
"weight": 161,
"cookies": false,
"type": "",
"demo": "teams\/create.md",
@@ -10437,7 +10462,7 @@
"x-appwrite": {
"method": "get",
"group": "teams",
"weight": 173,
"weight": 163,
"cookies": false,
"type": "",
"demo": "teams\/get.md",
@@ -10498,7 +10523,7 @@
"x-appwrite": {
"method": "updateName",
"group": "teams",
"weight": 175,
"weight": 165,
"cookies": false,
"type": "",
"demo": "teams\/update-name.md",
@@ -10572,7 +10597,7 @@
"x-appwrite": {
"method": "delete",
"group": "teams",
"weight": 177,
"weight": 167,
"cookies": false,
"type": "",
"demo": "teams\/delete.md",
@@ -10633,7 +10658,7 @@
"x-appwrite": {
"method": "listMemberships",
"group": "memberships",
"weight": 179,
"weight": 169,
"cookies": false,
"type": "",
"demo": "teams\/list-memberships.md",
@@ -10724,7 +10749,7 @@
"x-appwrite": {
"method": "createMembership",
"group": "memberships",
"weight": 178,
"weight": 168,
"cookies": false,
"type": "",
"demo": "teams\/create-membership.md",
@@ -10788,7 +10813,14 @@
"default": null,
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
},
"url": {
@@ -10836,7 +10868,7 @@
"x-appwrite": {
"method": "getMembership",
"group": "memberships",
"weight": 180,
"weight": 170,
"cookies": false,
"type": "",
"demo": "teams\/get-membership.md",
@@ -10905,7 +10937,7 @@
"x-appwrite": {
"method": "updateMembership",
"group": "memberships",
"weight": 181,
"weight": 171,
"cookies": false,
"type": "",
"demo": "teams\/update-membership.md",
@@ -10959,7 +10991,14 @@
"default": null,
"x-example": null,
"items": {
"type": "string"
"type": "string",
"enum": [
"admin",
"developer",
"owner"
],
"x-enum-name": null,
"x-enum-keys": []
}
}
},
@@ -10990,7 +11029,7 @@
"x-appwrite": {
"method": "deleteMembership",
"group": "memberships",
"weight": 183,
"weight": 173,
"cookies": false,
"type": "",
"demo": "teams\/delete-membership.md",
@@ -11061,7 +11100,7 @@
"x-appwrite": {
"method": "updateMembershipStatus",
"group": "memberships",
"weight": 182,
"weight": 172,
"cookies": false,
"type": "",
"demo": "teams\/update-membership-status.md",
@@ -11155,7 +11194,7 @@
"x-appwrite": {
"method": "getPrefs",
"group": "teams",
"weight": 174,
"weight": 164,
"cookies": false,
"type": "",
"demo": "teams\/get-prefs.md",
@@ -11216,7 +11255,7 @@
"x-appwrite": {
"method": "updatePrefs",
"group": "teams",
"weight": 176,
"weight": 166,
"cookies": false,
"type": "",
"demo": "teams\/update-prefs.md",
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+9 -40
View File
@@ -1,43 +1,12 @@
<?php
// TODO: Remove, replace with runtimes.php directly
// Used in function templates and site frameworks
use Utopia\Config\Config;
return [
'NODE' => [
'name' => 'node',
'versions' => ['22', '21.0', '20.0', '19.0', '18.0', '16.0', '14.5']
],
'PYTHON' => [
'name' => 'python',
'versions' => ['3.12', '3.11', '3.10', '3.9', '3.8']
],
'DART' => [
'name' => 'dart',
'versions' => ['3.9', '3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
],
'GO' => [
'name' => 'go',
'versions' => ['1.23']
],
'PHP' => [
'name' => 'php',
'versions' => ['8.3', '8.2', '8.1', '8.0']
],
'DENO' => [
'name' => 'deno',
'versions' => ['2.0', '1.46', '1.40', '1.35', '1.24', '1.21']
],
'BUN' => [
'name' => 'bun',
'versions' => ['1.1', '1.0']
],
'RUBY' => [
'name' => 'ruby',
'versions' => ['3.3', '3.2', '3.1', '3.0']
],
'FLUTTER' => [
'name' => 'flutter',
'versions' => ['3.35', '3.32', '3.24']
],
];
$runtimes = Config::getParam('runtimes');
$mappedRuntimes = \array_reduce($runtimes, function ($acc, $runtime) {
$acc[strtoupper($runtime['key'])][] = $runtime['key'] . '-' . $runtime['version'];
return $acc;
}, []);
return $mappedRuntimes;
+133 -72
View File
@@ -1,20 +1,22 @@
<?php
use Utopia\Config\Config;
use Utopia\System\System;
$templateRuntimes = Config::getParam('template-runtimes');
$allowList = \array_map('trim', \explode(',', System::getEnv('_APP_FUNCTIONS_RUNTIMES', '')));
function getRuntimes($runtime, $commands, $entrypoint, $providerRootDirectory, $versionsDenyList = [])
function getRuntimes($runtimes, $commands, $entrypoint, $providerRootDirectory, $allowList)
{
return array_map(function ($version) use ($runtime, $commands, $entrypoint, $providerRootDirectory) {
return array_map(function ($runtime) use ($commands, $entrypoint, $providerRootDirectory) {
return [
'name' => $runtime['name'] . '-' . $version,
'name' => $runtime,
'commands' => $commands,
'entrypoint' => $entrypoint,
'providerRootDirectory' => $providerRootDirectory
];
}, array_filter($runtime['versions'], function ($version) use ($versionsDenyList) {
return !in_array($version, $versionsDenyList);
}, array_filter($runtimes, function ($runtime) use ($allowList) {
return in_array($runtime, $allowList);
}));
}
@@ -32,24 +34,26 @@ return [
'timeout' => 15,
'useCases' => ['starter'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter'),
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/starter', $allowList),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/starter'
'python/starter',
$allowList
),
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter'),
...getRuntimes($templateRuntimes['GO'], '', 'main.go', 'go/starter'),
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/starter', $allowList),
...getRuntimes($templateRuntimes['GO'], '', 'main.go', 'go/starter', $allowList),
...getRuntimes(
$templateRuntimes['PHP'],
'composer install',
'src/index.php',
'php/starter'
'php/starter',
$allowList
),
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter'),
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter'),
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter'),
...getRuntimes($templateRuntimes['DENO'], 'deno cache src/main.ts', 'src/main.ts', 'deno/starter', $allowList),
...getRuntimes($templateRuntimes['BUN'], 'bun install', 'src/main.ts', 'bun/starter', $allowList),
...getRuntimes($templateRuntimes['RUBY'], 'bundle install', 'lib/main.rb', 'ruby/starter', $allowList),
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/starter">file</a>.',
'vcsProvider' => 'github',
@@ -75,7 +79,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/query-upstash-vector'
'node/query-upstash-vector',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-upstash-vector">file</a>.',
@@ -120,7 +125,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/query-redis-labs'
'node/query-redis-labs',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-redis-labs">file</a>.',
@@ -164,7 +170,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/query-neo4j-auradb'
'node/query-neo4j-auradb',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-neo4j-auradb">file</a>.',
@@ -217,7 +224,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/query-mongo-atlas'
'node/query-mongo-atlas',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-mongo-atlas">file</a>.',
@@ -255,7 +263,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/query-neon-postgres'
'node/query-neon-postgres',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/query-neon-postgres">file</a>.',
@@ -323,25 +332,29 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/prompt-chatgpt'
'node/prompt-chatgpt',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/prompt_chatgpt'
'python/prompt_chatgpt',
$allowList
),
...getRuntimes(
$templateRuntimes['PHP'],
'composer install',
'src/index.php',
'php/prompt-chatgpt'
'php/prompt-chatgpt',
$allowList
),
...getRuntimes(
$templateRuntimes['DART'],
'dart pub get',
'lib/main.dart',
'dart/prompt_chatgpt'
'dart/prompt_chatgpt',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/prompt-chatgpt">file</a>.',
@@ -385,19 +398,22 @@ return [
$templateRuntimes['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/discord-command-bot'
'node/discord-command-bot',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt && python src/setup.py',
'src/main.py',
'python/discord_command_bot'
'python/discord_command_bot',
$allowList
),
...getRuntimes(
$templateRuntimes['GO'],
'',
'main.go',
'go/discord-command-bot'
'go/discord-command-bot',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/discord-command-bot">file</a>.',
@@ -449,7 +465,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/analyze-with-perspectiveapi'
'node/analyze-with-perspectiveapi',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/analyze-with-perspectiveapi">file</a>.',
@@ -486,19 +503,22 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/censor-with-redact'
'node/censor-with-redact',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/censor_with_redact'
'python/censor_with_redact',
$allowList
),
...getRuntimes(
$templateRuntimes['DART'],
'dart pub get',
'lib/main.dart',
'dart/censor_with_redact'
'dart/censor_with_redact',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/censor-with-redact">file</a>.',
@@ -530,7 +550,7 @@ return [
'timeout' => 15,
'useCases' => ['utilities'],
'runtimes' => [
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf')
...getRuntimes($templateRuntimes['NODE'], 'npm install', 'src/main.js', 'node/generate-pdf', $allowList)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-pdf">file</a>.',
'vcsProvider' => 'github',
@@ -557,7 +577,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/github-issue-bot'
'node/github-issue-bot',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/github-issue-bot">file</a>.',
@@ -601,7 +622,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/url-shortener'
'node/url-shortener',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/url-shortener">file</a>.',
@@ -653,19 +675,22 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/sync-with-algolia'
'node/sync-with-algolia',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/sync_with_algolia'
'python/sync_with_algolia',
$allowList
),
...getRuntimes(
$templateRuntimes['PHP'],
'composer install',
'src/index.php',
'php/sync-with-algolia'
'php/sync-with-algolia',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-algolia">file</a>.',
@@ -735,31 +760,36 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/sync-with-meilisearch'
'node/sync-with-meilisearch',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/sync-with-meilisearch'
'python/sync-with-meilisearch',
$allowList
),
...getRuntimes(
$templateRuntimes['PHP'],
'composer install',
'src/index.php',
'php/sync-with-meilisearch'
'php/sync-with-meilisearch',
$allowList
),
...getRuntimes(
$templateRuntimes['BUN'],
'bun install',
'src/main.ts',
'bun/sync-with-meilisearch'
'bun/sync-with-meilisearch',
$allowList
),
...getRuntimes(
$templateRuntimes['RUBY'],
'bundle install',
'lib/main.rb',
'ruby/sync-with-meilisearch'
'ruby/sync-with-meilisearch',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-meilisearch">file</a>.',
@@ -829,37 +859,43 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/whatsapp-with-vonage'
'node/whatsapp-with-vonage',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/whatsapp_with_vonage'
'python/whatsapp_with_vonage',
$allowList
),
...getRuntimes(
$templateRuntimes['DART'],
'dart pub get',
'lib/main.dart',
'dart/whatsapp-with-vonage'
'dart/whatsapp-with-vonage',
$allowList
),
...getRuntimes(
$templateRuntimes['PHP'],
'composer install',
'src/index.php',
'php/whatsapp-with-vonage'
'php/whatsapp-with-vonage',
$allowList
),
...getRuntimes(
$templateRuntimes['BUN'],
'bun install',
'src/main.ts',
'bun/whatsapp-with-vonage'
'bun/whatsapp-with-vonage',
$allowList
),
...getRuntimes(
$templateRuntimes['RUBY'],
'bundle install',
'lib/main.rb',
'ruby/whatsapp-with-vonage'
'ruby/whatsapp-with-vonage',
$allowList
),
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/whatsapp-with-vonage">file</a>.',
@@ -916,7 +952,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/push-notification-with-fcm'
'node/push-notification-with-fcm',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/push-notification-with-fcm">file</a>.',
@@ -973,19 +1010,22 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/email-contact-form'
'node/email-contact-form',
$allowList
),
...getRuntimes(
$templateRuntimes['PYTHON'],
'pip install -r requirements.txt',
'src/main.py',
'python/email_contact_form'
'python/email_contact_form',
$allowList
),
...getRuntimes(
$templateRuntimes['PHP'],
'composer install',
'src/index.php',
'php/email-contact-form'
'php/email-contact-form',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/email-contact-form">file</a>.',
@@ -1057,7 +1097,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/subscriptions-with-stripe'
'node/subscriptions-with-stripe',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/subscriptions-with-stripe">file</a>.',
@@ -1099,7 +1140,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/payments-with-stripe'
'node/payments-with-stripe',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/payments-with-stripe">file</a>.',
@@ -1157,7 +1199,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/text-generation-with-huggingface'
'node/text-generation-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/text-generation-with-huggingface">file</a>.',
@@ -1192,7 +1235,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/language-translation-with-huggingface'
'node/language-translation-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/language-translation-with-huggingface">file</a>.',
@@ -1227,7 +1271,8 @@ return [
$templateRuntimes['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/image-classification-with-huggingface'
'node/image-classification-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/image-classification-with-huggingface">file</a>.',
@@ -1286,7 +1331,8 @@ return [
$templateRuntimes['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/object-detection-with-huggingface'
'node/object-detection-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/object-detection-with-huggingface">file</a>.',
@@ -1345,7 +1391,8 @@ return [
$templateRuntimes['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/speech-recognition-with-huggingface'
'node/speech-recognition-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speech-recognition-with-huggingface">file</a>.',
@@ -1407,7 +1454,8 @@ return [
$templateRuntimes['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/text-to-speech-with-huggingface'
'node/text-to-speech-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/text-to-speech-with-huggingface">file</a>.',
@@ -1466,7 +1514,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/generate-with-replicate'
'node/generate-with-replicate',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-with-replicate">file</a>.',
@@ -1502,7 +1551,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/generate-with-together-ai'
'node/generate-with-together-ai',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-with-together-ai">file</a>.',
@@ -1545,7 +1595,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/chat-with-perplexity-ai'
'node/chat-with-perplexity-ai',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/chat-with-perplexity-ai">file</a>.',
@@ -1587,7 +1638,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/generate-with-replicate'
'node/generate-with-replicate',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-with-replicate">file</a>.',
@@ -1623,7 +1675,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/sync-with-pinecone'
'node/sync-with-pinecone',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-pinecone">file</a>.',
@@ -1687,7 +1740,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/rag-with-langchain'
'node/rag-with-langchain',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/rag-with-langchain">file</a>.',
@@ -1751,7 +1805,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/speak-with-elevenlabs'
'node/speak-with-elevenlabs',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speak-with-elevenlabs">file</a>.',
@@ -1807,7 +1862,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/speak-with-lmnt'
'node/speak-with-lmnt',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speak-with-lmnt">file</a>.',
@@ -1849,7 +1905,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/chat-with-anyscale'
'node/chat-with-anyscale',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/chat-with-anyscale">file</a>.',
@@ -1891,7 +1948,8 @@ return [
$templateRuntimes['NODE'],
'npm install && npm run setup',
'src/main.js',
'node/music-generation-with-huggingface'
'node/music-generation-with-huggingface',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/music-generation-with-huggingface">file</a>.',
@@ -1934,7 +1992,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/generate-with-fal-ai'
'node/generate-with-fal-ai',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-with-fal-ai">file</a>.',
@@ -1970,7 +2029,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/subscriptions-with-lemon-squeezy'
'node/subscriptions-with-lemon-squeezy',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/subscriptions-with-lemon-squeezy">file</a>.',
@@ -2026,7 +2086,8 @@ return [
$templateRuntimes['NODE'],
'npm install',
'src/main.js',
'node/payments-with-lemon-squeezy'
'node/payments-with-lemon-squeezy',
$allowList
)
],
'instructions' => 'For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/payments-with-lemon-squeezy">file</a>.',
@@ -2094,7 +2155,7 @@ return [
'timeout' => 15,
'useCases' => ['auth'],
'runtimes' => [
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple')
...getRuntimes($templateRuntimes['DART'], 'dart pub get', 'lib/main.dart', 'dart/sign_in_with_apple', $allowList)
],
'instructions' => 'For documentation and instructions, check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/dart/sign_in_with_apple">file</a>.',
'vcsProvider' => 'github',
File diff suppressed because it is too large Load Diff
+25 -25
View File
@@ -96,8 +96,8 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
$appId = $project->getAttribute('oAuthProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('oAuthProviders', [])[$provider . 'Secret'] ?? '{}';
$className = 'Appwrite\\Auth\\OAuth2\\' . \ucfirst($provider);
$oAuthProviders = Config::getParam('oAuthProviders');
$className = $oAuthProviders[$provider]['class'];
if (!\class_exists($className)) {
throw new Exception(Exception::PROJECT_PROVIDER_UNSUPPORTED);
}
@@ -662,26 +662,26 @@ App::get('/v1/avatars/screenshots')
],
contentType: ContentType::IMAGE_PNG
))
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to capture.')
->param('headers', [], new Assoc(), 'HTTP headers to send with the browser request. Defaults to empty.', true)
->param('viewportWidth', 1280, new Range(1, 1920), 'Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280.', true)
->param('viewportHeight', 720, new Range(1, 1080), 'Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720.', true)
->param('scale', 1, new Range(0.1, 3, Range::TYPE_FLOAT), 'Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1.', true)
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true)
->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true)
->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true)
->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true)
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true)
->param('latitude', 0, new Range(-90, 90, Range::TYPE_FLOAT), 'Geolocation latitude. Pass a number between -90 to 90. Defaults to 0.', true)
->param('longitude', 0, new Range(-180, 180, Range::TYPE_FLOAT), 'Geolocation longitude. Pass a number between -180 to 180. Defaults to 0.', true)
->param('accuracy', 0, new Range(0, 100000, Range::TYPE_FLOAT), 'Geolocation accuracy in meters. Pass a number between 0 to 100000. Defaults to 0.', true)
->param('touch', false, new Boolean(true), 'Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.', true)
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true)
->param('sleep', 0, new Range(0, 10), 'Wait time in seconds before taking the screenshot. Pass an integer between 0 to 10. Defaults to 0.', true)
->param('width', 0, new Range(0, 2000), 'Output image width. Pass 0 to use original width, or an integer between 1 to 2000. Defaults to 0 (original width).', true)
->param('height', 0, new Range(0, 2000), 'Output image height. Pass 0 to use original height, or an integer between 1 to 2000. Defaults to 0 (original height).', true)
->param('quality', -1, new Range(-1, 100), 'Screenshot quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true)
->param('url', '', new URL(['http', 'https']), 'Website URL which you want to capture.', example: 'https://example.com')
->param('headers', [], new Assoc(), 'HTTP headers to send with the browser request. Defaults to empty.', true, example: '{"Authorization":"Bearer token123","X-Custom-Header":"value"}')
->param('viewportWidth', 1280, new Range(1, 1920), 'Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280.', true, example: '1920')
->param('viewportHeight', 720, new Range(1, 1080), 'Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720.', true, example: '1080')
->param('scale', 1, new Range(0.1, 3, Range::TYPE_FLOAT), 'Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1.', true, example: '2')
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark')
->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true, example: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15')
->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true, example: 'true')
->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true, example: 'en-US')
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'america/new_york')
->param('latitude', 0, new Range(-90, 90, Range::TYPE_FLOAT), 'Geolocation latitude. Pass a number between -90 to 90. Defaults to 0.', true, example: '37.7749')
->param('longitude', 0, new Range(-180, 180, Range::TYPE_FLOAT), 'Geolocation longitude. Pass a number between -180 to 180. Defaults to 0.', true, example: '-122.4194')
->param('accuracy', 0, new Range(0, 100000, Range::TYPE_FLOAT), 'Geolocation accuracy in meters. Pass a number between 0 to 100000. Defaults to 0.', true, example: '100')
->param('touch', false, new Boolean(true), 'Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.', true, example: 'true')
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]')
->param('sleep', 0, new Range(0, 10), 'Wait time in seconds before taking the screenshot. Pass an integer between 0 to 10. Defaults to 0.', true, example: '3')
->param('width', 0, new Range(0, 2000), 'Output image width. Pass 0 to use original width, or an integer between 1 to 2000. Defaults to 0 (original width).', true, example: '800')
->param('height', 0, new Range(0, 2000), 'Output image height. Pass 0 to use original height, or an integer between 1 to 2000. Defaults to 0 (original height).', true, example: '600')
->param('quality', -1, new Range(-1, 100), 'Screenshot quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true, example: '85')
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg')
->inject('response')
->inject('queueForStatsUsage')
->action(function (string $url, array $headers, int $viewportWidth, int $viewportHeight, float $scale, string $theme, string $userAgent, bool $fullpage, string $locale, string $timezone, float $latitude, float $longitude, float $accuracy, bool $touch, array $permissions, int $sleep, int $width, int $height, int $quality, string $output, Response $response, StatsUsage $queueForStatsUsage) {
@@ -854,7 +854,7 @@ App::get('/v1/cards/cloud')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
@@ -1061,7 +1061,7 @@ App::get('/v1/cards/cloud-back')
$userId = $user->getId();
$email = $user->getAttribute('email', '');
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubId = $gitHub['id'] ?? '';
$isHero = \array_key_exists($email, $heroes);
@@ -1145,7 +1145,7 @@ App::get('/v1/cards/cloud-og')
$email = $user->getAttribute('email', '');
$createdAt = new \DateTime($user->getCreatedAt());
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $authorization, $logger);
$githubName = $gitHub['name'] ?? '';
$githubId = $gitHub['id'] ?? '';
+2 -2
View File
@@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\GraphQL\Promises\Adapter;
@@ -9,6 +8,7 @@ use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use GraphQL\Error\DebugFlag;
@@ -33,7 +33,7 @@ App::init()
if (
array_key_exists('graphql', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['graphql']
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
+7 -5
View File
@@ -120,7 +120,7 @@ App::get('/v1/health/db')
$output[] = new Document([
'name' => $key . " ($database)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
]);
} else {
$failures[] = $database;
@@ -131,6 +131,8 @@ App::get('/v1/health/db')
}
}
// Only throw error if ALL databases failed (no successful pings)
// This allows partial failures in environments where not all DBs are ready
if (!empty($failures)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'DB failure on: ' . implode(", ", $failures));
}
@@ -180,7 +182,7 @@ App::get('/v1/health/cache')
$output[] = new Document([
'name' => $key . " ($cache)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
]);
} else {
$failures[] = $cache;
@@ -240,7 +242,7 @@ App::get('/v1/health/pubsub')
$output[] = new Document([
'name' => $key . " ($pubsub)",
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
]);
} else {
$failures[] = $pubsub;
@@ -822,7 +824,7 @@ App::get('/v1/health/storage/local')
$output = [
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
@@ -874,7 +876,7 @@ App::get('/v1/health/storage')
$output = [
'status' => 'pass',
'ping' => \round((\microtime(true) - $checkStart) / 1000)
'ping' => \round((\microtime(true) - $checkStart) * 1000)
];
$response->dynamic(new Document($output), Response::MODEL_HEALTH_STATUS);
+15 -7
View File
@@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Event\Event;
use Appwrite\Event\Migration;
use Appwrite\Extend\Exception;
@@ -341,12 +340,21 @@ App::post('/v1/migrations/csv/imports')
->inject('deviceForMigrations')
->inject('queueForEvents')
->inject('queueForMigrations')
->action(function (string $bucketId, string $fileId, string $resourceId, bool $internalFile, Response $response, Database $dbForProject, Database $dbForPlatform, Authorization $authorization, Document $project, Device $deviceForFiles, Device $deviceForMigrations, Event $queueForEvents, Migration $queueForMigrations) {
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
if ($internalFile && !$isPrivilegedUser) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
->action(function (
string $bucketId,
string $fileId,
string $resourceId,
bool $internalFile,
Response $response,
Database $dbForProject,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
Device $deviceForFiles,
Device $deviceForMigrations,
Event $queueForEvents,
Migration $queueForMigrations
) {
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
if ($internalFile) {
return $dbForPlatform->getDocument('buckets', 'default');
+1 -2
View File
@@ -1,7 +1,6 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\MockNumber;
use Appwrite\Event\Delete;
use Appwrite\Event\Mail;
@@ -119,7 +118,7 @@ App::post('/v1/projects')
'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT,
'passwordHistory' => 0,
'passwordDictionary' => false,
'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG,
'duration' => TOKEN_EXPIRATION_LOGIN_LONG,
'personalDataCheck' => false,
'mockNumbers' => [],
'sessionAlerts' => false,
+36 -27
View File
@@ -2,7 +2,6 @@
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\ClamAV\Network;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@@ -13,6 +12,7 @@ use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Buckets;
use Appwrite\Utopia\Database\Validator\Queries\Files;
@@ -439,8 +439,8 @@ App::post('/v1/storage/buckets/:bucketId/files')
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -471,7 +471,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = $authorization->getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
if (!User::isApp($roles) && !User::isPrivileged($roles)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
@@ -682,7 +682,13 @@ App::post('/v1/storage/buckets/:bucketId/files')
'metadata' => $metadata,
]);
$file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc);
try {
$file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc);
} catch (DuplicateException) {
throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS);
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
} else {
$file = $file
->setAttribute('$permissions', $permissions)
@@ -731,6 +737,8 @@ App::post('/v1/storage/buckets/:bucketId/files')
try {
$file = $dbForProject->createDocument('bucket_' . $bucket->getSequence(), $doc);
} catch (DuplicateException) {
throw new Exception(Exception::STORAGE_FILE_ALREADY_EXISTS);
} catch (NotFoundException) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
}
@@ -799,8 +807,8 @@ App::get('/v1/storage/buckets/:bucketId/files')
->action(function (string $bucketId, array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject, Authorization $authorization, string $mode) {
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -898,8 +906,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Authorization $authorization, string $mode) {
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -980,8 +988,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
/* @type Document $bucket */
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1006,7 +1014,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
}
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if ($file->isEmpty()) {
@@ -1124,7 +1132,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
//Do not update transformedAt if it's a console user
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
if (!User::isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
@@ -1176,8 +1184,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
/* @type Document $bucket */
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1337,8 +1345,8 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
/* @type Document $bucket */
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1495,7 +1503,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
try {
$decoded = $decoder->decode($jwt);
} catch (JWTException) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
throw new Exception(Exception::USER_UNAUTHORIZED);
}
if (
@@ -1503,14 +1511,15 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
$decoded['bucketId'] !== $bucketId ||
$decoded['fileId'] !== $fileId
) {
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$isInternal = $decoded['internal'] ?? false;
$disposition = $decoded['disposition'] ?? 'inline';
$dbForProject = $isInternal ? $dbForPlatform : $dbForProject;
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
@@ -1562,7 +1571,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
->setContentType($contentType)
->addHeader('Content-Security-Policy', 'script-src none;')
->addHeader('X-Content-Type-Options', 'nosniff')
->addHeader('Content-Disposition', 'inline; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Content-Disposition', $disposition . '; filename="' . $file->getAttribute('name', '') . '"')
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
->addHeader('X-Peak', \memory_get_peak_usage());
@@ -1666,8 +1675,8 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -1695,7 +1704,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
// Users can only manage their own roles, API keys and Admin users can manage any
$roles = $authorization->getRoles();
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
if (!User::isApp($roles) && !User::isPrivileged($roles) && !\is_null($permissions)) {
foreach (Database::PERMISSIONS as $type) {
foreach ($permissions as $permission) {
$permission = Permission::parse($permission);
@@ -1780,8 +1789,8 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) {
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isAPIKey = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAPIKey = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
+85 -70
View File
@@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
@@ -18,6 +17,7 @@ use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Template\Template;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Database\Validator\CustomId;
use Appwrite\Utopia\Database\Validator\Queries\Memberships;
use Appwrite\Utopia\Database\Validator\Queries\Teams;
@@ -28,6 +28,9 @@ use MaxMind\Db\Reader;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -88,8 +91,8 @@ App::post('/v1/teams')
->inject('queueForEvents')
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAppUser = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
@@ -177,6 +180,7 @@ App::get('/v1/teams')
->inject('dbForProject')
->action(function (array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject) {
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
@@ -475,10 +479,9 @@ App::post('/v1/teams/:teamId/memberships')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
;
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
$roles = array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
@@ -498,9 +501,11 @@ App::post('/v1/teams/:teamId/memberships')
->inject('timelimit')
->inject('queueForStatsUsage')
->inject('plan')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
$isAppUser = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
->inject('proofForPassword')
->inject('proofForToken')
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Password $proofForPassword, Token $proofForToken) {
$isAppUser = User::isApp($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$url = htmlentities($url);
if (empty($url)) {
@@ -570,49 +575,54 @@ App::post('/v1/teams/:teamId/memberships')
}
try {
$userId = ID::unique();
$hash = $proofForPassword->hash($proofForPassword->generate());
$emailCanonical = new Email($email);
} catch (Throwable) {
$emailCanonical = null;
}
$userId = ID::unique();
$userDocument = new Document([
'$id' => $userId,
'$permissions' => [
Permission::read(Role::any()),
Permission::read(Role::user($userId)),
Permission::update(Role::user($userId)),
Permission::delete(Role::user($userId)),
],
'email' => empty($email) ? null : $email,
'phone' => empty($phone) ? null : $phone,
'emailVerification' => false,
'status' => true,
// TODO: Set password empty?
'password' => $hash,
'hash' => $proofForPassword->getHash()->getName(),
'hashOptions' => $proofForPassword->getHash()->getOptions(),
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
* old password
*/
'passwordUpdate' => null,
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
]);
try {
$userId = ID::unique();
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
Permission::read(Role::any()),
Permission::read(Role::user($userId)),
Permission::update(Role::user($userId)),
Permission::delete(Role::user($userId)),
],
'email' => empty($email) ? null : $email,
'phone' => empty($phone) ? null : $phone,
'emailVerification' => false,
'status' => true,
// TODO: Set password empty?
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
/**
* Set the password update time to 0 for users created using
* team invite and OAuth to allow password updates without an
* old password
*/
'passwordUpdate' => null,
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
'prefs' => new \stdClass(),
'sessions' => null,
'tokens' => null,
'memberships' => null,
'search' => implode(' ', [$userId, $email, $name]),
'emailCanonical' => $emailCanonical?->getCanonical(),
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
'emailIsCorporate' => $emailCanonical?->isCorporate(),
'emailIsDisposable' => $emailCanonical?->isDisposable(),
'emailIsFree' => $emailCanonical?->isFree(),
])));
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', $userDocument));
} catch (Duplicate $th) {
throw new Exception(Exception::USER_ALREADY_EXISTS);
}
@@ -629,7 +639,7 @@ App::post('/v1/teams/:teamId/memberships')
Query::equal('teamInternalId', [$team->getSequence()]),
]);
$secret = Auth::tokenGenerator();
$secret = $proofForToken->generate();
if ($membership->isEmpty()) {
$membershipId = ID::unique();
$membership = new Document([
@@ -649,7 +659,7 @@ App::post('/v1/teams/:teamId/memberships')
'invited' => DateTime::now(),
'joined' => ($isPrivilegedUser || $isAppUser) ? DateTime::now() : null,
'confirm' => ($isPrivilegedUser || $isAppUser),
'secret' => Auth::hash($secret),
'secret' => $proofForToken->hash($secret),
'search' => implode(' ', [$membershipId, $invitee->getId()])
]);
@@ -660,9 +670,8 @@ App::post('/v1/teams/:teamId/memberships')
if ($isPrivilegedUser || $isAppUser) {
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
}
} elseif ($membership->getAttribute('confirm') === false) {
$membership->setAttribute('secret', Auth::hash($secret));
$membership->setAttribute('secret', $proofForToken->hash($secret));
$membership->setAttribute('invited', DateTime::now());
if ($isPrivilegedUser || $isAppUser) {
@@ -765,7 +774,6 @@ App::post('/v1/teams/:teamId/memberships')
->setName($invitee->getAttribute('name', ''))
->setVariables($emailVariables)
->trigger();
} elseif (!empty($phone)) {
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
@@ -930,8 +938,8 @@ App::get('/v1/teams/:teamId/memberships')
];
$roles = $authorization->getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
return $privacy || $isPrivilegedUser || $isAppUser;
@@ -1022,8 +1030,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
];
$roles = $authorization->getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
$membershipsPrivacy = array_map(function ($privacy) use ($isPrivilegedUser, $isAppUser) {
return $privacy || $isPrivilegedUser || $isAppUser;
@@ -1088,8 +1096,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
->param('roles', [], function (Document $project) {
if ($project->getId() === 'console') {
$roles = array_keys(Config::getParam('roles', []));
array_filter($roles, function ($role) {
return !in_array($role, [Auth::USER_ROLE_APPS, Auth::USER_ROLE_GUESTS, Auth::USER_ROLE_USERS]);
$roles = array_filter($roles, function ($role) {
return !in_array($role, [User::ROLE_APPS, User::ROLE_GUESTS, User::ROLE_USERS]);
});
return new ArrayList(new WhiteList($roles), APP_LIMIT_ARRAY_PARAMS_SIZE);
}
@@ -1119,8 +1127,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
throw new Exception(Exception::USER_NOT_FOUND);
}
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAppUser = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
if ($project->getId() === 'console') {
@@ -1206,7 +1214,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
->inject('project')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Document $project, Reader $geodb, Event $queueForEvents) {
->inject('store')
->inject('proofForToken')
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, $project, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$protocol = $request->getProtocol();
$membership = $dbForProject->getDocument('memberships', $membershipId);
@@ -1225,7 +1235,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
throw new Exception(Exception::TEAM_MEMBERSHIP_MISMATCH);
}
if (Auth::hash($secret) !== $membership->getAttribute('secret')) {
if (!$proofForToken->verify($secret, $membership->getAttribute('secret'))) {
throw new Exception(Exception::TEAM_INVALID_SECRET);
}
@@ -1259,9 +1269,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$authDuration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$authDuration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::addSeconds(new \DateTime(), $authDuration);
$secret = Auth::tokenGenerator();
$secret = $proofForToken->generate();
$session = new Document(array_merge([
'$id' => ID::unique(),
'$permissions' => [
@@ -1271,9 +1281,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
],
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'provider' => Auth::SESSION_PROVIDER_EMAIL,
'provider' => SESSION_PROVIDER_EMAIL,
'providerUid' => $user->getAttribute('email'),
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP(),
'factors' => ['email'],
@@ -1285,14 +1295,19 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
$authorization->addRole(Role::user($userId)->toString());
$encoded = $store
->setProperty('id', $user->getId())
->setProperty('secret', $secret)
->encode();
if (!Config::getParam('domainVerification')) {
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
$response->addHeader('X-Fallback-Cookies', \json_encode([$store->getKey() => $encoded]));
}
$response
->addCookie(
name: Auth::$cookieName . '_legacy',
value: Auth::encodeSession($user->getId(), $secret),
name: $store->getKey() . '_legacy',
value: $encoded,
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
@@ -1300,8 +1315,8 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
httponly: true
)
->addCookie(
name: Auth::$cookieName,
value: Auth::encodeSession($user->getId(), $secret),
name: $store->getKey(),
value: $encoded,
expire: (new \DateTime($expire))->getTimestamp(),
path: '/',
domain: Config::getParam('cookieDomain'),
+100 -76
View File
@@ -1,7 +1,6 @@
<?php
use Ahc\Jwt\JWT;
use Appwrite\Auth\Auth;
use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Auth\Validator\Password;
@@ -32,6 +31,18 @@ use Appwrite\Utopia\Response;
use MaxMind\Db\Reader;
use Utopia\App;
use Utopia\Audit\Audit;
use Utopia\Auth\Hash;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Bcrypt;
use Utopia\Auth\Hashes\MD5;
use Utopia\Auth\Hashes\PHPass;
use Utopia\Auth\Hashes\Plaintext;
use Utopia\Auth\Hashes\Scrypt;
use Utopia\Auth\Hashes\ScryptModified;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Password as ProofsPassword;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -62,10 +73,9 @@ use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
/** TODO: Remove function when we move to using utopia/platform */
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
function createUser(Hash $hash, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Hooks $hooks): Document
{
$plaintextPassword = $password;
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
if (!empty($email)) {
@@ -104,8 +114,24 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
} catch (Throwable) {
$emailCanonical = null;
}
$hashedPassword = null;
$isHashed = !$hash instanceof Plaintext;
$defaultHash = new ProofsPassword();
if (!empty($password)) {
if (!$isHashed) { // Password was never hashed, hash it with the default hash
$hashedPassword = $defaultHash->hash($password);
$hash = $defaultHash->getHash();
} else {
$hashedPassword = $password;
}
} else {
// when password is not provided, plaintext was set as the default hash causing the issue
$hash = $defaultHash->getHash();
$isHashed = !$hash instanceof Plaintext;
}
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = new Document([
'$id' => $userId,
'$permissions' => [
@@ -119,11 +145,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'phoneVerification' => false,
'status' => true,
'labels' => [],
'password' => $password,
'passwordHistory' => is_null($password) || $passwordHistory === 0 ? [] : [$password],
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash,
'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptionsObject + ['type' => $hash],
'password' => $hashedPassword,
'passwordHistory' => is_null($hashedPassword) || $passwordHistory === 0 ? [] : [$hashedPassword],
'passwordUpdate' => (!empty($hashedPassword)) ? DateTime::now() : null,
'hash' => $hash->getName(),
'hashOptions' => $hash->getOptions(),
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
@@ -139,7 +165,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'emailIsFree' => $emailCanonical?->isFree(),
]);
if ($hash === 'plaintext') {
if (!$isHashed && !empty($password)) {
$hooks->trigger('passwordValidator', [$dbForProject, $project, $plaintextPassword, &$user, true]);
}
@@ -230,7 +256,9 @@ App::post('/v1/users')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$plaintext = new Plaintext();
$user = createUser($plaintext, $userId, $email, $password, $phone, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($user, Response::MODEL_USER);
@@ -264,7 +292,10 @@ App::post('/v1/users/bcrypt')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$bcrypt = new Bcrypt();
$bcrypt->setCost(8); // Default cost
$user = createUser($bcrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -299,7 +330,9 @@ App::post('/v1/users/md5')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$md5 = new MD5();
$user = createUser($md5, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -334,7 +367,9 @@ App::post('/v1/users/argon2')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$argon2 = new Argon2();
$user = createUser($argon2, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -370,13 +405,12 @@ App::post('/v1/users/sha')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$options = '{}';
$sha = new Sha();
if (!empty($passwordVersion)) {
$options = '{"version":"' . $passwordVersion . '"}';
$sha->setVersion($passwordVersion);
}
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($sha, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -411,7 +445,9 @@ App::post('/v1/users/phpass')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$phpass = new PHPass();
$user = createUser($phpass, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -451,15 +487,15 @@ App::post('/v1/users/scrypt')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$options = [
'salt' => $passwordSalt,
'costCpu' => $passwordCpu,
'costMemory' => $passwordMemory,
'costParallel' => $passwordParallel,
'length' => $passwordLength
];
$scrypt = new Scrypt();
$scrypt
->setSalt($passwordSalt)
->setCpuCost($passwordCpu)
->setMemoryCost($passwordMemory)
->setParallelCost($passwordParallel)
->setLength($passwordLength);
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$user = createUser($scrypt, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -497,7 +533,13 @@ App::post('/v1/users/scrypt-modified')
->inject('dbForProject')
->inject('hooks')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Hooks $hooks) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$scryptModified = new ScryptModified();
$scryptModified
->setSalt($passwordSalt)
->setSaltSeparator($passwordSaltSeparator)
->setSignerKey($passwordSignerKey);
$user = createUser($scryptModified, $userId, $email, $password, null, $name, $project, $dbForProject, $hooks);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -809,16 +851,12 @@ App::get('/v1/users/:userId/sessions')
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$sessions = $user->getAttribute('sessions', []);
foreach ($sessions as $key => $session) {
/** @var Document $session */
$countryName = $locale->getText('countries.' . strtolower($session->getAttribute('countryCode')), $locale->getText('locale.country.unknown'));
$session->setAttribute('countryName', $countryName);
$session->setAttribute('current', false);
$sessions[$key] = $session;
}
@@ -858,28 +896,22 @@ App::get('/v1/users/:userId/memberships')
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
// Set internal queries
$queries[] = Query::equal('userInternalId', [$user->getSequence()]);
$memberships = array_map(function ($membership) use ($dbForProject, $user) {
$team = $dbForProject->getDocument('teams', $membership->getAttribute('teamId'));
$membership
->setAttribute('teamName', $team->getAttribute('name'))
->setAttribute('userName', $user->getAttribute('name'))
->setAttribute('userEmail', $user->getAttribute('email'));
return $membership;
}, $dbForProject->find('memberships', $queries));
@@ -920,35 +952,26 @@ App::get('/v1/users/:userId/logs')
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
// Temp fix for logs
$queries[] = Query::or([
Query::greaterThan('$createdAt', DateTime::format(new \DateTime('2025-02-26T01:30+00:00'))),
Query::lessThan('$createdAt', DateTime::format(new \DateTime('2025-02-13T00:00+00:00'))),
]);
$audit = new Audit($dbForProject);
$logs = $audit->getLogsByUser($user->getSequence(), $queries);
$output = [];
foreach ($logs as $i => &$log) {
$log['userAgent'] = (!empty($log['userAgent'])) ? $log['userAgent'] : 'UNKNOWN';
$detector = new Detector($log['userAgent']);
$detector->skipBotDetection(); // OPTIONAL: If called, bot detection will completely be skipped (bots will be detected as regular devices then)
$os = $detector->getOS();
$client = $detector->getClient();
$device = $detector->getDevice();
$output[$i] = new Document([
'event' => $log['event'],
'userId' => ID::custom($log['data']['userId']),
@@ -969,9 +992,7 @@ App::get('/v1/users/:userId/logs')
'deviceBrand' => $device['deviceBrand'],
'deviceModel' => $device['deviceModel']
]);
$record = $geodb->get($log['ip']);
if ($record) {
$output[$i]['countryCode'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), false) ? \strtolower($record['country']['iso_code']) : '--';
$output[$i]['countryName'] = $locale->getText('countries.' . strtolower($record['country']['iso_code']), $locale->getText('locale.country.unknown'));
@@ -1015,15 +1036,12 @@ App::get('/v1/users/:userId/targets')
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$queries[] = Query::equal('userId', [$userId]);
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
@@ -1031,20 +1049,16 @@ App::get('/v1/users/:userId/targets')
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$targetId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('targets', $targetId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Target '{$targetId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
try {
@@ -1092,7 +1106,6 @@ App::get('/v1/users/identities')
if (!empty($search)) {
$queries[] = Query::search('search', $search);
}
/**
* Get cursor document if there was a cursor query, we use array_filter and reset for reference $cursor to $queries
*/
@@ -1102,19 +1115,15 @@ App::get('/v1/users/identities')
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$identityId = $cursor->getValue();
$cursorDocument = $dbForProject->getDocument('identities', $identityId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "User '{$identityId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
@@ -1354,12 +1363,17 @@ App::patch('/v1/users/:userId/password')
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, true]);
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
// Create Argon2 hasher with default settings
$hasher = new Argon2();
$newPassword = $hasher->hash($password);
$hash = ProofsPassword::createHash($user->getAttribute('hash'), $user->getAttribute('hashOptions'));
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = $user->getAttribute('passwordHistory', []);
if ($historyLimit > 0) {
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
$validator = new PasswordHistory($history, $hash);
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED);
}
@@ -1372,8 +1386,8 @@ App::patch('/v1/users/:userId/password')
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
->setAttribute('hash', $hasher->getName())
->setAttribute('hashOptions', $hasher->getOptions());
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
@@ -2197,17 +2211,19 @@ App::post('/v1/users/:userId/sessions')
->inject('locale')
->inject('geodb')
->inject('queueForEvents')
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents) {
->inject('store')
->inject('proofForToken')
->action(function (string $userId, Request $request, Response $response, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Store $store, Token $proofForToken) {
$user = $dbForProject->getDocument('users', $userId);
if ($user->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_SESSION);
$secret = $proofForToken->generate();
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
$duration = $project->getAttribute('auths', [])['duration'] ?? TOKEN_EXPIRATION_LOGIN_LONG;
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$session = new Document(array_merge(
@@ -2215,8 +2231,8 @@ App::post('/v1/users/:userId/sessions')
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'provider' => Auth::SESSION_PROVIDER_SERVER,
'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak
'provider' => SESSION_PROVIDER_SERVER,
'secret' => $proofForToken->hash($secret), // One way hash encryption to protect DB leak
'userAgent' => $request->getUserAgent('UNKNOWN'),
'factors' => ['server'],
'ip' => $request->getIP(),
@@ -2240,8 +2256,13 @@ App::post('/v1/users/:userId/sessions')
$dbForProject->purgeCachedDocument('users', $user->getId());
$encoded = $store
->setProperty('id', $user->getId())
->setProperty('secret', $secret)
->encode();
$session
->setAttribute('secret', Auth::encodeSession($user->getId(), $secret))
->setAttribute('secret', $encoded)
->setAttribute('countryName', $countryName);
$queueForEvents
@@ -2276,7 +2297,7 @@ App::post('/v1/users/:userId/tokens')
))
->param('userId', '', new UID(), 'User ID.')
->param('length', 6, new Range(4, 128), 'Token length in characters. The default length is 6 characters', true)
->param('expire', Auth::TOKEN_EXPIRATION_GENERIC, new Range(60, Auth::TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->param('expire', TOKEN_EXPIRATION_GENERIC, new Range(60, TOKEN_EXPIRATION_LOGIN_LONG), 'Token expiration period in seconds. The default expiration is 15 minutes.', true)
->inject('request')
->inject('response')
->inject('dbForProject')
@@ -2288,15 +2309,17 @@ App::post('/v1/users/:userId/tokens')
throw new Exception(Exception::USER_NOT_FOUND);
}
$secret = Auth::tokenGenerator($length);
$proofForToken = new Token($length);
$proofForToken->setHash(new Sha());
$secret = $proofForToken->generate();
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $expire));
$token = new Document([
'$id' => ID::unique(),
'userId' => $user->getId(),
'userInternalId' => $user->getSequence(),
'type' => Auth::TOKEN_TYPE_GENERIC,
'secret' => Auth::hash($secret),
'type' => TOKEN_TYPE_GENERIC,
'secret' => $proofForToken->hash($secret),
'expire' => $expire,
'userAgent' => $request->getUserAgent('UNKNOWN'),
'ip' => $request->getIP()
@@ -2609,7 +2632,8 @@ App::post('/v1/users/:userId/jwts')
$session = \count($sessions) > 0 ? $sessions[\count($sessions) - 1] : new Document();
} else {
// Find by ID
foreach ($sessions as $loopSession) { /** @var Utopia\Database\Document $loopSession */
foreach ($sessions as $loopSession) {
/** @var Utopia\Database\Document $loopSession */
if ($loopSession->getId() == $sessionId) {
$session = $loopSession;
break;
+34 -18
View File
@@ -17,7 +17,9 @@ use Appwrite\Vcs\Comment;
use Swoole\Coroutine\WaitGroup;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Config\Adapters\Dotenv as ConfigDotenv;
use Utopia\Config\Config;
use Utopia\Config\Exceptions\Parse;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
@@ -29,7 +31,10 @@ use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Queries;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Database\Validator\Query\Limit;
use Utopia\Database\Validator\Query\Offset;
use Utopia\Detector\Detection\Framework\Analog;
use Utopia\Detector\Detection\Framework\Angular;
use Utopia\Detector\Detection\Framework\Astro;
@@ -978,14 +983,14 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
$contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/' . $file);
$envFile = $contentResponse['content'] ?? '';
$envLines = \explode("\n", $envFile);
foreach ($envLines as $line) {
$parts = \explode('=', $line, 2);
$envName = \trim($parts[0] ?? '');
$envValue = \trim($parts[1] ?? '');
if (!empty($envName)) {
$configAdapter = new ConfigDotenv();
try {
$envObject = $configAdapter->parse($envFile);
foreach ($envObject as $envName => $envValue) {
$envs[$envName] = $envValue;
}
} catch (Parse $err) {
// Silence error, so rest of endpoint can return
}
} finally {
$wg->done();
@@ -1031,10 +1036,11 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
->param('installationId', '', new Text(256), 'Installation Id')
->param('type', '', new WhiteList(['runtime', 'framework']), 'Detector type. Must be one of the following: runtime, framework')
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->param('queries', [], new Queries([new Limit(), new Offset()]), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Only supported methods are limit and offset', true)
->inject('gitHub')
->inject('response')
->inject('dbForPlatform')
->action(function (string $installationId, string $type, string $search, GitHub $github, Response $response, Database $dbForPlatform) {
->action(function (string $installationId, string $type, string $search, array $queries, GitHub $github, Response $response, Database $dbForPlatform) {
if (empty($search)) {
$search = "";
}
@@ -1050,11 +1056,20 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
$page = 1;
$perPage = 4;
$queries = Query::parseQueries($queries);
$limitQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_LIMIT));
$offsetQuery = current(array_filter($queries, fn ($query) => $query->getMethod() === Query::TYPE_OFFSET));
$limit = !empty($limitQuery) ? $limitQuery->getValue() : 4;
$offset = !empty($offsetQuery) ? $offsetQuery->getValue() : 0;
if ($offset % $limit !== 0) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'offset must be a multiple of the limit');
}
$page = ($offset / $limit) + 1;
$owner = $github->getOwnerName($providerInstallationId);
$repos = $github->searchRepositories($owner, $page, $perPage, $search);
['items' => $repos, 'total' => $total] = $github->searchRepositories($owner, $page, $limit, $search);
$repos = \array_map(function ($repo) use ($installation) {
$repo['id'] = \strval($repo['id'] ?? '');
@@ -1192,14 +1207,14 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
$contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], $file);
$envFile = $contentResponse['content'] ?? '';
$envLines = \explode("\n", $envFile);
foreach ($envLines as $line) {
$parts = \explode('=', $line, 2);
$envName = \trim($parts[0] ?? '');
$envValue = \trim($parts[1] ?? '');
if (!empty($envName)) {
$configAdapter = new ConfigDotenv();
try {
$envObject = $configAdapter->parse($envFile);
foreach ($envObject as $envName => $envValue) {
$envs[$envName] = $envValue;
}
} catch (Parse) {
// Silence error, so rest of endpoint can return
}
} finally {
$wg->done();
@@ -1226,7 +1241,7 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
$response->dynamic(new Document([
$type === 'framework' ? 'frameworkProviderRepositories' : 'runtimeProviderRepositories' => $repos,
'total' => \count($repos),
'total' => $total,
]), ($type === 'framework') ? Response::MODEL_PROVIDER_REPOSITORY_FRAMEWORK_LIST : Response::MODEL_PROVIDER_REPOSITORY_RUNTIME_LIST);
});
@@ -1783,7 +1798,8 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
}
$repository = $authorization->skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
$repository = $authorization->skip(fn () => $dbForPlatform->findOne('repositories', [
Query::equal('$id', [$repositoryId]),
Query::equal('projectInternalId', [$project->getSequence()])
]));
+4 -4
View File
@@ -4,7 +4,6 @@ require_once __DIR__ . '/../init.php';
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Key;
use Appwrite\Event\Certificate;
use Appwrite\Event\Event;
@@ -17,6 +16,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Transformation\Adapter\Preview;
use Appwrite\Transformation\Transformation;
use Appwrite\Utopia\Database\Documents\User as DBUser;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Request\Filters\V16 as RequestV16;
use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
@@ -223,7 +223,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
*/
$requirePreview = \is_null($apiKey) || !$apiKey->isPreviewAuthDisabled();
if ($isPreview && $requirePreview) {
$cookie = $request->getCookie(Auth::$cookieNamePreview, '');
$cookie = $request->getCookie(COOKIE_NAME_PREVIEW, '');
$authorized = false;
// Security checks to mark authorized true
@@ -1263,7 +1263,7 @@ App::error()
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
*/
if (!$publish && $project->getId() !== 'console') {
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
if (!DBUser::isPrivileged($authorization->getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
@@ -1623,7 +1623,7 @@ App::get('/_appwrite/authorize')
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$response
->addCookie(Auth::$cookieNamePreview, $jwt, (new \DateTime($expire))->getTimestamp(), '/', $host, ('https' === $protocol), true, null)
->addCookie(COOKIE_NAME_PREVIEW, $jwt, (new \DateTime($expire))->getTimestamp(), '/', $host, ('https' === $protocol), true, null)
->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
->addHeader('Pragma', 'no-cache')
->redirect($protocol . '://' . $host . $path);
+83 -27
View File
@@ -1,6 +1,5 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Auth\Key;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Event\Audit;
@@ -16,13 +15,13 @@ use Appwrite\Event\Webhook;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\SDK\Method;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Abuse\Abuse;
use Utopia\App;
use Utopia\Cache\Adapter\Filesystem;
use Utopia\Cache\Cache;
use Utopia\CLI\Console;
use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
@@ -35,7 +34,7 @@ use Utopia\System\System;
use Utopia\Telemetry\Adapter as Telemetry;
use Utopia\Validator\WhiteList;
$parseLabel = function (string $label, array $responsePayload, array $requestParams, Document $user) {
$parseLabel = function (string $label, array $responsePayload, array $requestParams, User $user) {
preg_match_all('/{(.*?)}/', $label, $matches);
foreach ($matches[1] ?? [] as $pos => $match) {
$find = $matches[0][$pos];
@@ -236,42 +235,95 @@ App::init()
->inject('authorization')
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
$route = $utopia->getRoute();
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted' && str_starts_with($route->getPath(), '/v1/backups')) {
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Database Backups are available on Appwrite Cloud');
}
/**
* Handle user authentication and session validation.
*
* This function follows a series of steps to determine the appropriate user session
* based on cookies, headers, and JWT tokens.
*
* Process:
*
* Project & Role Validation:
* 1. Check if the project is empty. If so, throw an exception.
* 2. Get the roles configuration.
* 3. Determine the role for the user based on the user document.
* 4. Get the scopes for the role.
*
* API Key Authentication:
* 5. If there is an API key:
* - Verify no user session exists simultaneously
* - Check if key is expired
* - Set role and scopes from API key
* - Handle special app role case
* - For standard keys, update last accessed time
*
* User Activity:
* 6. If the project is not the console and user is not admin:
* - Update user's last activity timestamp
*
* Access Control:
* 7. Get the method from the route
* 8. Validate namespace permissions
* 9. Validate scope permissions
* 10. Check if user is blocked
*
* Security Checks:
* 11. Verify password status (check if reset required)
* 12. Validate MFA requirements:
* - Check if MFA is enabled
* - Verify email status
* - Verify phone status
* - Verify authenticator status
* 13. Handle Multi-Factor Authentication:
* - Check remaining required factors
* - Validate factor completion
* - Throw exception if factors incomplete
*/
// Step 1: Check if project is empty
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
// Step 2: Get roles configuration
$roles = Config::getParam('roles', []);
// Step 3: Determine role for user
// TODO get scopes from the identity instead of the user roles config. The identity will containn the scopes the user authorized for the access token.
$role = $user->isEmpty()
? Role::guests()->toString()
: Role::users()->toString();
// Step 4: Get scopes for the role
$scopes = $roles[$role]['scopes'];
// API Key authentication
// Step 5: API Key Authentication
if (!empty($apiKey)) {
// Verify no user session exists simultaneously
if (!$user->isEmpty()) {
throw new Exception(Exception::USER_API_KEY_AND_SESSION_SET);
}
// Check if key is expired
if ($apiKey->isExpired()) {
throw new Exception(Exception::PROJECT_KEY_EXPIRED);
}
// Set role and scopes from API key
$role = $apiKey->getRole();
$scopes = $apiKey->getScopes();
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
// Handle special app role case
if ($apiKey->getRole() === User::ROLE_APPS) {
// Disable authorization checks for API keys
$authorization->setDefaultStatus(false);
$user = new Document([
$user = new User([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_APP,
'type' => ACTIVITY_TYPE_APP,
'email' => 'app.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => $apiKey->getName(),
@@ -280,6 +332,7 @@ App::init()
$queueForAudits->setUser($user);
}
// For standard keys, update last accessed time
if ($apiKey->getType() === API_KEY_STANDARD) {
$dbKey = $project->find(
key: 'secret',
@@ -345,11 +398,11 @@ App::init()
$scopes = \array_unique($scopes);
$authorization->addRole($role);
foreach (Auth::getRoles($user, $authorization) as $authRole) {
foreach ($user->getRoles($authorization) as $authRole) {
$authorization->addRole($authRole);
}
// Update project last activity
// Step 6: Update project and user last activity
if (!$project->isEmpty() && $project->getId() !== 'console') {
$accessedAt = $project->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
@@ -358,7 +411,6 @@ App::init()
}
}
// Update user last activity
if (!empty($user->getId())) {
$accessedAt = $user->getAttribute('accessedAt', 0);
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_USER_ACCESS)) > $accessedAt) {
@@ -372,6 +424,7 @@ App::init()
}
}
// Steps 7-9: Access Control - Method, Namespace and Scope Validation
/**
* @var ?Method $method
*/
@@ -389,27 +442,29 @@ App::init()
if (
array_key_exists($namespace, $project->getAttribute('services', []))
&& !$project->getAttribute('services', [])[$namespace]
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
}
}
// Do now allow access if scope is not allowed
// Step 9: Validate scope permissions
$allowed = (array)$route->getLabel('scope', 'none');
if (empty(\array_intersect($allowed, $scopes))) {
throw new Exception(Exception::GENERAL_UNAUTHORIZED_SCOPE, $user->getAttribute('email', 'User') . ' (role: ' . \strtolower($roles[$role]['label']) . ') missing scopes (' . \json_encode($allowed) . ')');
}
// Do not allow access to blocked accounts
// Step 10: Check if user is blocked
if (false === $user->getAttribute('status')) { // Account is blocked
throw new Exception(Exception::USER_BLOCKED);
}
// Step 11: Verify password status
if ($user->getAttribute('reset')) {
throw new Exception(Exception::USER_PASSWORD_RESET_REQUIRED);
}
// Step 12: Validate MFA requirements
$mfaEnabled = $user->getAttribute('mfa', false);
$hasVerifiedEmail = $user->getAttribute('emailVerification', false);
$hasVerifiedPhone = $user->getAttribute('phoneVerification', false);
@@ -417,6 +472,7 @@ App::init()
$hasMoreFactors = $hasVerifiedEmail || $hasVerifiedPhone || $hasVerifiedAuthenticator;
$minimumFactors = ($mfaEnabled && $hasMoreFactors) ? 2 : 1;
// Step 13: Handle Multi-Factor Authentication
if (!in_array('mfa', $route->getGroups())) {
if ($session && \count($session->getAttribute('factors', [])) < $minimumFactors) {
throw new Exception(Exception::USER_MORE_FACTORS_REQUIRED);
@@ -457,7 +513,7 @@ App::init()
if (
array_key_exists('rest', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['rest']
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
@@ -488,8 +544,8 @@ App::init()
$closestLimit = null;
$roles = $authorization->getRoles();
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
$isAppUser = Auth::isAppUser($roles);
$isPrivilegedUser = User::isPrivileged($roles);
$isAppUser = User::isApp($roles);
foreach ($timeLimitArray as $timeLimit) {
foreach ($request->getParams() as $key => $value) { // Set request params as potential abuse keys
@@ -544,7 +600,7 @@ App::init()
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
}
@@ -587,7 +643,7 @@ App::init()
if ($useCache) {
$route = $utopia->match($request);
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser($authorization->getRoles());
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !User::isPrivileged($authorization->getRoles());
$key = $request->cacheIdentifier();
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
@@ -610,7 +666,7 @@ App::init()
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
@@ -643,7 +699,7 @@ App::init()
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
//Do not update transformedAt if it's a console user
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
if (!User::isPrivileged($authorization->getRoles())) {
$transformedAt = $file->getAttribute('transformedAt', '');
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
$file->setAttribute('transformedAt', DateTime::now());
@@ -792,7 +848,7 @@ App::shutdown()
if (!$user->isEmpty()) {
$userClone = clone $user;
// $user doesn't support `type` and can cause unintended effects.
$userClone->setAttribute('type', Auth::ACTIVITY_TYPE_USER);
$userClone->setAttribute('type', ACTIVITY_TYPE_USER);
$queueForAudits->setUser($userClone);
} elseif ($queueForAudits->getUser() === null || $queueForAudits->getUser()->isEmpty()) {
/**
@@ -803,10 +859,10 @@ App::shutdown()
*
* Therefore, we consider this an anonymous request and create a relevant user.
*/
$user = new Document([
$user = new User([
'$id' => '',
'status' => true,
'type' => Auth::ACTIVITY_TYPE_GUEST,
'type' => ACTIVITY_TYPE_GUEST,
'email' => 'guest.' . $project->getId() . '@service.' . $request->getHostname(),
'password' => '',
'name' => 'Guest',
@@ -896,7 +952,7 @@ App::shutdown()
}
if ($project->getId() !== 'console') {
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
if (!User::isPrivileged($authorization->getRoles())) {
$fileSize = 0;
$file = $request->getFiles('file');
if (!empty($file)) {
+4 -4
View File
@@ -1,7 +1,7 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use MaxMind\Db\Reader;
use Utopia\App;
@@ -20,7 +20,7 @@ App::init()
$lastUpdate = $session->getAttribute('mfaUpdatedAt');
if (!empty($lastUpdate)) {
$now = DateTime::now();
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), Auth::MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
$maxAllowedDate = DateTime::addSeconds(new \DateTime($lastUpdate), MFA_RECENT_DURATION); // Maximum date until session is considered safe before asking for another challenge
$isSessionFresh = DateTime::formatTz($maxAllowedDate) >= DateTime::formatTz($now);
}
@@ -50,8 +50,8 @@ App::init()
$route = $utopia->match($request);
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
$isAppUser = Auth::isAppUser($authorization->getRoles());
$isPrivilegedUser = User::isPrivileged($authorization->getRoles());
$isAppUser = User::isApp($authorization->getRoles());
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
return;
+39 -36
View File
@@ -1,42 +1,45 @@
<?php
use Utopia\Config\Adapters\PHP;
use Utopia\Config\Config;
require_once __DIR__ . '/../config/storage/resource_limits.php';
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php');
Config::load('events', __DIR__ . '/../config/events.php');
Config::load('auth', __DIR__ . '/../config/auth.php');
Config::load('apis', __DIR__ . '/../config/apis.php'); // List of APIs
Config::load('errors', __DIR__ . '/../config/errors.php');
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php');
Config::load('platforms', __DIR__ . '/../config/platforms.php');
Config::load('console', __DIR__ . '/../config/console.php');
Config::load('collections', __DIR__ . '/../config/collections.php');
Config::load('frameworks', __DIR__ . '/../config/frameworks.php');
Config::load('runtimes', __DIR__ . '/../config/runtimes.php');
Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php');
Config::load('usage', __DIR__ . '/../config/usage.php');
Config::load('roles', __DIR__ . '/../config/roles.php'); // User roles and scopes
Config::load('scopes', __DIR__ . '/../config/scopes.php'); // User roles and scopes
Config::load('services', __DIR__ . '/../config/services.php'); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php'); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php'); // List of available regions
Config::load('avatar-browsers', __DIR__ . '/../config/avatars/browsers.php');
Config::load('avatar-credit-cards', __DIR__ . '/../config/avatars/credit-cards.php');
Config::load('avatar-flags', __DIR__ . '/../config/avatars/flags.php');
Config::load('locale-codes', __DIR__ . '/../config/locale/codes.php');
Config::load('locale-currencies', __DIR__ . '/../config/locale/currencies.php');
Config::load('locale-eu', __DIR__ . '/../config/locale/eu.php');
Config::load('locale-languages', __DIR__ . '/../config/locale/languages.php');
Config::load('locale-phones', __DIR__ . '/../config/locale/phones.php');
Config::load('locale-countries', __DIR__ . '/../config/locale/countries.php');
Config::load('locale-continents', __DIR__ . '/../config/locale/continents.php');
Config::load('locale-templates', __DIR__ . '/../config/locale/templates.php');
Config::load('storage-logos', __DIR__ . '/../config/storage/logos.php');
Config::load('storage-mimes', __DIR__ . '/../config/storage/mimes.php');
Config::load('storage-inputs', __DIR__ . '/../config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/../config/storage/outputs.php');
Config::load('specifications', __DIR__ . '/../config/specifications.php');
Config::load('templates-function', __DIR__ . '/../config/templates/function.php');
Config::load('templates-site', __DIR__ . '/../config/templates/site.php');
$configAdapter = new PHP();
Config::load('runtimes', __DIR__ . '/../config/runtimes.php', $configAdapter);
Config::load('runtimes-v2', __DIR__ . '/../config/runtimes-v2.php', $configAdapter);
Config::load('template-runtimes', __DIR__ . '/../config/template-runtimes.php', $configAdapter);
Config::load('events', __DIR__ . '/../config/events.php', $configAdapter);
Config::load('auth', __DIR__ . '/../config/auth.php', $configAdapter);
Config::load('apis', __DIR__ . '/../config/apis.php', $configAdapter); // List of APIs
Config::load('errors', __DIR__ . '/../config/errors.php', $configAdapter);
Config::load('oAuthProviders', __DIR__ . '/../config/oAuthProviders.php', $configAdapter);
Config::load('platforms', __DIR__ . '/../config/platforms.php', $configAdapter);
Config::load('console', __DIR__ . '/../config/console.php', $configAdapter);
Config::load('collections', __DIR__ . '/../config/collections.php', $configAdapter);
Config::load('frameworks', __DIR__ . '/../config/frameworks.php', $configAdapter);
Config::load('usage', __DIR__ . '/../config/usage.php', $configAdapter);
Config::load('roles', __DIR__ . '/../config/roles.php', $configAdapter); // User roles and scopes
Config::load('scopes', __DIR__ . '/../config/scopes.php', $configAdapter); // User roles and scopes
Config::load('services', __DIR__ . '/../config/services.php', $configAdapter); // List of services
Config::load('variables', __DIR__ . '/../config/variables.php', $configAdapter); // List of env variables
Config::load('regions', __DIR__ . '/../config/regions.php', $configAdapter); // List of available regions
Config::load('avatar-browsers', __DIR__ . '/../config/avatars/browsers.php', $configAdapter);
Config::load('avatar-credit-cards', __DIR__ . '/../config/avatars/credit-cards.php', $configAdapter);
Config::load('avatar-flags', __DIR__ . '/../config/avatars/flags.php', $configAdapter);
Config::load('locale-codes', __DIR__ . '/../config/locale/codes.php', $configAdapter);
Config::load('locale-currencies', __DIR__ . '/../config/locale/currencies.php', $configAdapter);
Config::load('locale-eu', __DIR__ . '/../config/locale/eu.php', $configAdapter);
Config::load('locale-languages', __DIR__ . '/../config/locale/languages.php', $configAdapter);
Config::load('locale-phones', __DIR__ . '/../config/locale/phones.php', $configAdapter);
Config::load('locale-countries', __DIR__ . '/../config/locale/countries.php', $configAdapter);
Config::load('locale-continents', __DIR__ . '/../config/locale/continents.php', $configAdapter);
Config::load('locale-templates', __DIR__ . '/../config/locale/templates.php', $configAdapter);
Config::load('storage-logos', __DIR__ . '/../config/storage/logos.php', $configAdapter);
Config::load('storage-mimes', __DIR__ . '/../config/storage/mimes.php', $configAdapter);
Config::load('storage-inputs', __DIR__ . '/../config/storage/inputs.php', $configAdapter);
Config::load('storage-outputs', __DIR__ . '/../config/storage/outputs.php', $configAdapter);
Config::load('specifications', __DIR__ . '/../config/specifications.php', $configAdapter);
Config::load('templates-function', __DIR__ . '/../config/templates/function.php', $configAdapter);
Config::load('templates-site', __DIR__ . '/../config/templates/site.php', $configAdapter);
+66
View File
@@ -93,6 +93,69 @@ const APP_VCS_GITHUB_EMAIL = 'team@appwrite.io';
const APP_VCS_GITHUB_URL = 'https://github.com/TeamAppwrite';
const APP_BRANDED_EMAIL_BASE_TEMPLATE = 'email-base-styled';
/**
* JWT for Resource Tokens.
*/
const RESOURCE_TOKEN_ALGORITHM = 'HS256';
const RESOURCE_TOKEN_MAX_AGE = 86400 * 365 * 10; /* 10 years */
const RESOURCE_TOKEN_LEEWAY = 10; // 10 seconds
/**
* Token Expiration times.
*/
const TOKEN_EXPIRATION_LOGIN_LONG = 31536000; /* 1 year */
const TOKEN_EXPIRATION_LOGIN_SHORT = 3600; /* 1 hour */
const TOKEN_EXPIRATION_RECOVERY = 3600; /* 1 hour */
const TOKEN_EXPIRATION_CONFIRM = 3600 * 1; /* 1 hour */
const TOKEN_EXPIRATION_OTP = 60 * 15; /* 15 minutes */
const TOKEN_EXPIRATION_GENERIC = 60 * 15; /* 15 minutes */
/**
* Token Lengths.
*/
const TOKEN_LENGTH_MAGIC_URL = 64;
const TOKEN_LENGTH_VERIFICATION = 256;
const TOKEN_LENGTH_RECOVERY = 256;
const TOKEN_LENGTH_OAUTH2 = 64;
const TOKEN_LENGTH_SESSION = 256;
/**
* Token Types.
*/
const TOKEN_TYPE_LOGIN = 1; // Deprecated
const TOKEN_TYPE_VERIFICATION = 2;
const TOKEN_TYPE_RECOVERY = 3;
const TOKEN_TYPE_INVITE = 4;
const TOKEN_TYPE_MAGIC_URL = 5;
const TOKEN_TYPE_PHONE = 6;
const TOKEN_TYPE_OAUTH2 = 7;
const TOKEN_TYPE_GENERIC = 8;
const TOKEN_TYPE_EMAIL = 9; // OTP
/**
* Session Providers.
*/
const SESSION_PROVIDER_EMAIL = 'email';
const SESSION_PROVIDER_ANONYMOUS = 'anonymous';
const SESSION_PROVIDER_MAGIC_URL = 'magic-url';
const SESSION_PROVIDER_PHONE = 'phone';
const SESSION_PROVIDER_OAUTH2 = 'oauth2';
const SESSION_PROVIDER_TOKEN = 'token';
const SESSION_PROVIDER_SERVER = 'server';
/**
* Activity associated with user or the app.
*/
const ACTIVITY_TYPE_APP = 'app';
const ACTIVITY_TYPE_USER = 'user';
const ACTIVITY_TYPE_GUEST = 'guest';
/**
* MFA
*/
const MFA_RECENT_DURATION = 1800; // 30 mins
// Database Reconnect
const DATABASE_RECONNECT_SLEEP = 2;
const DATABASE_RECONNECT_MAX_ATTEMPTS = 10;
@@ -297,3 +360,6 @@ const TOKENS_RESOURCE_TYPE_DATABASES = 'databases';
const SCHEDULE_RESOURCE_TYPE_EXECUTION = 'execution';
const SCHEDULE_RESOURCE_TYPE_FUNCTION = 'function';
const SCHEDULE_RESOURCE_TYPE_MESSAGE = 'message';
/** Preview cookie */
const COOKIE_NAME_PREVIEW = 'a_jwt_console';
+16 -16
View File
@@ -69,11 +69,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
$attributes = $database->find('attributes', [
$attributes = $database->getAuthorization()->skip(fn () => $database->find('attributes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForAttributes()),
]);
]));
foreach ($attributes as $attribute) {
$attributeType = $attribute->getAttribute('type');
@@ -104,12 +104,12 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('indexes', [
Query::equal('collectionInternalId', [$document->getSequence()]),
Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
Query::limit($database->getLimitForIndexes()),
]);
]));
}
);
@@ -119,11 +119,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('platforms', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -133,11 +133,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('keys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -147,11 +147,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('devKeys', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -161,11 +161,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('webhooks', [
Query::equal('projectInternalId', [$document->getSequence()]),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -250,14 +250,14 @@ Database::addFilter(
default => ['function', 'site']
};
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('variables', [
Query::equal('resourceInternalId', [$document->getSequence()]),
Query::equal('resourceType', $resourceType),
Query::orderAsc('resourceType'),
Query::orderAsc(),
Query::limit(APP_LIMIT_SUBQUERY),
]);
]));
}
);
@@ -293,11 +293,11 @@ Database::addFilter(
return;
},
function (mixed $value, Document $document, Database $database) {
return $database
return $database->getAuthorization()->skip(fn () => $database
->find('variables', [
Query::equal('resourceType', ['project']),
Query::limit(APP_LIMIT_SUBQUERY)
]);
]));
}
);
+47 -1
View File
@@ -39,6 +39,7 @@ if (!App::isProduction()) {
PublicDomain::allow(['request-catcher-sms']);
PublicDomain::allow(['request-catcher-webhook']);
}
$register->set('logger', function () {
// Register error logger
$providerName = System::getEnv('_APP_LOGGING_PROVIDER', '');
@@ -97,6 +98,51 @@ $register->set('logger', function () {
return new Logger($adapter);
});
$register->set('realtimeLogger', function () {
// Register error logger for realtime, falls back to default logging config
$providerConfig = System::getEnv('_APP_LOGGING_CONFIG_REALTIME', '')
?: System::getEnv('_APP_LOGGING_CONFIG', '');
if (empty($providerConfig)) {
return;
}
$loggingProvider = new DSN($providerConfig);
$providerName = $loggingProvider->getScheme();
$providerConfig = match ($providerName) {
'sentry' => ['key' => $loggingProvider->getPassword(), 'projectId' => $loggingProvider->getUser() ?? '', 'host' => 'https://' . $loggingProvider->getHost()],
'logowl' => ['ticket' => $loggingProvider->getUser() ?? '', 'host' => $loggingProvider->getHost()],
default => ['key' => $loggingProvider->getHost()],
};
if (empty($providerName) || empty($providerConfig)) {
return;
}
if (!Logger::hasProvider($providerName)) {
throw new Exception(Exception::GENERAL_SERVER_ERROR, "Logging provider not supported. Logging is disabled");
}
try {
$adapter = match ($providerName) {
'sentry' => new Sentry($providerConfig['projectId'], $providerConfig['key'], $providerConfig['host']),
'logowl' => new LogOwl($providerConfig['ticket'], $providerConfig['host']),
'raygun' => new Raygun($providerConfig['key']),
'appsignal' => new AppSignal($providerConfig['key']),
default => null
};
} catch (Throwable $th) {
$adapter = null;
}
if ($adapter === null) {
Console::error("Logging provider not supported. Logging is disabled");
return;
}
return new Logger($adapter);
});
$register->set('pools', function () {
$group = new Group();
@@ -326,7 +372,7 @@ $register->set('smtp', function () {
return $mail;
});
$register->set('geodb', function () {
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2024-09.mmdb');
return new Reader(__DIR__ . '/../assets/dbip/dbip-country-lite-2025-12.mmdb');
});
$register->set('passwordsDictionary', function () {
$content = \file_get_contents(__DIR__ . '/../assets/security/10k-common-passwords');
+101 -50
View File
@@ -2,7 +2,6 @@
use Ahc\Jwt\JWT;
use Ahc\Jwt\JWTException;
use Appwrite\Auth\Auth;
use Appwrite\Auth\Key;
use Appwrite\Databases\TransactionState;
use Appwrite\Event\Audit;
@@ -23,10 +22,18 @@ use Appwrite\Extend\Exception;
use Appwrite\GraphQL\Schema;
use Appwrite\Network\Platform;
use Appwrite\Network\Validator\Origin;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Executor\Executor;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Argon2;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Code;
use Utopia\Auth\Proofs\Password;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -226,77 +233,91 @@ App::setResource('platforms', function (Request $request, Document $console, Doc
];
}, ['request', 'console', 'project', 'dbForPlatform', 'authorization']);
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, $authorization) {
/** @var Appwrite\Utopia\Request $request */
/** @var Appwrite\Utopia\Response $response */
/** @var Utopia\Database\Document $project */
/** @var Utopia\Database\Database $dbForProject */
/** @var Utopia\Database\Database $dbForPlatform */
/** @var Utopia\Database\Authorization $authorization */
/** @var string $mode */
App::setResource('user', function (string $mode, Document $project, Document $console, Request $request, Response $response, Database $dbForProject, Database $dbForPlatform, Store $store, Token $proofForToken, $authorization) {
/**
* Handles user authentication and session validation.
*
* This function follows a series of steps to determine the appropriate user session
* based on cookies, headers, and JWT tokens.
*
* Process:
* 1. Checks the cookie based on mode:
* - If in admin mode, uses console project id for key.
* - Otherwise, sets the key using the project ID
* 2. If no cookie is found, attempts to retrieve the fallback header `x-fallback-cookies`.
* - If this method is used, returns the header: `X-Debug-Fallback: true`.
* 3. Fetches the user document from the appropriate database based on the mode.
* 4. If the user document is empty or the session key cannot be verified, sets an empty user document.
* 5. Regardless of the results from steps 1-4, attempts to fetch the JWT token.
* 6. If the JWT user has a valid session ID, updates the user variable with the user from `projectDB`,
* overwriting the previous value.
*/
$authorization->setDefaultStatus(true);
Auth::setCookieName('a_session_' . $project->getId());
$store->setKey('a_session_' . $project->getId());
if (APP_MODE_ADMIN === $mode) {
Auth::setCookieName('a_session_' . $console->getId());
$store->setKey('a_session_' . $console->getId());
}
$session = Auth::decodeSession(
$store->decode(
$request->getCookie(
Auth::$cookieName, // Get sessions
$request->getCookie(Auth::$cookieName . '_legacy', '')
$store->getKey(), // Get sessions
$request->getCookie($store->getKey() . '_legacy', '')
)
);
// Get session from header for SSR clients
if (empty($session['id']) && empty($session['secret'])) {
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
$sessionHeader = $request->getHeader('x-appwrite-session', '');
if (!empty($sessionHeader)) {
$session = Auth::decodeSession($sessionHeader);
$store->decode($sessionHeader);
}
}
// Get fallback session from old clients (no SameSite support) or clients who block 3rd-party cookies
if ($response) {
if ($response) { // if in http context - add debug header
$response->addHeader('X-Debug-Fallback', 'false');
}
if (empty($session['id']) && empty($session['secret'])) {
if (empty($store->getProperty('id', '')) && empty($store->getProperty('secret', ''))) {
if ($response) {
$response->addHeader('X-Debug-Fallback', 'true');
}
$fallback = $request->getHeader('x-fallback-cookies', '');
$fallback = \json_decode($fallback, true);
$session = Auth::decodeSession(((isset($fallback[Auth::$cookieName])) ? $fallback[Auth::$cookieName] : ''));
$store->decode(((is_array($fallback) && isset($fallback[$store->getKey()])) ? $fallback[$store->getKey()] : ''));
}
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
$user = new Document([]);
if (!empty(Auth::$unique)) {
if ($mode === APP_MODE_ADMIN) {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} elseif (!$project->isEmpty()) {
if ($project->getId() === 'console') {
$user = $dbForPlatform->getDocument('users', Auth::$unique);
} else {
$user = $dbForProject->getDocument('users', Auth::$unique);
$user = null;
if (APP_MODE_ADMIN === $mode) {
/** @var User $user */
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
if ($project->isEmpty()) {
$user = new User([]);
} else {
if (!empty($store->getProperty('id', ''))) {
if ($project->getId() === 'console') {
/** @var User $user */
$user = $dbForPlatform->getDocument('users', $store->getProperty('id', ''));
} else {
/** @var User $user */
$user = $dbForProject->getDocument('users', $store->getProperty('id', ''));
}
}
}
}
if (
!$user ||
$user->isEmpty() // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret)
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken)
) { // Validate user has valid login token
$user = new Document([]);
$user = new User([]);
}
// if (APP_MODE_ADMIN === $mode) {
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
// $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
@@ -304,18 +325,14 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
// $user = new Document([]);
// }
// }
$authJWT = $request->getHeader('x-appwrite-jwt', '');
if (!empty($authJWT) && !$project->isEmpty()) { // JWT authentication
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
try {
$payload = $jwt->decode($authJWT);
} catch (JWTException $error) {
throw new Exception(Exception::USER_JWT_INVALID, 'Failed to verify JWT. ' . $error->getMessage());
}
$jwtUserId = $payload['userId'] ?? '';
if (!empty($jwtUserId)) {
if ($mode === APP_MODE_ADMIN) {
@@ -324,20 +341,18 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
$user = $dbForProject->getDocument('users', $jwtUserId);
}
}
$jwtSessionId = $payload['sessionId'] ?? '';
if (!empty($jwtSessionId)) {
if (empty($user->find('$id', $jwtSessionId, 'sessions'))) { // Match JWT to active token
$user = new Document([]);
$user = new User([]);
}
}
}
$dbForProject->setMetadata('user', $user->getId());
$dbForPlatform->setMetadata('user', $user->getId());
return $user;
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'authorization']);
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'store', 'proofForToken', 'authorization']);
App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
/** @var Appwrite\Utopia\Request $request */
@@ -355,26 +370,56 @@ App::setResource('project', function ($dbForPlatform, $request, $console, $autho
return $project;
}, ['dbForPlatform', 'request', 'console', 'authorization']);
App::setResource('session', function (Document $user) {
App::setResource('session', function (User $user, Store $store, Token $proofForToken) {
if ($user->isEmpty()) {
return;
}
$sessions = $user->getAttribute('sessions', []);
$sessionId = Auth::sessionVerify($user->getAttribute('sessions'), Auth::$secret);
$sessionId = $user->sessionVerify($store->getProperty('secret', ''), $proofForToken);
if (!$sessionId) {
return;
}
foreach ($sessions as $session) {/** @var Document $session */
foreach ($sessions as $session) {
/** @var Document $session */
if ($sessionId === $session->getId()) {
return $session;
}
}
return;
}, ['user']);
}, ['user', 'store', 'proofForToken']);
App::setResource('store', function (): Store {
return new Store();
});
App::setResource('proofForPassword', function (): Password {
$hash = new Argon2();
$hash
->setMemoryCost(7168)
->setTimeCost(5)
->setThreads(1);
$password = new Password();
$password
->setHash($hash);
return $password;
});
App::setResource('proofForToken', function (): Token {
$token = new Token();
$token->setHash(new Sha());
return $token;
});
App::setResource('proofForCode', function (): Code {
$code = new Code();
$code->setHash(new Sha());
return $code;
});
App::setResource('console', function () {
return new Document(Config::getParam('console'));
@@ -405,6 +450,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->setDocumentType('users', User::class);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -436,6 +482,8 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authoriz
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
$database->setDocumentType('users', User::class);
return $database;
}, ['pools', 'cache', 'authorization']);
@@ -460,7 +508,9 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId())
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES)
->setDocumentType('users', User::class)
;
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
@@ -963,7 +1013,8 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request, A
$tokenJWT = $request->getParam('token');
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 900, 10); // Instantiate with key, algo, maxAge and leeway.
// Use a large but reasonable maxAge to avoid auto-exp when token has no expiry
$jwt = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), RESOURCE_TOKEN_ALGORITHM, RESOURCE_TOKEN_MAX_AGE, RESOURCE_TOKEN_LEEWAY); // Instantiate with key, algo, maxAge and leeway.
try {
$payload = $jwt->decode($tokenJWT);
+92 -51
View File
@@ -1,13 +1,14 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Extend\Exception;
use Appwrite\Extend\Exception as AppwriteException;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\Network\Validator\Origin;
use Appwrite\PubSub\Adapter\Pool as PubSubPool;
use Appwrite\Utopia\Database\Documents\User;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Swoole\Coroutine;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Runtime;
@@ -16,6 +17,9 @@ use Swoole\Timer;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
use Utopia\App;
use Utopia\Auth\Hashes\Sha;
use Utopia\Auth\Proofs\Token;
use Utopia\Auth\Store;
use Utopia\Cache\Adapter\Pool as CachePool;
use Utopia\Cache\Adapter\Sharding;
use Utopia\Cache\Cache;
@@ -48,14 +52,14 @@ Runtime::enableCoroutine(SWOOLE_HOOK_ALL);
if (!function_exists('getConsoleDB')) {
function getConsoleDB(): Database
{
global $register;
$ctx = Coroutine::getContext();
static $database = null;
if ($database !== null) {
return $database;
if (isset($ctx['dbForPlatform'])) {
return $ctx['dbForPlatform'];
}
global $register;
/** @var Group $pools */
$pools = $register->get('pools');
@@ -65,8 +69,8 @@ if (!function_exists('getConsoleDB')) {
->setNamespace('_console')
->setMetadata('host', \gethostname())
->setMetadata('project', '_console');
return $database;
$database->setDocumentType('users', User::class);
return $ctx['dbForPlatform'] = $database;
}
}
@@ -74,14 +78,18 @@ if (!function_exists('getConsoleDB')) {
if (!function_exists('getProjectDB')) {
function getProjectDB(Document $project): Database
{
global $register;
$ctx = Coroutine::getContext();
static $databases = [];
if (isset($databases[$project->getSequence()])) {
return $databases[$project->getSequence()];
if (!isset($ctx['dbForProject'])) {
$ctx['dbForProject'] = [];
}
if (isset($ctx['dbForProject'][$project->getSequence()])) {
return $ctx['dbForProject'][$project->getSequence()];
}
global $register;
/** @var Group $pools */
$pools = $register->get('pools');
@@ -117,7 +125,9 @@ if (!function_exists('getProjectDB')) {
->setMetadata('host', \gethostname())
->setMetadata('project', $project->getId());
return $databases[$project->getSequence()] = $database;
$database->setDocumentType('users', User::class);
return $ctx['dbForProject'][$project->getSequence()] = $database;
}
}
@@ -125,14 +135,14 @@ if (!function_exists('getProjectDB')) {
if (!function_exists('getCache')) {
function getCache(): Cache
{
global $register;
$ctx = Coroutine::getContext();
static $cache = null;
if ($cache !== null) {
return $cache;
if (isset($ctx['cache'])) {
return $ctx['cache'];
}
global $register;
$pools = $register->get('pools'); /** @var Group $pools */
$list = Config::getParam('pools-cache', []);
@@ -142,7 +152,7 @@ if (!function_exists('getCache')) {
$adapters[] = new CachePool($pools->get($value));
}
return $cache = new Cache(new Sharding($adapters));
return $ctx['cache'] = new Cache(new Sharding($adapters));
}
}
@@ -150,10 +160,10 @@ if (!function_exists('getCache')) {
if (!function_exists('getRedis')) {
function getRedis(): \Redis
{
static $redis = null;
$ctx = Coroutine::getContext();
if ($redis !== null) {
return $redis;
if (isset($ctx['redis'])) {
return $ctx['redis'];
}
$host = System::getEnv('_APP_REDIS_HOST', 'localhost');
@@ -167,46 +177,46 @@ if (!function_exists('getRedis')) {
}
$redis->setOption(\Redis::OPT_READ_TIMEOUT, -1);
return $redis;
return $ctx['redis'] = $redis;
}
}
if (!function_exists('getTimelimit')) {
function getTimelimit(): TimeLimitRedis
function getTimelimit(string $key = "", int $limit = 0, int $seconds = 1): TimeLimitRedis
{
static $timelimit = null;
$ctx = Coroutine::getContext();
if ($timelimit !== null) {
return $timelimit;
if (isset($ctx['timelimit'])) {
return $ctx['timelimit'];
}
return $timelimit = new TimeLimitRedis("", 0, 1, getRedis());
return $ctx['timelimit'] = new TimeLimitRedis($key, $limit, $seconds, getRedis());
}
}
if (!function_exists('getRealtime')) {
function getRealtime(): Realtime
{
static $realtime = null;
$ctx = Coroutine::getContext();
if ($realtime !== null) {
return $realtime;
if (isset($ctx['realtime'])) {
return $ctx['realtime'];
}
return $realtime = new Realtime();
return $ctx['realtime'] = new Realtime();
}
}
if (!function_exists('getTelemetry')) {
function getTelemetry(int $workerId): Utopia\Telemetry\Adapter
{
static $telemetry = null;
$ctx = Coroutine::getContext();
if ($telemetry !== null) {
return $telemetry;
if (isset($ctx['telemetry'])) {
return $ctx['telemetry'];
}
return $telemetry = new NoTelemetry();
return $ctx['telemetry'] = new NoTelemetry();
}
}
@@ -235,7 +245,7 @@ $adapter
$server = new Server($adapter);
$logError = function (Throwable $error, string $action) use ($register) {
$logger = $register->get('logger');
$logger = $register->get('realtimeLogger');
if ($logger && !$error instanceof Exception) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
@@ -456,8 +466,10 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
$database = getProjectDB($project);
/** @var Appwrite\Utopia\Database\Documents\User $user */
$user = $database->getDocument('users', $userId);
$roles = Auth::getRoles($user, $database->getAuthorization());
$roles = $user->getRoles($database->getAuthorization());
$channels = $realtime->connections[$connection]['channels'];
$realtime->unsubscribe($connection);
@@ -525,14 +537,14 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
if (
array_key_exists('realtime', $project->getAttribute('apis', []))
&& !$project->getAttribute('apis', [])['realtime']
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
&& !(User::isPrivileged($authorization->getRoles()) || User::isApp($authorization->getRoles()))
) {
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
}
$timelimit = $app->getResource('timelimit');
$platforms = $app->getResource('platforms');
$user = $app->getResource('user'); /** @var Document $user */
$user = $app->getResource('user'); /** @var User $user */
/*
* Abuse Check
@@ -562,7 +574,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
}
$roles = Auth::getRoles($user, $authorization);
$roles = $user->getRoles($authorization);
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
@@ -575,6 +587,8 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$realtime->subscribe($project->getId(), $connection, $roles, $channels);
$realtime->connections[$connection]['authorization'] = $authorization;
$user = empty($user->getId()) ? null : $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
@@ -603,6 +617,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$code = 500;
}
$message = $th->getMessage();
// sanitize 0 && 5xx errors
@@ -632,12 +647,19 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
$server->onMessage(function (int $connection, string $message) use ($server, $register, $realtime, $containerId) {
try {
$response = new Response(new SwooleResponse());
$projectId = $realtime->connections[$connection]['projectId'];
$projectId = $realtime->connections[$connection]['projectId'] ?? null;
// Get authorization from connection (stored during onOpen)
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$database = getConsoleDB();
$database->setAuthorization($authorization);
if ($projectId !== 'console') {
$project = $database->getAuthorization()->skip(fn () => $database->getDocument('projects', $projectId));
$database = getProjectDB($project, $database->getAuthorization());
$project = $authorization->skip(fn () => $database->getDocument('projects', $projectId));
$database = getProjectDB($project);
$database->setAuthorization($authorization);
} else {
$project = null;
}
@@ -677,24 +699,43 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Payload is not valid.');
}
$session = Auth::decodeSession($message['data']['session']);
Auth::$unique = $session['id'] ?? '';
Auth::$secret = $session['secret'] ?? '';
$store = new Store();
$user = $database->getDocument('users', Auth::$unique);
$store->decode($message['data']['session']);
/** @var User $user */
$user = $database->getDocument('users', $store->getProperty('id', ''));
/**
* TODO:
* Moving forward, we should try to use our dependency injection container
* to inject the proof for token.
* This way we will have one source of truth for the proof for token.
*/
$proofForToken = new Token();
$proofForToken->setHash(new Sha());
if (
empty($user->getId()) // Check a document has been found in the DB
|| !Auth::sessionVerify($user->getAttribute('sessions', []), Auth::$secret) // Validate user has valid login token
|| !$user->sessionVerify($store->getProperty('secret', ''), $proofForToken) // Validate user has valid login token
) {
// cookie not valid
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
}
$roles = Auth::getRoles($user, $database->getAuthorization());
$roles = $user->getRoles($database->getAuthorization());
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
// Preserve authorization before subscribe overwrites the connection array
$authorization = $realtime->connections[$connection]['authorization'] ?? null;
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
// Restore authorization after subscribe
if ($authorization !== null) {
$realtime->connections[$connection]['authorization'] = $authorization;
}
$user = $response->output($user, Response::MODEL_ACCOUNT);
$server->send([$connection], json_encode([
'type' => 'response',
+3 -3
View File
@@ -849,7 +849,7 @@ $image = $this->getParam('image', '');
- _APP_DB_PASS
appwrite-assistant:
image: appwrite/assistant:0.8.3
image: appwrite/assistant:0.8.4
container_name: appwrite-assistant
<<: *x-logging
restart: unless-stopped
@@ -857,9 +857,9 @@ $image = $this->getParam('image', '');
- appwrite
environment:
- _APP_ASSISTANT_OPENAI_API_KEY
appwrite-browser:
image: appwrite/browser:0.3.1
image: appwrite/browser:0.3.2
container_name: appwrite-browser
<<: *x-logging
restart: unless-stopped
+5 -1
View File
@@ -17,6 +17,7 @@ use Appwrite\Event\Realtime;
use Appwrite\Event\StatsUsage;
use Appwrite\Event\Webhook;
use Appwrite\Platform\Appwrite;
use Appwrite\Utopia\Database\Documents\User;
use Executor\Executor;
use Swoole\Runtime;
use Utopia\Abuse\Adapters\TimeLimit\Redis as TimeLimitRedis;
@@ -62,7 +63,9 @@ Server::setResource('dbForPlatform', function (Cache $cache, Registry $register,
$dbForPlatform
->setAuthorization($authorization)
->setNamespace('_console');
->setNamespace('_console')
->setDocumentType('users', User::class)
;
return $dbForPlatform;
@@ -95,6 +98,7 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
$adapter = new DatabasePool($pools->get($dsn->getHost()));
$database = new Database($adapter, $cache);
$database->setDocumentType('users', User::class);
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
+4 -3
View File
@@ -48,9 +48,10 @@
"utopia-php/abuse": "1.*",
"utopia-php/analytics": "0.10.*",
"utopia-php/audit": "1.*",
"utopia-php/auth": "0.5.*",
"utopia-php/cache": "0.13.*",
"utopia-php/cli": "0.15.*",
"utopia-php/config": "0.2.*",
"utopia-php/config": "1.*.*",
"utopia-php/database": "dev-cache-documents-xlist as 4.3.0",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "0.9.*",
@@ -63,7 +64,7 @@
"utopia-php/locale": "0.8.*",
"utopia-php/logger": "0.6.*",
"utopia-php/messaging": "0.20.*",
"utopia-php/migration": "1.*",
"utopia-php/migration": "1.3.*",
"utopia-php/orchestration": "0.9.*",
"utopia-php/platform": "0.7.*",
"utopia-php/pools": "0.8.*",
@@ -74,7 +75,7 @@
"utopia-php/swoole": "0.8.*",
"utopia-php/system": "0.9.*",
"utopia-php/telemetry": "0.1.*",
"utopia-php/vcs": "0.12.*",
"utopia-php/vcs": "0.13.*",
"utopia-php/websocket": "0.3.*",
"matomo/device-detector": "6.4.*",
"dragonmantank/cron-expression": "3.4.*",
-7
View File
@@ -1,7 +0,0 @@
services:
appwrite:
# volumes:
# - ~/.ssh:/root/.ssh
environment:
- GH_TOKEN=
- GIT_EMAIL=
+3 -2
View File
@@ -285,6 +285,7 @@ services:
- _APP_DB_PASS
- _APP_USAGE_STATS
- _APP_LOGGING_CONFIG
- _APP_LOGGING_CONFIG_REALTIME
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-audits:
@@ -950,7 +951,7 @@ services:
appwrite-assistant:
container_name: appwrite-assistant
image: appwrite/assistant:0.8.3
image: appwrite/assistant:0.8.4
networks:
- appwrite
environment:
@@ -958,7 +959,7 @@ services:
appwrite-browser:
container_name: appwrite-browser
image: appwrite/browser:0.3.1
image: appwrite/browser:0.3.2
networks:
- appwrite
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.5.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: &lt;REGION&gt;.cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.5.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.5.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: &lt;REGION&gt;.cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.5.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: &lt;REGION&gt;.cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.5.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.5.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: &lt;REGION&gt;.cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.6.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.7.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.7.0
@@ -1,4 +1,4 @@
POST /v1/account/mfa/challenge HTTP/1.1
POST /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.7.0
@@ -1,4 +1,4 @@
PUT /v1/account/mfa/challenge HTTP/1.1
PUT /v1/account/mfa/challenges HTTP/1.1
Host: cloud.appwrite.io
Content-Type: application/json
X-Appwrite-Response-Format: 1.7.0
@@ -13,7 +13,7 @@ account.createOAuth2Session(
OAuthProvider.AMAZON, // provider
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
listOf(), // scopes (optional)
List.of(), // scopes (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -13,7 +13,7 @@ account.createOAuth2Token(
OAuthProvider.AMAZON, // provider
"https://example.com", // success (optional)
"https://example.com", // failure (optional)
listOf(), // scopes (optional)
List.of(), // scopes (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -9,7 +9,7 @@ Client client = new Client(context)
Account account = new Account(client);
account.listIdentities(
listOf(), // queries (optional)
List.of(), // queries (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -9,7 +9,7 @@ Client client = new Client(context)
Account account = new Account(client);
account.listLogs(
listOf(), // queries (optional)
List.of(), // queries (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -9,10 +9,10 @@ Client client = new Client(context)
Account account = new Account(client);
account.updatePrefs(
mapOf(
"language" to "en",
"timezone" to "UTC",
"darkTheme" to true
Map.of(
"language", "en",
"timezone", "UTC",
"darkTheme", true
), // prefs
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -13,24 +13,27 @@ Avatars avatars = new Avatars(client);
avatars.getScreenshot(
"https://example.com", // url
mapOf( "a" to "b" ), // headers (optional)
1, // viewportWidth (optional)
1, // viewportHeight (optional)
0.1, // scale (optional)
Map.of(
"Authorization", "Bearer token123",
"X-Custom-Header", "value"
), // headers (optional)
1920, // viewportWidth (optional)
1080, // viewportHeight (optional)
2, // scale (optional)
Theme.LIGHT, // theme (optional)
"<USER_AGENT>", // userAgent (optional)
false, // fullpage (optional)
"<LOCALE>", // locale (optional)
"Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15", // userAgent (optional)
true, // fullpage (optional)
"en-US", // locale (optional)
Timezone.AFRICA_ABIDJAN, // timezone (optional)
-90, // latitude (optional)
-180, // longitude (optional)
0, // accuracy (optional)
false, // touch (optional)
listOf(), // permissions (optional)
0, // sleep (optional)
0, // width (optional)
0, // height (optional)
-1, // quality (optional)
37.7749, // latitude (optional)
-122.4194, // longitude (optional)
100, // accuracy (optional)
true, // touch (optional)
List.of("geolocation", "notifications"), // permissions (optional)
3, // sleep (optional)
800, // width (optional)
600, // height (optional)
85, // quality (optional)
Output.JPG, // output (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,14 +14,14 @@ databases.createDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 30,
"isAdmin" to false
Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 30,
"isAdmin", false
), // data
listOf(Permission.read(Role.any())), // permissions (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -10,17 +10,15 @@ Databases databases = new Databases(client);
databases.createOperations(
"<TRANSACTION_ID>", // transactionId
listOf(
{
"action": "create",
"databaseId": "<DATABASE_ID>",
"collectionId": "<COLLECTION_ID>",
"documentId": "<DOCUMENT_ID>",
"data": {
"name": "Walter O'Brien"
}
}
), // operations (optional)
List.of(Map.of(
"action", "create",
"databaseId", "<DATABASE_ID>",
"collectionId", "<COLLECTION_ID>",
"documentId", "<DOCUMENT_ID>",
"data", Map.of(
"name", "Walter O'Brien"
)
)), // operations (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -12,7 +12,7 @@ databases.getDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
listOf(), // queries (optional)
List.of(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -11,7 +11,7 @@ Databases databases = new Databases(client);
databases.listDocuments(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
listOf(), // queries (optional)
List.of(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
@@ -9,7 +9,7 @@ Client client = new Client(context)
Databases databases = new Databases(client);
databases.listTransactions(
listOf(), // queries (optional)
List.of(), // queries (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,8 +14,8 @@ databases.updateDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
mapOf( "a" to "b" ), // data (optional)
listOf(Permission.read(Role.any())), // permissions (optional)
Map.of("a", "b"), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Databases;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.Databases;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,8 +14,8 @@ databases.upsertDocument(
"<DATABASE_ID>", // databaseId
"<COLLECTION_ID>", // collectionId
"<DOCUMENT_ID>", // documentId
mapOf( "a" to "b" ), // data
listOf(Permission.read(Role.any())), // permissions (optional)
Map.of("a", "b"), // data
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -15,7 +15,7 @@ functions.createExecution(
false, // async (optional)
"<PATH>", // path (optional)
ExecutionMethod.GET, // method (optional)
mapOf( "a" to "b" ), // headers (optional)
Map.of("a", "b"), // headers (optional)
"<SCHEDULED_AT>", // scheduledAt (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -10,7 +10,7 @@ Functions functions = new Functions(client);
functions.listExecutions(
"<FUNCTION_ID>", // functionId
listOf(), // queries (optional)
List.of(), // queries (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -9,7 +9,7 @@ Client client = new Client(context)
Graphql graphql = new Graphql(client);
graphql.mutation(
mapOf( "a" to "b" ), // query
Map.of("a", "b"), // query
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -9,7 +9,7 @@ Client client = new Client(context)
Graphql graphql = new Graphql(client);
graphql.query(
mapOf( "a" to "b" ), // query
Map.of("a", "b"), // query
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,9 +1,9 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.models.InputFile;
import io.appwrite.services.Storage;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.Storage;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -15,7 +15,7 @@ storage.createFile(
"<BUCKET_ID>", // bucketId
"<FILE_ID>", // fileId
InputFile.fromPath("file.png"), // file
listOf(Permission.read(Role.any())), // permissions (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -10,7 +10,7 @@ Storage storage = new Storage(client);
storage.listFiles(
"<BUCKET_ID>", // bucketId
listOf(), // queries (optional)
List.of(), // queries (optional)
"<SEARCH>", // search (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.Storage;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.Storage;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,7 +14,7 @@ storage.updateFile(
"<BUCKET_ID>", // bucketId
"<FILE_ID>", // fileId
"<NAME>", // name (optional)
listOf(Permission.read(Role.any())), // permissions (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -10,17 +10,15 @@ TablesDB tablesDB = new TablesDB(client);
tablesDB.createOperations(
"<TRANSACTION_ID>", // transactionId
listOf(
{
"action": "create",
"databaseId": "<DATABASE_ID>",
"tableId": "<TABLE_ID>",
"rowId": "<ROW_ID>",
"data": {
"name": "Walter O'Brien"
}
}
), // operations (optional)
List.of(Map.of(
"action", "create",
"databaseId", "<DATABASE_ID>",
"tableId", "<TABLE_ID>",
"rowId", "<ROW_ID>",
"data", Map.of(
"name", "Walter O'Brien"
)
)), // operations (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,14 +14,14 @@ tablesDB.createRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
mapOf(
"username" to "walter.obrien",
"email" to "walter.obrien@example.com",
"fullName" to "Walter O'Brien",
"age" to 30,
"isAdmin" to false
Map.of(
"username", "walter.obrien",
"email", "walter.obrien@example.com",
"fullName", "Walter O'Brien",
"age", 30,
"isAdmin", false
), // data
listOf(Permission.read(Role.any())), // permissions (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -12,7 +12,7 @@ tablesDB.getRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
listOf(), // queries (optional)
List.of(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -11,7 +11,7 @@ TablesDB tablesDB = new TablesDB(client);
tablesDB.listRows(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
listOf(), // queries (optional)
List.of(), // queries (optional)
"<TRANSACTION_ID>", // transactionId (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
@@ -9,7 +9,7 @@ Client client = new Client(context)
TablesDB tablesDB = new TablesDB(client);
tablesDB.listTransactions(
listOf(), // queries (optional)
List.of(), // queries (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,8 +14,8 @@ tablesDB.updateRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
mapOf( "a" to "b" ), // data (optional)
listOf(Permission.read(Role.any())), // permissions (optional)
Map.of("a", "b"), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -1,8 +1,8 @@
import io.appwrite.Client;
import io.appwrite.coroutines.CoroutineCallback;
import io.appwrite.services.TablesDB;
import io.appwrite.Permission;
import io.appwrite.Role;
import io.appwrite.services.TablesDB;
Client client = new Client(context)
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
@@ -14,8 +14,8 @@ tablesDB.upsertRow(
"<DATABASE_ID>", // databaseId
"<TABLE_ID>", // tableId
"<ROW_ID>", // rowId
mapOf( "a" to "b" ), // data (optional)
listOf(Permission.read(Role.any())), // permissions (optional)
Map.of("a", "b"), // data (optional)
List.of(Permission.read(Role.any())), // permissions (optional)
"<TRANSACTION_ID>", // transactionId (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
@@ -10,7 +10,7 @@ Teams teams = new Teams(client);
teams.createMembership(
"<TEAM_ID>", // teamId
listOf(), // roles
List.of(), // roles
"email@example.com", // email (optional)
"<USER_ID>", // userId (optional)
"+12065550100", // phone (optional)
@@ -11,7 +11,7 @@ Teams teams = new Teams(client);
teams.create(
"<TEAM_ID>", // teamId
"<NAME>", // name
listOf(), // roles (optional)
List.of(), // roles (optional)
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -10,7 +10,7 @@ Teams teams = new Teams(client);
teams.listMemberships(
"<TEAM_ID>", // teamId
listOf(), // queries (optional)
List.of(), // queries (optional)
"<SEARCH>", // search (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
@@ -9,7 +9,7 @@ Client client = new Client(context)
Teams teams = new Teams(client);
teams.list(
listOf(), // queries (optional)
List.of(), // queries (optional)
"<SEARCH>", // search (optional)
false, // total (optional)
new CoroutineCallback<>((result, error) -> {
@@ -11,7 +11,7 @@ Teams teams = new Teams(client);
teams.updateMembership(
"<TEAM_ID>", // teamId
"<MEMBERSHIP_ID>", // membershipId
listOf(), // roles
List.of(), // roles
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();
@@ -10,7 +10,7 @@ Teams teams = new Teams(client);
teams.updatePrefs(
"<TEAM_ID>", // teamId
mapOf( "a" to "b" ), // prefs
Map.of("a", "b"), // prefs
new CoroutineCallback<>((result, error) -> {
if (error != null) {
error.printStackTrace();

Some files were not shown because too many files have changed in this diff Show More