From 78a057ebf91e82885cfaf797eec41a41a3300ac7 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 9 Jan 2023 22:39:58 +0530 Subject: [PATCH 01/11] feat: add code to account creation endpoint --- .env | 3 ++- app/config/variables.php | 9 +++++++++ app/controllers/api/account.php | 19 ++++++++++++------- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/.env b/.env index 51e6cc370..8de833b1e 100644 --- a/.env +++ b/.env @@ -3,6 +3,7 @@ _APP_LOCALE=en _APP_WORKER_PER_CORE=2 _APP_CONSOLE_WHITELIST_ROOT=disabled _APP_CONSOLE_WHITELIST_EMAILS= +_APP_CONSOLE_WHITELIST_CODES=mlh _APP_CONSOLE_WHITELIST_IPS= _APP_CONSOLE_INVITES=enabled _APP_SYSTEM_EMAIL_NAME=Appwrite @@ -72,4 +73,4 @@ _APP_LOGGING_PROVIDER= _APP_LOGGING_CONFIG= _APP_REGION=default _APP_DOCKER_HUB_USERNAME= -_APP_DOCKER_HUB_PASSWORD= +_APP_DOCKER_HUB_PASSWORD= \ No newline at end of file diff --git a/app/config/variables.php b/app/config/variables.php index 85c7a04f2..0b2492b07 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -88,6 +88,15 @@ return [ 'question' => '', 'filter' => '' ], + [ + 'name' => '_APP_CONSOLE_WHITELIST_CODES', + 'description' => 'This option allows you to control the creation of new users on the Appwrite console. This option is useful when you want to give access to a group of users without sharing their email addresses. To enable it, pass a list of allowed invitation codes separated by a comma.', + 'introduction' => '1.3.0', + 'default' => '', + 'required' => false, + 'question' => '', + 'filter' => '' + ], // [ // 'name' => '_APP_CONSOLE_WHITELIST_DOMAINS', // 'description' => 'This option allows you to limit creation of users to Appwrite console for users sharing the same email domains. This option is very useful for team working with company emails domain.\n\nTo enable this option, pass a list of allowed email domains separated by a comma.', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 346820d69..5501e9fbf 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -66,24 +66,29 @@ App::post('/v1/account') ->param('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) + ->param('code', '', new Text(64), 'An invite code to restrict users ', true) ->inject('request') ->inject('response') ->inject('project') ->inject('dbForProject') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { + ->action(function (string $userId, string $email, string $password, string $name, string $code, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { $email = \strtolower($email); + if ('console' === $project->getId()) { $whitelistEmails = $project->getAttribute('authWhitelistEmails'); $whitelistIPs = $project->getAttribute('authWhitelistIPs'); + $whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : []; - if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) { - throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED); - } - - if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) { - throw new Exception(Exception::USER_IP_NOT_WHITELISTED); + if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) { + if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) { + throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED); + } + + if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) { + throw new Exception(Exception::USER_IP_NOT_WHITELISTED); + } } } From b751af6db9fe23448262026bf6492556b55ee2b1 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 9 Jan 2023 22:46:02 +0530 Subject: [PATCH 02/11] feat: add code to account creation endpoint --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 5501e9fbf..798334fbd 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -66,7 +66,7 @@ App::post('/v1/account') ->param('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->param('code', '', new Text(64), 'An invite code to restrict users ', true) + ->param('code', '', new Text(64), 'An invite code to restrict user signups on the Appwrite console. Users with an invite code will be able to create accounts irrespective of email and IP whitelists.', true) ->inject('request') ->inject('response') ->inject('project') From 3f992b316e0b15cb0f25982008a881d39b4d2d1d Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Mon, 9 Jan 2023 22:52:15 +0530 Subject: [PATCH 03/11] feat: add code to account creation endpoint --- app/controllers/api/account.php | 95 ++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 798334fbd..3ed05fdab 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -44,7 +44,7 @@ use Utopia\Validator\WhiteList; $oauthDefaultSuccess = '/auth/oauth2/success'; $oauthDefaultFailure = '/auth/oauth2/failure'; -App::post('/v1/account') +App::post('/v1/account/code/:code') ->desc('Create Account') ->groups(['api', 'account', 'auth']) ->label('event', 'users.[userId].create') @@ -141,6 +141,99 @@ App::post('/v1/account') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); }); + +App::post('/v1/account') + ->desc('Create Account') + ->groups(['api', 'account', 'auth']) + ->label('event', 'users.[userId].create') + ->label('scope', 'public') + ->label('auth.type', 'emailPassword') + ->label('audits.event', 'user.create') + ->label('audits.resource', 'user/{response.$id}') + ->label('audits.userId', '{response.$id}') + ->label('usage.metric', 'users.{scope}.requests.create') + ->label('sdk.auth', []) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'create') + ->label('sdk.description', '/docs/references/account/create.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_ACCOUNT) + ->label('abuse-limit', 10) + ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. 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('name', '', new Text(128), 'User name. Max length: 128 chars.', true) + ->inject('request') + ->inject('response') + ->inject('project') + ->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'); + $whitelistIPs = $project->getAttribute('authWhitelistIPs'); + + if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) { + throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED); + } + + if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) { + throw new Exception(Exception::USER_IP_NOT_WHITELISTED); + } + } + + $limit = $project->getAttribute('auths', [])['limit'] ?? 0; + + if ($limit !== 0) { + $total = $dbForProject->count('users', max: APP_LIMIT_USERS); + + if ($total >= $limit) { + throw new Exception(Exception::USER_COUNT_EXCEEDED); + } + } + + try { + $userId = $userId == 'unique()' ? ID::unique() : $userId; + $user = Authorization::skip(fn() => $dbForProject->createDocument('users', new Document([ + '$id' => $userId, + '$permissions' => [ + Permission::read(Role::any()), + Permission::update(Role::user($userId)), + Permission::delete(Role::user($userId)), + ], + 'email' => $email, + 'emailVerification' => false, + 'status' => true, + 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), + 'hash' => Auth::DEFAULT_ALGO, + 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, + 'passwordUpdate' => DateTime::now(), + 'registration' => DateTime::now(), + 'reset' => false, + 'name' => $name, + 'prefs' => new \stdClass(), + 'sessions' => null, + 'tokens' => null, + 'memberships' => null, + 'search' => implode(' ', [$userId, $email, $name]) + ]))); + } catch (Duplicate $th) { + throw new Exception(Exception::USER_ALREADY_EXISTS); + } + + Authorization::unsetRole(Role::guests()->toString()); + Authorization::setRole(Role::user($user->getId())->toString()); + Authorization::setRole(Role::users()->toString()); + + $events->setParam('userId', $user->getId()); + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($user, Response::MODEL_ACCOUNT); + }); App::post('/v1/account/sessions/email') ->alias('/v1/account/sessions') From 59adaff541bcb06280d3f529a33ecd00ff0c43b2 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 10 Jan 2023 14:23:08 +0530 Subject: [PATCH 04/11] feat: linter fixes --- app/controllers/api/account.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3ed05fdab..43261dc17 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -85,7 +85,7 @@ App::post('/v1/account/code/:code') if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) { throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED); } - + if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) { throw new Exception(Exception::USER_IP_NOT_WHITELISTED); } @@ -141,7 +141,7 @@ App::post('/v1/account/code/:code') ->setStatusCode(Response::STATUS_CODE_CREATED) ->dynamic($user, Response::MODEL_ACCOUNT); }); - + App::post('/v1/account') ->desc('Create Account') ->groups(['api', 'account', 'auth']) From e3a21355158d1fc7da51dcdb4c6f8bcecf71ff71 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Tue, 10 Jan 2023 20:18:45 +0530 Subject: [PATCH 05/11] feat: linter fixes --- app/config/errors.php | 5 +++++ app/controllers/api/account.php | 16 ++++------------ src/Appwrite/Extend/Exception.php | 1 + 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index c5c6a489b..a071b0cb7 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -125,6 +125,11 @@ return [ 'description' => 'Console registration is restricted to specific emails. Contact your administrator for more information.', 'code' => 401, ], + Exception::USER_CODE_INVALID => [ + 'name' => Exception::USER_CODE_INVALID, + 'description' => 'The specified code is not valid. Contact your administrator for more information.', + 'code' => 401, + ], Exception::USER_IP_NOT_WHITELISTED => [ 'name' => Exception::USER_IP_NOT_WHITELISTED, 'description' => 'Console registration is restricted to specific IPs. Contact your administrator for more information.', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 43261dc17..9ec9b47ec 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -44,8 +44,8 @@ use Utopia\Validator\WhiteList; $oauthDefaultSuccess = '/auth/oauth2/success'; $oauthDefaultFailure = '/auth/oauth2/failure'; -App::post('/v1/account/code/:code') - ->desc('Create Account') +App::post('/v1/account/invite') + ->desc('Create Account using an invite code') ->groups(['api', 'account', 'auth']) ->label('event', 'users.[userId].create') ->label('scope', 'public') @@ -56,7 +56,7 @@ App::post('/v1/account/code/:code') ->label('usage.metric', 'users.{scope}.requests.create') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') - ->label('sdk.method', 'create') + ->label('sdk.method', 'createWithInviteCode') ->label('sdk.description', '/docs/references/account/create.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) @@ -77,18 +77,10 @@ App::post('/v1/account/code/:code') $email = \strtolower($email); if ('console' === $project->getId()) { - $whitelistEmails = $project->getAttribute('authWhitelistEmails'); - $whitelistIPs = $project->getAttribute('authWhitelistIPs'); $whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : []; if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) { - if (!empty($whitelistEmails) && !\in_array($email, $whitelistEmails)) { - throw new Exception(Exception::USER_EMAIL_NOT_WHITELISTED); - } - - if (!empty($whitelistIPs) && !\in_array($request->getIP(), $whitelistIPs)) { - throw new Exception(Exception::USER_IP_NOT_WHITELISTED); - } + throw new Exception(Exception::USER_CODE_INVALID); } } diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index d25cfb0d4..9f035863e 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -60,6 +60,7 @@ class Exception extends \Exception public const USER_PASSWORD_RESET_REQUIRED = 'user_password_reset_required'; public const USER_EMAIL_NOT_WHITELISTED = 'user_email_not_whitelisted'; public const USER_IP_NOT_WHITELISTED = 'user_ip_not_whitelisted'; + public const USER_CODE_INVALID = 'user_code_invalid'; public const USER_INVALID_CREDENTIALS = 'user_invalid_credentials'; public const USER_ANONYMOUS_CONSOLE_PROHIBITED = 'user_anonymous_console_prohibited'; public const USER_SESSION_ALREADY_EXISTS = 'user_session_already_exists'; From 9f9daf900ab5751ec46a02c14b19e949a50200ec Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 11 Jan 2023 17:11:33 +0530 Subject: [PATCH 06/11] feat: review comments --- app/config/variables.php | 9 --------- app/controllers/api/account.php | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/app/config/variables.php b/app/config/variables.php index 0b2492b07..85c7a04f2 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -88,15 +88,6 @@ return [ 'question' => '', 'filter' => '' ], - [ - 'name' => '_APP_CONSOLE_WHITELIST_CODES', - 'description' => 'This option allows you to control the creation of new users on the Appwrite console. This option is useful when you want to give access to a group of users without sharing their email addresses. To enable it, pass a list of allowed invitation codes separated by a comma.', - 'introduction' => '1.3.0', - 'default' => '', - 'required' => false, - 'question' => '', - 'filter' => '' - ], // [ // 'name' => '_APP_CONSOLE_WHITELIST_DOMAINS', // 'description' => 'This option allows you to limit creation of users to Appwrite console for users sharing the same email domains. This option is very useful for team working with company emails domain.\n\nTo enable this option, pass a list of allowed email domains separated by a comma.', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9ec9b47ec..33a4d53af 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -45,7 +45,7 @@ $oauthDefaultSuccess = '/auth/oauth2/success'; $oauthDefaultFailure = '/auth/oauth2/failure'; App::post('/v1/account/invite') - ->desc('Create Account using an invite code') + ->desc('Create account using an invite code') ->groups(['api', 'account', 'auth']) ->label('event', 'users.[userId].create') ->label('scope', 'public') @@ -66,7 +66,7 @@ App::post('/v1/account/invite') ->param('email', '', new Email(), 'User email.') ->param('password', '', new Password(), 'User password. Must be at least 8 chars.') ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) - ->param('code', '', new Text(64), 'An invite code to restrict user signups on the Appwrite console. Users with an invite code will be able to create accounts irrespective of email and IP whitelists.', true) + ->param('code', '', new Text(128), 'An invite code to restrict user signups on the Appwrite console. Users with an invite code will be able to create accounts irrespective of email and IP whitelists.', true) ->inject('request') ->inject('response') ->inject('project') From 2bc85e4708a533d719a0f609a231b20cb1bd1049 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 11 Jan 2023 17:12:29 +0530 Subject: [PATCH 07/11] feat: linter fixes --- app/controllers/api/account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 33a4d53af..33f260e8e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -80,7 +80,7 @@ App::post('/v1/account/invite') $whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : []; if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) { - throw new Exception(Exception::USER_CODE_INVALID); + throw new Exception(Exception::USER_CODE_INVALID); } } From d7104d9cd684bec1a9b84d0d173d92296112c81e Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 11 Jan 2023 17:54:37 +0530 Subject: [PATCH 08/11] feat: add tests --- .env | 2 +- docker-compose.yml | 1 + .../Account/AccountConsoleClientTest.php | 87 ++++++++++++++++++- 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 8de833b1e..ea666a2ac 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ _APP_LOCALE=en _APP_WORKER_PER_CORE=2 _APP_CONSOLE_WHITELIST_ROOT=disabled _APP_CONSOLE_WHITELIST_EMAILS= -_APP_CONSOLE_WHITELIST_CODES=mlh +_APP_CONSOLE_WHITELIST_CODES=code-zero,code-one _APP_CONSOLE_WHITELIST_IPS= _APP_CONSOLE_INVITES=enabled _APP_SYSTEM_EMAIL_NAME=Appwrite diff --git a/docker-compose.yml b/docker-compose.yml index a7c1ddf97..d09e5649a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -102,6 +102,7 @@ services: - _APP_LOCALE - _APP_CONSOLE_WHITELIST_ROOT - _APP_CONSOLE_WHITELIST_EMAILS + - _APP_CONSOLE_WHITELIST_CODES - _APP_CONSOLE_WHITELIST_IPS - _APP_CONSOLE_INVITES - _APP_SYSTEM_EMAIL_NAME diff --git a/tests/e2e/Services/Account/AccountConsoleClientTest.php b/tests/e2e/Services/Account/AccountConsoleClientTest.php index 4aa2462f8..c4ad09386 100644 --- a/tests/e2e/Services/Account/AccountConsoleClientTest.php +++ b/tests/e2e/Services/Account/AccountConsoleClientTest.php @@ -2,13 +2,98 @@ namespace Tests\E2E\Services\Account; +use Appwrite\Extend\Exception; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\ProjectConsole; use Tests\E2E\Scopes\SideClient; +use Utopia\Database\ID; +use Utopia\Database\DateTime; +use Tests\E2E\Client; class AccountConsoleClientTest extends Scope { - use AccountBase; + // use AccountBase; use ProjectConsole; use SideClient; + + public function testCreateAccountWithInvite(): void + { + $email = uniqid() . 'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_POST, '/account/invite', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + 'code' => 'Invalid Code' + ]); + + $this->assertEquals($response['headers']['status-code'], 401); + $this->assertEquals($response['body']['type'], Exception::USER_CODE_INVALID); + + $response = $this->client->call(Client::METHOD_POST, '/account/invite', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + ]); + + $this->assertEquals($response['headers']['status-code'], 401); + $this->assertEquals($response['body']['type'], Exception::USER_CODE_INVALID); + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_POST, '/account/invite', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + 'code' => 'code-zero' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals(true, DateTime::isValid($response['body']['registration'])); + $this->assertEquals($response['body']['email'], $email); + $this->assertEquals($response['body']['name'], $name); + + $email = uniqid() . 'user@localhost.test'; + $response = $this->client->call(Client::METHOD_POST, '/account/invite', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + 'code' => 'code-one' + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEquals(true, DateTime::isValid($response['body']['registration'])); + $this->assertEquals($response['body']['email'], $email); + $this->assertEquals($response['body']['name'], $name); + } } From 71150ced1a05e7202a4d162540c759da18dae27a Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 11 Jan 2023 18:05:07 +0530 Subject: [PATCH 09/11] feat: add tests --- app/controllers/api/account.php | 15 ++++++----- .../Account/AccountCustomClientTest.php | 27 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 33f260e8e..667dee52c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -54,7 +54,8 @@ App::post('/v1/account/invite') ->label('audits.resource', 'user/{response.$id}') ->label('audits.userId', '{response.$id}') ->label('usage.metric', 'users.{scope}.requests.create') - ->label('sdk.auth', []) + ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) + ->label('sdk.hide', true) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createWithInviteCode') ->label('sdk.description', '/docs/references/account/create.md') @@ -74,14 +75,16 @@ App::post('/v1/account/invite') ->inject('events') ->action(function (string $userId, string $email, string $password, string $name, string $code, Request $request, Response $response, Document $project, Database $dbForProject, Event $events) { + if ($project->getId() !== 'console') { + throw new Exception(Exception::GENERAL_ACCESS_FORBIDDEN); + } + $email = \strtolower($email); - if ('console' === $project->getId()) { - $whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : []; + $whitelistCodes = (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_CODES', null)) : []; - if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) { - throw new Exception(Exception::USER_CODE_INVALID); - } + if (!empty($whitelistCodes) && !\in_array($code, $whitelistCodes)) { + throw new Exception(Exception::USER_CODE_INVALID); } $limit = $project->getAttribute('auths', [])['limit'] ?? 0; diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 65567b22e..ea24d06bd 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Account; +use Appwrite\Extend\Exception; use Appwrite\SMS\Adapter\Mock; use Tests\E2E\Client; use Tests\E2E\Scopes\Scope; @@ -18,6 +19,32 @@ class AccountCustomClientTest extends Scope use ProjectCustom; use SideClient; + public function testCreateAccountWithInvite(): void + { + $email = uniqid() . 'user@localhost.test'; + $password = 'password'; + $name = 'User Name'; + + /** + * Test for FAILURE + * Make sure the invite endpoint is only accessible through the console project. + */ + $response = $this->client->call(Client::METHOD_POST, '/account/invite', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => ID::unique(), + 'email' => $email, + 'password' => $password, + 'name' => $name, + 'code' => 'Invalid Code' + ]); + + $this->assertEquals($response['headers']['status-code'], 401); + $this->assertEquals($response['body']['type'], Exception::GENERAL_ACCESS_FORBIDDEN); + } + /** * @depends testCreateAccountSession */ From a2dfd692f15abc948009ff284ca198421b34ff3d Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 11 Jan 2023 18:09:56 +0530 Subject: [PATCH 10/11] feat: add tests --- tests/e2e/Services/Account/AccountConsoleClientTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/Services/Account/AccountConsoleClientTest.php b/tests/e2e/Services/Account/AccountConsoleClientTest.php index c4ad09386..4258004ec 100644 --- a/tests/e2e/Services/Account/AccountConsoleClientTest.php +++ b/tests/e2e/Services/Account/AccountConsoleClientTest.php @@ -12,7 +12,7 @@ use Tests\E2E\Client; class AccountConsoleClientTest extends Scope { - // use AccountBase; + use AccountBase; use ProjectConsole; use SideClient; From 837bad59a448e961cedc54c221ce73fe3bb0761e Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 11 Jan 2023 18:10:49 +0530 Subject: [PATCH 11/11] feat: add tests --- app/controllers/api/account.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 667dee52c..e04b86e57 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -55,7 +55,6 @@ App::post('/v1/account/invite') ->label('audits.userId', '{response.$id}') ->label('usage.metric', 'users.{scope}.requests.create') ->label('sdk.auth', [APP_AUTH_TYPE_ADMIN]) - ->label('sdk.hide', true) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createWithInviteCode') ->label('sdk.description', '/docs/references/account/create.md')