Merge branch '1.3.x' of github.com:appwrite/appwrite into poc-timeout

This commit is contained in:
fogelito
2023-03-02 08:56:36 +02:00
255 changed files with 11323 additions and 365 deletions
+4
View File
@@ -1,5 +1,9 @@
name: "CodeQL"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches: [master]
+4
View File
@@ -1,5 +1,9 @@
name: "Linter"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: [pull_request]
jobs:
lint:
+24 -9
View File
@@ -1,5 +1,9 @@
name: "Tests"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on: [pull_request]
jobs:
tests:
@@ -21,18 +25,29 @@ jobs:
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# This is a separate action that sets up buildx runner
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Build Appwrite
# Upstream bug causes buildkit pulls to fail so prefetch base images
# https://github.com/moby/moby/issues/41864
uses: docker/build-push-action@v3
with:
context: .
push: false
tags: appwrite-dev
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
DEBUG=false
TESTING=true
VERSION=dev
- name: Start Appwrite
run: |
export COMPOSE_INTERACTIVE_NO_CLI
export DOCKER_BUILDKIT=1
export COMPOSE_DOCKER_CLI_BUILD=1
export BUILDKIT_PROGRESS=plain
docker pull composer:2.0
docker compose build appwrite
docker compose up -d
sleep 30
- name: Doctor
run: docker compose exec -T appwrite doctor
@@ -40,4 +55,4 @@ jobs:
run: docker compose exec -T appwrite vars
- name: Run Tests
run: docker compose exec -T appwrite test --debug
run: docker compose exec -T appwrite test --debug
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 2.1.1
branch = 2.2.0
+18 -2
View File
@@ -1,12 +1,28 @@
# Version 1.2.1
## Features
# Version TBD
## Features
- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906)
- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866)
## Bugs
- Fix expire to formatTz in create account session [#4985](https://github.com/appwrite/appwrite/pull/4985)
- Fix not storing function's response on response codes 5xx [#4610](https://github.com/appwrite/appwrite/pull/4610)
# Version 1.2.1
## Changes
- Upgrade Console to [2.2.0](https://github.com/appwrite/console/releases/tag/2.2.0)
- Update DBIP Database [#5049](https://github.com/appwrite/appwrite/pull/5049)
## Bugs
- Fix a few null safety warnings [#4654](https://github.com/appwrite/appwrite/pull/4654)
- Fix timestamp format in Realtime response [#4515](https://github.com/appwrite/appwrite/pull/4515)
- Add flutter-web as a platform type [#4992](https://github.com/appwrite/appwrite/pull/4992)
- Fix typo in Model/Locale.php [#4669](https://github.com/appwrite/appwrite/pull/4669)
- Fix deletes worker not deleting project database tables [#4984](https://github.com/appwrite/appwrite/pull/4984)
- Fix deletes worker not deleting database collections [#4983](https://github.com/appwrite/appwrite/pull/4983)
- Fix restart policy for worker-messaging container [#4994](https://github.com/appwrite/appwrite/pull/4994)
- Fix validating origin for apple platforms [#5089](https://github.com/appwrite/appwrite/pull/5089)
# Version 1.2.0
## Features
+9 -6
View File
@@ -1,3 +1,6 @@
> 准备好迎接狂风暴'云'了吗? 🌩 ☂️
> Appwrite Cloud即将到来!你能够通过https://appwrite.io/cloud了解更多的资讯, 注册即可领取试用额度哦
<br />
<p align="center">
<a href="https://appwrite.io" target="_blank"><img width="260" height="39" src="https://appwrite.io/images/appwrite.svg" alt="Appwrite Logo"></a>
@@ -23,7 +26,7 @@
[**我们发布了 Appwrite Console 2.0 版本,点击这里了解更多!**](https://medium.com/appwrite-io/announcing-console-2-0-2e0e96891cb0?source=friends_link&sk=7a82b4069778e3adc165dc026e960fe1)
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面极简了从零编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面简化了从零开始编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
Appwrite 可以提供给开发者用户验证,外部授权,用户数据读写检索,文件储存,图像处理,云函数计算,[等多种服务](https://appwrite.io/docs).
@@ -55,7 +58,7 @@ Appwrite 可以提供给开发者用户验证,外部授权,用户数据读
Appwrite 的容器化服务器只需要一行指令就可以运行。您可以使用 docker-compose 在本地主机上运行 Appwrite,也可以在任何其他容器化工具(如 Kubernetes、Docker Swarm 或 Rancher)上运行 Appwrite。
开始运行 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
启动 Appwrite 服务器的最简单方法是运行我们的 docker-compose 文件。在运行安装命令之前,请确保您的机器上安装了 [Docker](https://dockerdocs.cn/get-docker/index.html):
### Unix
@@ -64,7 +67,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.2.0
appwrite/appwrite:1.2.1
```
### Windows
@@ -76,7 +79,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.2.0
appwrite/appwrite:1.2.1
```
#### PowerShell
@@ -86,7 +89,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.2.0
appwrite/appwrite:1.2.1
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
@@ -159,7 +162,7 @@ Appwrite API 界面层利用后台缓存和任务委派来提供极速的响应
## 贡献代码
所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。这是为了确保正确审查所有代码。
为了确保正确审查,所有代码贡献 - 包括来自具有直接提交更改权限的贡献者 - 都必须提交PR请求并在合并分支之前得到核心开发人员的批准。
我们欢迎所有人提交PR!如果您愿意提供帮助,可以在 [贡献指南](CONTRIBUTING.md) 中了解有关如何为项目做出贡献的更多信息。
+3 -3
View File
@@ -75,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.2.0
appwrite/appwrite:1.2.1
```
### Windows
@@ -87,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.2.0
appwrite/appwrite:1.2.1
```
#### PowerShell
@@ -97,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.2.0
appwrite/appwrite:1.2.1
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+7 -7
View File
@@ -2,13 +2,13 @@
## Supported Versions
| Version | Supported |
| ------- | ------------------ |
| <= 0.14 | :x: |
| 1.15.x | :white_check_mark: |
| 1.0.x | :white_check_mark: |
| 1.1.x | :white_check_mark: |
| 1.2.x | :white_check_mark: |
| Version | Supported |
| --------- | ------------------ |
| <= 0.14.x | :x: |
| 0.15.x | :white_check_mark: |
| 1.0.x | :white_check_mark: |
| 1.1.x | :white_check_mark: |
| 1.2.x | :white_check_mark: |
## Reporting a Vulnerability
File diff suppressed because it is too large Load Diff
+11
View File
@@ -1234,6 +1234,17 @@ $collections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('passwordHistory'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => false,
'default' => null,
'array' => true,
'filters' => [],
],
[
'$id' => ID::custom('password'),
'type' => Database::VAR_STRING,
+7 -7
View File
@@ -11,7 +11,7 @@ return [
'description' => 'Client libraries for integrating with Appwrite to build client-based applications and websites. Read the [getting started for web](/docs/getting-started-for-web) or [getting started for Flutter](/docs/getting-started-for-flutter) tutorials to start building your first application.',
'enabled' => true,
'beta' => false,
'languages' => [ // TODO change key to 'sdks'
'sdks' => [
[
'key' => 'web',
'name' => 'Web',
@@ -181,11 +181,11 @@ return [
'name' => 'Console',
'enabled' => false,
'beta' => false,
'languages' => [
'sdks' => [
[
'key' => 'web',
'name' => 'Console',
'version' => '7.2.0',
'version' => '0.0.2-preview-0.0',
'url' => 'https://github.com/appwrite/sdk-for-console',
'package' => '',
'enabled' => true,
@@ -193,10 +193,10 @@ return [
'dev' => false,
'hidden' => true,
'family' => APP_PLATFORM_CONSOLE,
'prism' => 'console',
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => '',
'gitBranch' => '',
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
'gitBranch' => 'main',
'gitRepoName' => 'sdk-for-console',
'gitUserName' => 'appwrite',
],
@@ -227,7 +227,7 @@ return [
'description' => 'Libraries for integrating with Appwrite to build server side integrations. Read the [getting started for server](/docs/getting-started-for-server) tutorial to start building your first server integration.',
'enabled' => true,
'beta' => false,
'languages' => [
'sdks' => [
[
'key' => 'nodejs',
'name' => 'Node.js',
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+76 -12
View File
@@ -40,6 +40,8 @@ use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PasswordDictionary;
$oauthDefaultSuccess = '/auth/oauth2/success';
$oauthDefaultFailure = '/auth/oauth2/failure';
@@ -64,7 +66,7 @@ App::post('/v1/account')
->label('abuse-limit', 10)
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('request')
->inject('response')
@@ -72,7 +74,6 @@ App::post('/v1/account')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) {
$email = \strtolower($email);
if ('console' === $project->getId()) {
$whitelistEmails = $project->getAttribute('authWhitelistEmails');
@@ -97,6 +98,8 @@ App::post('/v1/account')
}
}
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
try {
$userId = $userId == 'unique()' ? ID::unique() : $userId;
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
@@ -109,10 +112,11 @@ App::post('/v1/account')
'email' => $email,
'emailVerification' => false,
'status' => true,
'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'password' => $password,
'passwordHistory' => $passwordHistory > 0 ? [$password] : [],
'passwordUpdate' => DateTime::now(),
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => DateTime::now(),
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
@@ -188,7 +192,7 @@ App::post('/v1/account/sessions/email')
$detector = new Detector($request->getUserAgent('UNKNOWN'));
$record = $geodb->get($request->getIP());
$expire = DateTime::addSeconds(new \DateTime(), $duration);
$expire = DateTime::formatTz(DateTime::addSeconds(new \DateTime(), $duration));
$secret = Auth::tokenGenerator();
$session = new Document(array_merge(
[
@@ -280,6 +284,12 @@ App::get('/v1/account/sessions/oauth2/:provider')
$protocol = $request->getProtocol();
$callback = $protocol . '://' . $request->getHostname() . '/v1/account/sessions/oauth2/callback/' . $provider . '/' . $project->getId();
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
}
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
@@ -371,6 +381,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
->label('scope', 'public')
->label('audits.event', 'session.create')
->label('audits.resource', 'user/{user.$id}')
->label('audits.userId', '{user.$id}')
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->label('docs', false)
@@ -394,6 +405,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
$validateURL = new URL();
$appId = $project->getAttribute('authProviders', [])[$provider . 'Appid'] ?? '';
$appSecret = $project->getAttribute('authProviders', [])[$provider . 'Secret'] ?? '{}';
$providerEnabled = $project->getAttribute('authProviders', [])[$provider . 'Enabled'] ?? false;
if (!$providerEnabled) {
throw new Exception(Exception::PROJECT_PROVIDER_DISABLED, 'This provider is disabled. Please enable the provider from your ' . APP_NAME . ' console to continue.');
}
if (!empty($appSecret) && isset($appSecret['version'])) {
$key = App::getEnv('_APP_OPENSSL_KEY_V' . $appSecret['version']);
@@ -489,8 +505,11 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
}
}
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
try {
$userId = ID::unique();
$password = Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
@@ -501,7 +520,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
'email' => $email,
'emailVerification' => true,
'status' => true, // Email should already be authenticated by OAuth2 provider
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
'passwordHistory' => $passwordHistory > 0 ? [$password] : null,
'password' => $password,
'hash' => Auth::DEFAULT_ALGO,
'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS,
'passwordUpdate' => null,
@@ -1309,6 +1329,8 @@ App::get('/v1/account')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('sdk.offline.model', '/account')
->label('sdk.offline.key', 'current')
->inject('response')
->inject('user')
->action(function (Response $response, Document $user) {
@@ -1328,6 +1350,8 @@ App::get('/v1/account/prefs')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PREFERENCES)
->label('sdk.offline.model', '/account/prefs')
->label('sdk.offline.key', 'current')
->inject('response')
->inject('user')
->action(function (Response $response, Document $user) {
@@ -1349,6 +1373,7 @@ App::get('/v1/account/sessions')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SESSION_LIST)
->label('sdk.offline.model', '/account/sessions')
->inject('response')
->inject('user')
->inject('locale')
@@ -1447,6 +1472,8 @@ App::get('/v1/account/sessions/:sessionId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_SESSION)
->label('sdk.offline.model', '/account/sessions')
->label('sdk.offline.key', '{sessionId}')
->param('sessionId', '', new UID(), 'Session ID. Use the string \'current\' to get the current device session.')
->inject('response')
->inject('user')
@@ -1485,6 +1512,7 @@ App::patch('/v1/account/name')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -1493,6 +1521,8 @@ App::patch('/v1/account/name')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('sdk.offline.model', '/account')
->label('sdk.offline.key', 'current')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.')
->inject('response')
->inject('user')
@@ -1525,24 +1555,42 @@ App::patch('/v1/account/password')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->label('sdk.offline.model', '/account')
->label('sdk.offline.key', 'current')
->param('oldPassword', '', new Password(), 'Current user password. Must be at least 8 chars.', true)
->inject('response')
->inject('user')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $password, string $oldPassword, Response $response, Document $user, Database $dbForProject, Event $events) {
->action(function (string $password, string $oldPassword, Response $response, Document $user, Document $project, Database $dbForProject, Event $events) {
// Check old password only if its an existing user.
if (!empty($user->getAttribute('passwordUpdate')) && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'), $user->getAttribute('hash'), $user->getAttribute('hashOptions'))) { // Double check user password
throw new Exception(Exception::USER_INVALID_CREDENTIALS);
}
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = [];
if ($historyLimit > 0) {
$history = $user->getAttribute('passwordHistory', []);
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
}
$history[] = $newPassword;
array_slice($history, (count($history) - $historyLimit), $historyLimit);
}
$user = $dbForProject->updateDocument('users', $user->getId(), $user
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
->setAttribute('passwordUpdate', DateTime::now()))
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
->setAttribute('passwordUpdate', DateTime::now()));
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
$events->setParam('userId', $user->getId());
@@ -1556,6 +1604,7 @@ App::patch('/v1/account/email')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -1564,6 +1613,8 @@ App::patch('/v1/account/email')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('sdk.offline.model', '/account')
->label('sdk.offline.key', 'current')
->param('email', '', new Email(), 'User email.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('response')
@@ -1608,6 +1659,7 @@ App::patch('/v1/account/phone')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -1616,6 +1668,8 @@ App::patch('/v1/account/phone')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('sdk.offline.model', '/account')
->label('sdk.offline.key', 'current')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
->inject('response')
@@ -1656,6 +1710,7 @@ App::patch('/v1/account/prefs')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -1664,6 +1719,8 @@ App::patch('/v1/account/prefs')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_ACCOUNT)
->label('sdk.offline.model', '/account/prefs')
->label('sdk.offline.key', 'current')
->param('prefs', [], new Assoc(), 'Prefs key-value JSON object.')
->inject('response')
->inject('user')
@@ -1685,6 +1742,7 @@ App::patch('/v1/account/status')
->label('scope', 'account')
->label('audits.event', 'user.update')
->label('audits.resource', 'user/{response.$id}')
->label('audits.userId', '{response.$id}')
->label('usage.metric', 'users.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -1720,6 +1778,7 @@ App::delete('/v1/account/sessions/:sessionId')
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
->label('audits.userId', '{user.$id}')
->label('usage.metric', 'sessions.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -1879,6 +1938,7 @@ App::delete('/v1/account/sessions')
->label('event', 'users.[userId].sessions.[sessionId].delete')
->label('audits.event', 'session.delete')
->label('audits.resource', 'user/{user.$id}')
->label('audits.userId', '{user.$id}')
->label('usage.metric', 'sessions.{scope}.requests.delete')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -2110,9 +2170,9 @@ App::put('/v1/account/recovery')
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('emailVerification', true));
$recoveryDocument = $dbForProject->getDocument('tokens', $recovery);
@@ -2139,6 +2199,7 @@ App::post('/v1/account/verification')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -2250,6 +2311,7 @@ App::put('/v1/account/verification')
->label('event', 'users.[userId].verification.[tokenId].update')
->label('audits.event', 'verification.update')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -2309,6 +2371,7 @@ App::post('/v1/account/verification/phone')
->label('event', 'users.[userId].verification.[tokenId].create')
->label('audits.event', 'verification.create')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
@@ -2394,6 +2457,7 @@ App::put('/v1/account/verification/phone')
->label('event', 'users.[userId].verification.[tokenId].update')
->label('audits.event', 'verification.update')
->label('audits.resource', 'user/{response.userId}')
->label('audits.userId', '{response.userId}')
->label('usage.metric', 'users.{scope}.requests.update')
->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_JWT])
->label('sdk.namespace', 'account')
+10 -1
View File
@@ -1930,6 +1930,8 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.code', Response::STATUS_CODE_CREATED)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->label('sdk.offline.key', '{documentId}')
->param('databaseId', '', new UID(), 'Database ID.')
->param('documentId', '', new CustomId(), 'Document ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection). Make sure to define attributes before creating documents.')
@@ -2052,6 +2054,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT_LIST)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('queries', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
@@ -2082,7 +2085,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
}
// Validate queries
$queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes'));
$queriesValidator = new Documents($collection->getAttribute('attributes'));
$validQueries = $queriesValidator->isValid($queries);
// todo: take crae of this
if (!$validQueries && $queries[0] != 'notEqual("longtext", "appwrite")') {
@@ -2156,6 +2159,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->label('sdk.offline.key', '{documentId}')
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', '', new UID(), 'Document ID.')
@@ -2325,6 +2330,8 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_DOCUMENT)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->label('sdk.offline.key', '{documentId}')
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID.')
->param('documentId', '', new UID(), 'Document ID.')
@@ -2458,6 +2465,8 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
->label('sdk.description', '/docs/references/databases/delete-document.md')
->label('sdk.response.code', Response::STATUS_CODE_NOCONTENT)
->label('sdk.response.model', Response::MODEL_NONE)
->label('sdk.offline.model', '/databases/{databaseId}/collections/{collectionId}/documents')
->label('sdk.offline.key', '{documentId}')
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('documentId', '', new UID(), 'Document ID.')
+14
View File
@@ -19,6 +19,8 @@ App::get('/v1/locale')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LOCALE)
->label('sdk.offline.model', '/locale')
->label('sdk.offline.key', 'current')
->inject('request')
->inject('response')
->inject('locale')
@@ -77,6 +79,8 @@ App::get('/v1/locale/countries')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
->label('sdk.offline.model', '/locale/countries')
->label('sdk.offline.response.key', 'code')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -108,6 +112,8 @@ App::get('/v1/locale/countries/eu')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_COUNTRY_LIST)
->label('sdk.offline.model', '/locale/countries/eu')
->label('sdk.offline.response.key', 'code')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -141,6 +147,8 @@ App::get('/v1/locale/countries/phones')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PHONE_LIST)
->label('sdk.offline.model', '/locale/countries/phones')
->label('sdk.offline.response.key', 'countryCode')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -173,6 +181,8 @@ App::get('/v1/locale/continents')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_CONTINENT_LIST)
->label('sdk.offline.model', '/locale/continents')
->label('sdk.offline.response.key', 'code')
->inject('response')
->inject('locale')
->action(function (Response $response, Locale $locale) {
@@ -203,6 +213,8 @@ App::get('/v1/locale/currencies')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_CURRENCY_LIST)
->label('sdk.offline.model', '/locale/currencies')
->label('sdk.offline.response.key', 'code')
->inject('response')
->action(function (Response $response) {
$list = Config::getParam('locale-currencies');
@@ -224,6 +236,8 @@ App::get('/v1/locale/languages')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_LANGUAGE_LIST)
->label('sdk.offline.model', '/locale/languages')
->label('sdk.offline.response.key', 'code')
->inject('response')
->action(function (Response $response) {
$list = Config::getParam('locale-languages');
+63 -1
View File
@@ -80,7 +80,7 @@ App::post('/v1/projects')
}
$auth = Config::getParam('auth', []);
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
$auths = ['limit' => 0, 'maxSessions' => APP_LIMIT_USER_SESSIONS_DEFAULT, 'passwordHistory' => 0, 'passwordDictionary' => false, 'duration' => Auth::TOKEN_EXPIRATION_LOGIN_LONG];
foreach ($auth as $index => $method) {
$auths[$method['key'] ?? ''] = true;
}
@@ -575,6 +575,68 @@ App::patch('/v1/projects/:projectId/auth/:method')
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/password-history')
->desc('Update authentication password history. Use this endpoint to set the number of password history to save and 0 to disable password history.')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthPasswordHistory')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('limit', 0, new Range(0, APP_LIMIT_USER_PASSWORD_HISTORY), 'Set the max number of passwords to store in user history. User can\'t choose a new password that is already stored in the password history list. Max number of passwords allowed in history is' . APP_LIMIT_USER_PASSWORD_HISTORY . '. Default value is 0')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, int $limit, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['passwordHistory'] = $limit;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/password-dictionary')
->desc('Update authentication password disctionary status. Use this endpoint to enable or disable the dicitonary check for user password')
->groups(['api', 'projects'])
->label('scope', 'projects.write')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
->label('sdk.method', 'updateAuthPasswordDictionary')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_PROJECT)
->param('projectId', '', new UID(), 'Project unique ID.')
->param('enabled', false, new Boolean(false), 'Set whether or not to enable checking user\'s password against most commonly used passwords. Default is false.')
->inject('response')
->inject('dbForConsole')
->action(function (string $projectId, bool $enabled, Response $response, Database $dbForConsole) {
$project = $dbForConsole->getDocument('projects', $projectId);
if ($project->isEmpty()) {
throw new Exception(Exception::PROJECT_NOT_FOUND);
}
$auths = $project->getAttribute('auths', []);
$auths['passwordDictionary'] = $enabled;
$dbForConsole->updateDocument('projects', $project->getId(), $project
->setAttribute('auths', $auths));
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::patch('/v1/projects/:projectId/auth/max-sessions')
->desc('Update Project user sessions limit')
->groups(['api', 'projects'])
+84 -40
View File
@@ -1,6 +1,7 @@
<?php
use Appwrite\Auth\Auth;
use Appwrite\Auth\Validator\Phone;
use Appwrite\Detector\Detector;
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
@@ -39,6 +40,7 @@ use Utopia\Validator\Text;
use Utopia\Validator\Range;
use Utopia\Validator\ArrayList;
use Utopia\Validator\WhiteList;
use Appwrite\Event\Phone as EventPhone;
App::post('/v1/teams')
->desc('Create Team')
@@ -133,6 +135,7 @@ App::get('/v1/teams')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM_LIST)
->label('sdk.offline.model', '/teams')
->param('queries', [], new Teams(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Teams::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
->inject('response')
@@ -182,6 +185,8 @@ App::get('/v1/teams/:teamId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->label('sdk.offline.model', '/teams')
->label('sdk.offline.key', '{teamId}')
->param('teamId', '', new UID(), 'Team ID.')
->inject('response')
->inject('dbForProject')
@@ -210,6 +215,8 @@ App::put('/v1/teams/:teamId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_TEAM)
->label('sdk.offline.model', '/teams')
->label('sdk.offline.key', '{teamId}')
->param('teamId', '', new UID(), 'Team ID.')
->param('name', null, new Text(128), 'New team name. Max length: 128 chars.')
->inject('response')
@@ -304,7 +311,9 @@ App::post('/v1/teams/:teamId/memberships')
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('abuse-limit', 10)
->param('teamId', '', new UID(), 'Team ID.')
->param('email', '', new Email(), 'Email of the new team member.')
->param('email', '', new Email(), 'Email of the new team member.', true)
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('roles', [], new ArrayList(new Key(), APP_LIMIT_ARRAY_PARAMS_SIZE), '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](/docs/permissions). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 32 characters long.')
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the invitation email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['clients']) // TODO add our own built-in confirm page
->param('name', '', new Text(128), 'Name of the new team member. Max length: 128 chars.', true)
@@ -314,9 +323,13 @@ App::post('/v1/teams/:teamId/memberships')
->inject('dbForProject')
->inject('locale')
->inject('mails')
->inject('messaging')
->inject('events')
->action(function (string $teamId, string $email, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, Event $events) {
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $mails, EventPhone $messaging, Event $events) {
if (empty($userId) && empty($email) && empty($phone)) {
throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'At least one of userId, email, or phone is required');
}
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
$isAppUser = Auth::isAppUser(Authorization::getRoles());
@@ -331,8 +344,31 @@ App::post('/v1/teams/:teamId/memberships')
if ($team->isEmpty()) {
throw new Exception(Exception::TEAM_NOT_FOUND);
}
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
if (!empty($userId)) {
$invitee = $dbForProject->getDocument('users', $userId);
if ($invitee->isEmpty()) {
throw new Exception(Exception::USER_NOT_FOUND, 'User with given userId doesn\'t exist.', 404);
}
if (!empty($email) && $invitee->getAttribute('email', '') != $email) {
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and email doesn\'t match', 409);
}
if (!empty($phone) && $invitee->getAttribute('phone', '') != $phone) {
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given userId and phone doesn\'t match', 409);
}
$email = $invitee->getAttribute('email', '');
$phone = $invitee->getAttribute('phone', '');
$name = empty($name) ? $invitee->getAttribute('name', '') : $name;
} elseif (!empty($email)) {
$invitee = $dbForProject->findOne('users', [Query::equal('email', [$email])]); // Get user by email address
if (!empty($invitee) && !empty($phone) && $invitee->getAttribute('phone', '') != $phone) {
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given email and phone doesn\'t match', 409);
}
} elseif (!empty($phone)) {
$invitee = $dbForProject->findOne('users', [Query::equal('phone', [$phone])]);
if (!empty($invitee) && !empty($email) && $invitee->getAttribute('email', '') != $email) {
throw new Exception(Exception::USER_ALREADY_EXISTS, 'Given phone and email doesn\'t match', 409);
}
}
if (empty($invitee)) { // Create new user if no user with same email found
$limit = $project->getAttribute('auths', [])['limit'] ?? 0;
@@ -355,7 +391,8 @@ App::post('/v1/teams/:teamId/memberships')
Permission::update(Role::user($userId)),
Permission::delete(Role::user($userId)),
],
'email' => $email,
'email' => empty($email) ? null : $email,
'phone' => empty($phone) ? null : $phone,
'emailVerification' => false,
'status' => true,
'password' => Auth::passwordHash(Auth::passwordGenerator(), Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS),
@@ -427,46 +464,50 @@ App::post('/v1/teams/:teamId/memberships')
} catch (Duplicate $th) {
throw new Exception(Exception::TEAM_INVITE_ALREADY_EXISTS);
}
}
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
$url = Template::unParseURL($url);
$url = Template::parseURL($url);
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['membershipId' => $membership->getId(), 'userId' => $invitee->getId(), 'secret' => $secret, 'teamId' => $teamId]);
$url = Template::unParseURL($url);
if (!empty($email)) {
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
if (!$isPrivilegedUser && !$isAppUser) { // No need of confirmation when in admin or app mode
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
$body->setParam('{{owner}}', $user->getAttribute('name'));
$body->setParam('{{team}}', $team->getAttribute('name'));
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
$subject = \sprintf($locale->getText("emails.invitation.subject"), $team->getAttribute('name'), $projectName);
$body->setParam('{{owner}}', $user->getAttribute('name'));
$body->setParam('{{team}}', $team->getAttribute('name'));
$body
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{name}}', $user->getAttribute('name'))
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
->setParam('{{redirect}}', $url)
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
->setParam('{{project}}', $projectName)
->setParam('{{direction}}', $locale->getText('settings.direction'))
->setParam('{{bg-body}}', '#f7f7f7')
->setParam('{{bg-content}}', '#ffffff')
->setParam('{{text-content}}', '#000000');
$body
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.invitation.hello"))
->setParam('{{name}}', $user->getAttribute('name'))
->setParam('{{body}}', $locale->getText("emails.invitation.body"))
->setParam('{{redirect}}', $url)
->setParam('{{footer}}', $locale->getText("emails.invitation.footer"))
->setParam('{{thanks}}', $locale->getText("emails.invitation.thanks"))
->setParam('{{signature}}', $locale->getText("emails.invitation.signature"))
->setParam('{{project}}', $projectName)
->setParam('{{direction}}', $locale->getText('settings.direction'))
->setParam('{{bg-body}}', '#f7f7f7')
->setParam('{{bg-content}}', '#ffffff')
->setParam('{{text-content}}', '#000000');
$body = $body->render();
$body = $body->render();
$mails
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name'))
->trigger()
;
$mails
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setRecipient($invitee->getAttribute('email'))
->setName($invitee->getAttribute('name'))
->trigger()
;
} elseif (!empty($phone)) {
$messaging
->setRecipient($phone)
->setMessage($url)
->trigger();
}
}
$events
@@ -496,6 +537,7 @@ App::get('/v1/teams/:teamId/memberships')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP_LIST)
->label('sdk.offline.model', '/teams/{teamId}/memberships')
->param('teamId', '', new UID(), 'Team ID.')
->param('queries', [], new Memberships(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', Memberships::ALLOWED_ATTRIBUTES), true)
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
@@ -577,6 +619,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MEMBERSHIP)
->label('sdk.offline.model', '/teams/{teamId}/memberships')
->label('sdk.offline.key', '{membershipId}')
->param('teamId', '', new UID(), 'Team ID.')
->param('membershipId', '', new UID(), 'Membership ID.')
->inject('response')
+56 -25
View File
@@ -34,11 +34,14 @@ use Utopia\Validator\Text;
use Utopia\Validator\Boolean;
use MaxMind\Db\Reader;
use Utopia\Validator\Integer;
use Appwrite\Auth\Validator\PasswordHistory;
use Appwrite\Auth\Validator\PasswordDictionary;
/** 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, Database $dbForProject, Event $events): Document
function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Document $project, Database $dbForProject, Event $events): Document
{
$hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
if (!empty($email)) {
$email = \strtolower($email);
@@ -49,6 +52,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
? ID::unique()
: ID::custom($userId);
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
$user = $dbForProject->createDocument('users', new Document([
'$id' => $userId,
'$permissions' => [
@@ -61,10 +65,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
'phone' => $phone,
'phoneVerification' => false,
'status' => true,
'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null,
'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],
'passwordUpdate' => (!empty($password)) ? DateTime::now() : null,
'registration' => DateTime::now(),
'reset' => false,
'name' => $name,
@@ -101,13 +106,15 @@ App::post('/v1/users')
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', null, new Email(), 'User email.', true)
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
->param('password', null, new Password(), 'Plain text user password. Must be at least 8 chars.', true)
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $events);
->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -134,10 +141,11 @@ App::post('/v1/users/bcrypt')
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -164,10 +172,11 @@ App::post('/v1/users/md5')
->param('password', '', new Password(), 'User password hashed using MD5.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = createUser('md5', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -194,10 +203,11 @@ App::post('/v1/users/argon2')
->param('password', '', new Password(), 'User password hashed using Argon2.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -225,16 +235,17 @@ App::post('/v1/users/sha')
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Database $dbForProject, Event $events) {
->action(function (string $userId, string $email, string $password, string $passwordVersion, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$options = '{}';
if (!empty($passwordVersion)) {
$options = '{"version":"' . $passwordVersion . '"}';
}
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $events);
$user = createUser('sha', $options, $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -261,10 +272,11 @@ App::post('/v1/users/phpass')
->param('password', '', new Password(), 'User password hashed using PHPass.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $events);
->action(function (string $userId, string $email, string $password, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -296,9 +308,10 @@ App::post('/v1/users/scrypt')
->param('passwordLength', 64, new Integer(), 'Optional hash length used to hash password.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $passwordSalt, int $passwordCpu, int $passwordMemory, int $passwordParallel, int $passwordLength, string $name, Response $response, Database $dbForProject, Event $events) {
->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, Event $events) {
$options = [
'salt' => $passwordSalt,
'costCpu' => $passwordCpu,
@@ -307,7 +320,7 @@ App::post('/v1/users/scrypt')
'length' => $passwordLength
];
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $events);
$user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -337,10 +350,11 @@ App::post('/v1/users/scrypt-modified')
->param('passwordSignerKey', '', new Text(128), 'Signer key used to hash password.')
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Event $events) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $events);
->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $project, $dbForProject, $events);
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
@@ -779,11 +793,12 @@ App::patch('/v1/users/:userId/password')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->param('userId', '', new UID(), 'User ID.')
->param('password', '', new Password(), 'New user password. Must be at least 8 chars.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->inject('response')
->inject('project')
->inject('dbForProject')
->inject('events')
->action(function (string $userId, string $password, Response $response, Database $dbForProject, Event $events) {
->action(function (string $userId, string $password, Response $response, Document $project, Database $dbForProject, Event $events) {
$user = $dbForProject->getDocument('users', $userId);
@@ -791,11 +806,27 @@ App::patch('/v1/users/:userId/password')
throw new Exception(Exception::USER_NOT_FOUND);
}
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
$historyLimit = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
$history = [];
if ($historyLimit > 0) {
$history = $user->getAttribute('passwordHistory', []);
$validator = new PasswordHistory($history, $user->getAttribute('hash'), $user->getAttribute('hashOptions'));
if (!$validator->isValid($password)) {
throw new Exception(Exception::USER_PASSWORD_RECENTLY_USED, 'The password was recently used', 409);
}
$history[] = $newPassword;
array_slice($history, (count($history) - $historyLimit), $historyLimit);
}
$user
->setAttribute('password', Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS))
->setAttribute('password', $newPassword)
->setAttribute('passwordHistory', $history)
->setAttribute('passwordUpdate', DateTime::now())
->setAttribute('hash', Auth::DEFAULT_ALGO)
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS)
->setAttribute('passwordUpdate', DateTime::now());
->setAttribute('hashOptions', Auth::DEFAULT_ALGO_OPTIONS);
$user = $dbForProject->updateDocument('users', $user->getId(), $user);
+2
View File
@@ -135,6 +135,8 @@ App::post('/v1/mock/tests/bar')
->label('sdk.response.code', Response::STATUS_CODE_OK)
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_MOCK)
->label('sdk.offline.model', '/mock/tests/bar')
->label('sdk.offline.key', '{required}')
->label('sdk.mock', true)
->param('required', '', new Text(100), 'Sample string param')
->param('default', '', new Integer(true), 'Sample numeric param')
+1 -1
View File
@@ -17,7 +17,7 @@ App::get('/versions')
];
foreach ($platforms as $platform) {
$languages = $platform['languages'] ?? [];
$languages = $platform['sdks'] ?? [];
foreach ($languages as $key => $language) {
if (isset($language['dev']) && $language['dev']) {
+4
View File
@@ -548,6 +548,10 @@ App::post('/v1/execution')
case $statusCode >= 500:
$stderr = ($executorResponse ?? [])['stderr'] ?? 'Internal Runtime error.';
$stdout = ($executorResponse ?? [])['stdout'] ?? 'Internal Runtime error.';
$res = ($executorResponse ?? [])['response'] ?? '';
if (is_array($res)) {
$res = json_encode($res, JSON_UNESCAPED_UNICODE);
}
break;
case $statusCode >= 100:
$stdout = $executorResponse['stdout'];
+14 -2
View File
@@ -86,6 +86,7 @@ const APP_MODE_ADMIN = 'admin';
const APP_PAGING_LIMIT = 12;
const APP_LIMIT_COUNT = 5000;
const APP_LIMIT_USERS = 10000;
const APP_LIMIT_USER_PASSWORD_HISTORY = 20;
const APP_LIMIT_USER_SESSIONS_MAX = 100;
const APP_LIMIT_USER_SESSIONS_DEFAULT = 10;
const APP_LIMIT_ANTIVIRUS = 20000000; //20MB
@@ -100,7 +101,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 501;
const APP_VERSION_STABLE = '1.2.0';
const APP_VERSION_STABLE = '1.2.1';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -607,6 +608,12 @@ $register->set('smtp', function () {
$register->set('geodb', function () {
return new Reader(__DIR__ . '/assets/dbip/dbip-country-lite-2023-01.mmdb');
});
$register->set('passwordsDictionary', function () {
$content = \file_get_contents(__DIR__ . '/assets/security/10k-common-passwords');
$content = explode("\n", $content);
$content = array_flip($content);
return $content;
});
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
$dbHost = App::getEnv('_APP_DB_HOST', '');
@@ -1027,6 +1034,11 @@ App::setResource('geodb', function ($register) {
return $register->get('geodb');
}, ['register']);
App::setResource('passwordsDictionary', function ($register) {
/** @var Utopia\Registry\Registry $register */
return $register->get('passwordsDictionary');
}, ['register']);
App::setResource('sms', function () {
$dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER'));
$user = $dsn->getUser();
@@ -1049,7 +1061,7 @@ App::setResource('servers', function () {
$languages = array_map(function ($language) {
return strtolower($language['name']);
}, $server['languages']);
}, $server['sdks']);
return $languages;
});
+15 -5
View File
@@ -26,7 +26,8 @@ $cli
->task('sdks')
->action(function () {
$platforms = Config::getParam('platforms');
$selected = \strtolower(Console::confirm('Choose SDK ("*" for all):'));
$selectedPlatform = Console::confirm('Choose Platform ("' . APP_PLATFORM_CLIENT . '", "' . APP_PLATFORM_SERVER . '", "' . APP_PLATFORM_CONSOLE . '" or "*" for all):');
$selectedSDK = \strtolower(Console::confirm('Choose SDK ("*" for all):'));
$version = Console::confirm('Choose an Appwrite version');
$git = (Console::confirm('Should we use git push? (yes/no)') == 'yes');
$production = ($git) ? (Console::confirm('Type "Appwrite" to push code to production git repos') == 'Appwrite') : false;
@@ -37,8 +38,12 @@ $cli
}
foreach ($platforms as $key => $platform) {
foreach ($platform['languages'] as $language) {
if ($selected !== $language['key'] && $selected !== '*') {
if ($selectedPlatform !== $key && $selectedPlatform !== '*') {
continue;
}
foreach ($platform['sdks'] as $language) {
if ($selectedSDK !== $language['key'] && $selectedSDK !== '*') {
continue;
}
@@ -81,8 +86,13 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
switch ($language['key']) {
case 'web':
$config = new Web();
$config->setNPMPackage('appwrite');
$config->setBowerPackage('appwrite');
if ($platform['key'] === APP_PLATFORM_CONSOLE) {
$config->setNPMPackage('@appwrite.io/console');
$config->setBowerPackage('@appwrite.io/console');
} else {
$config->setNPMPackage('appwrite');
$config->setBowerPackage('appwrite');
}
break;
case 'cli':
$config = new CLI();
Generated
+1 -1
View File
@@ -5599,5 +5599,5 @@
"platform-overrides": {
"php": "8.0"
},
"plugin-api-version": "2.2.0"
"plugin-api-version": "2.3.0"
}
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Account } from "appwrite";
import { Client, Account } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Avatars } from "appwrite";
import { Client, Avatars } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();
@@ -1,4 +1,4 @@
import { Client, Databases } from "appwrite";
import { Client, Databases } from "@appwrite.io/console";
const client = new Client();

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