From c10500c882f6f666786fe39bda5915e87eceb919 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 23 Feb 2021 13:29:12 +0200 Subject: [PATCH 01/33] Added an option for a console god user --- .env | 3 +++ Dockerfile | 7 ++++++ app/config/collections.php | 1 + app/config/variables.php | 10 +++++++- app/controllers/api/account.php | 14 +++++++++++ app/controllers/web/home.php | 40 ++++++++++++++++++++++++++++++-- app/tasks/doctor.php | 4 +++- app/views/home/auth/signin.phtml | 5 +++- app/views/home/auth/signup.phtml | 7 +++++- 9 files changed, 85 insertions(+), 6 deletions(-) diff --git a/.env b/.env index 581af6d978..eb1c594ed8 100644 --- a/.env +++ b/.env @@ -1,5 +1,8 @@ _APP_ENV=production _APP_ENV=development +_APP_CONSOLE_WHITELIST_GOD=enabled +_APP_CONSOLE_WHITELIST_EMAILS= +_APP_CONSOLE_WHITELIST_IPS= _APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io diff --git a/Dockerfile b/Dockerfile index c7316bd1d4..52dbeaff7b 100755 --- a/Dockerfile +++ b/Dockerfile @@ -72,6 +72,13 @@ ENV _APP_SERVER=swoole \ _APP_DOMAIN_TARGET=localhost \ _APP_HOME=https://appwrite.io \ _APP_EDITION=community \ + _APP_CONSOLE_WHITELIST_GOD=enabled \ + _APP_CONSOLE_WHITELIST_EMAILS= \ + _APP_CONSOLE_WHITELIST_IPS= \ + _APP_SYSTEM_EMAIL_NAME= \ + _APP_SYSTEM_EMAIL_ADDRESS= \ + _APP_SYSTEM_RESPONSE_FORMAT= \ + _APP_SYSTEM_SECURITY_EMAIL_ADDRESS= \ _APP_OPTIONS_ABUSE=enabled \ _APP_OPTIONS_FORCE_HTTPS=disabled \ _APP_OPENSSL_KEY_V1=your-secret-key \ diff --git a/app/config/collections.php b/app/config/collections.php index 9170b07573..ec735a278b 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -43,6 +43,7 @@ $collections = [ 'legalCity' => '', 'legalAddress' => '', 'legalTaxId' => '', + 'authWhitelistGod' => App::getEnv('_APP_CONSOLE_WHITELIST_GOD', 'enabled'), 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], 'authWhitelistDomains' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], diff --git a/app/config/variables.php b/app/config/variables.php index 722796c6f1..c8f571e1f1 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -55,9 +55,17 @@ return [ 'required' => true, 'question' => 'Enter a DNS A record hostname to serve as a CNAME for your custom domains.\nYou can use the same value as used for the Appwrite hostname.', ], + [ + 'name' => '_APP_CONSOLE_WHITELIST_GOD', + 'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registartion form. New users can be added by invting them to your project. By default this option is enabled.', + 'introduction' => '', + 'default' => 'enabled', + 'required' => false, + 'question' => '', + ], [ 'name' => '_APP_CONSOLE_WHITELIST_EMAILS', - 'description' => 'This option allows you to limit creation of users to Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.', + 'description' => 'This option allows you to limit creation of new users on the Appwrite console. This option is very useful for small teams or sole developers. To enable it, pass a list of allowed email addresses separated by a comma.', 'introduction' => '', 'default' => '', 'required' => false, diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index e8ae76312b..9c6516f91a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -58,10 +58,24 @@ App::post('/v1/account') /** @var Appwrite\Event\Event $audits */ if ('console' === $project->getId()) { + $whitlistGod = $project->getAttribute('authWhitelistGod'); $whitlistEmails = $project->getAttribute('authWhitelistEmails'); $whitlistIPs = $project->getAttribute('authWhitelistIPs'); $whitlistDomains = $project->getAttribute('authWhitelistDomains'); + if($whitlistGod !== 'disabled') { + $sum = $projectDB->getCount([ // Count users + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + ], + ]); + + if($sum !== 0) { + throw new Exception('Console registration is restricted. Contact your administrator for more information.', 401); + } + } + if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) { throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); } diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index f302f0af33..18231e4a04 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -1,5 +1,6 @@ label('permission', 'public') ->label('scope', 'home') ->inject('response') - ->action(function ($response) { + ->inject('project') + ->inject('projectDB') + ->action(function ($response, $projectDB, $project) { /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Database\Document $project */ - $response->redirect('/auth/signin'); + $response + ->addHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') + ->addHeader('Expires', 0) + ->addHeader('Pragma', 'no-cache') + ; + + if ('console' === $project->getId()) { + $whitlistGod = $project->getAttribute('authWhitelistGod'); + + if($whitlistGod !== 'disabled') { + $sum = $projectDB->getCount([ // Count users + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + ], + ]); + + if($sum !== 0) { + return $response->redirect('/auth/signin'); + } + } + } + + $response->redirect('/auth/signup'); }); App::get('/auth/signin') @@ -58,6 +86,10 @@ App::get('/auth/signin') $page = new View(__DIR__.'/../../views/home/auth/signin.phtml'); + $page + ->setParam('god', App::getEnv('_APP_CONSOLE_WHITELIST_GOD', 'enabled')) + ; + $layout ->setParam('title', 'Sign In - '.APP_NAME) ->setParam('body', $page); @@ -72,6 +104,10 @@ App::get('/auth/signup') /** @var Utopia\View $layout */ $page = new View(__DIR__.'/../../views/home/auth/signup.phtml'); + $page + ->setParam('god', App::getEnv('_APP_CONSOLE_WHITELIST_GOD', 'enabled')) + ; + $layout ->setParam('title', 'Sign Up - '.APP_NAME) ->setParam('body', $page); diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index 2918d4f1e4..a6231daca9 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -61,11 +61,13 @@ $cli Console::log('🟢 Abuse protection is enabled'); } + $authWhitelistGod = App::getEnv('_APP_CONSOLE_WHITELIST_GOD', null); $authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null); $authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null); $authWhitelistDomains = App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null); - if(empty($authWhitelistEmails) + if(empty($authWhitelistGod) + && empty($authWhitelistEmails) && empty($authWhitelistDomains) && empty($authWhitelistIPs) ) { diff --git a/app/views/home/auth/signin.phtml b/app/views/home/auth/signin.phtml index 405256a602..d89f7e41de 100644 --- a/app/views/home/auth/signin.phtml +++ b/app/views/home/auth/signin.phtml @@ -1,3 +1,6 @@ +getParam('god') !== 'disabled'); +?>
- Forgot password? or don't have an account? Sign up now + Forgot password? or don't have an account? Sign up now
diff --git a/app/views/home/auth/signup.phtml b/app/views/home/auth/signup.phtml index b5aac01436..03613746e1 100644 --- a/app/views/home/auth/signup.phtml +++ b/app/views/home/auth/signup.phtml @@ -1,3 +1,6 @@ +getParam('god') !== 'disabled'); +?>

Sign Up @@ -44,6 +47,8 @@

+
Already have an account? -
\ No newline at end of file + + \ No newline at end of file From cdf54fd19c4c87425317447dafc4c03bc2c2e8d9 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 23 Feb 2021 14:00:31 +0200 Subject: [PATCH 02/33] Disable recovery when SMTP is off --- .env | 2 +- app/controllers/web/home.php | 4 ++++ app/views/home/auth/recovery.phtml | 11 ++++++++++- app/views/home/auth/signup.phtml | 4 ++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.env b/.env index eb1c594ed8..927a863f2b 100644 --- a/.env +++ b/.env @@ -26,7 +26,7 @@ _APP_INFLUXDB_HOST=influxdb _APP_INFLUXDB_PORT=8086 _APP_STATSD_HOST=telegraf _APP_STATSD_PORT=8125 -_APP_SMTP_HOST=maildev +_APP_SMTP_HOST= _APP_SMTP_PORT=25 _APP_SMTP_SECURE= _APP_SMTP_USERNAME= diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 18231e4a04..0a26c67a83 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -123,6 +123,10 @@ App::get('/auth/recovery') $page = new View(__DIR__.'/../../views/home/auth/recovery.phtml'); + $page + ->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST')))) + ; + $layout ->setParam('title', 'Password Recovery - '.APP_NAME) ->setParam('body', $page); diff --git a/app/views/home/auth/recovery.phtml b/app/views/home/auth/recovery.phtml index 6f633b9a0c..39ac4237ff 100644 --- a/app/views/home/auth/recovery.phtml +++ b/app/views/home/auth/recovery.phtml @@ -1,3 +1,6 @@ +getParam('smtpEnabled', false); +?>

Password Recovery @@ -25,7 +28,13 @@ - + +
+ SMTP connection is disabled. Learn more +
+ + +

diff --git a/app/views/home/auth/signup.phtml b/app/views/home/auth/signup.phtml index 03613746e1..6b5d01f7e2 100644 --- a/app/views/home/auth/signup.phtml +++ b/app/views/home/auth/signup.phtml @@ -26,6 +26,10 @@ $god = ($this->getParam('god') !== 'disabled'); data-failure-param-alert-text="Registration Failed. Please try again later" data-failure-param-alert-classname="error"> + +

Please create your first god account

+ + From db898d9f9c2565bd6f5c33a9a45a1d956ed83134 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 23 Feb 2021 14:52:14 +0200 Subject: [PATCH 03/33] Fixed count method --- app/controllers/api/account.php | 5 +++-- app/views/install/compose.phtml | 1 + docker-compose.yml | 3 +++ 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9c6516f91a..6674c30cd0 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -64,12 +64,13 @@ App::post('/v1/account') $whitlistDomains = $project->getAttribute('authWhitelistDomains'); if($whitlistGod !== 'disabled') { - $sum = $projectDB->getCount([ // Count users - 'limit' => 1, + $projectDB->getCollection([ // Count users 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, ], ]); + + $sum = $projectDB->getSum(); if($sum !== 0) { throw new Exception('Console registration is restricted. Contact your administrator for more information.', 401); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 1c0c98108f..993944f26c 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -56,6 +56,7 @@ services: - influxdb environment: - _APP_ENV + - _APP_CONSOLE_WHITELIST_GOD - _APP_CONSOLE_WHITELIST_EMAILS - _APP_CONSOLE_WHITELIST_IPS - _APP_SYSTEM_EMAIL_NAME diff --git a/docker-compose.yml b/docker-compose.yml index c4ad29c643..a9b9d0d87a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -75,6 +75,9 @@ services: - influxdb environment: - _APP_ENV + - _APP_CONSOLE_WHITELIST_GOD + - _APP_CONSOLE_WHITELIST_EMAILS + - _APP_CONSOLE_WHITELIST_IPS - _APP_SYSTEM_EMAIL_NAME - _APP_SYSTEM_EMAIL_ADDRESS - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS From 3b9b751d5a1f736f9b19367e41697f3e5961e04b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 23 Feb 2021 15:03:43 +0200 Subject: [PATCH 04/33] Fixed count --- app/controllers/web/home.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 0a26c67a83..1e43634404 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -60,13 +60,14 @@ App::get('/') $whitlistGod = $project->getAttribute('authWhitelistGod'); if($whitlistGod !== 'disabled') { - $sum = $projectDB->getCount([ // Count users - 'limit' => 1, + $projectDB->getCollection([ // Count users 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, ], ]); - + + $sum = $projectDB->getSum(); + if($sum !== 0) { return $response->redirect('/auth/signin'); } From 76cb9c14feb5a0c297d641fcac47b25b8e36519d Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Tue, 23 Feb 2021 15:19:19 +0200 Subject: [PATCH 05/33] Fix for tests --- .env | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 927a863f2b..1029b7b557 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ _APP_ENV=production _APP_ENV=development -_APP_CONSOLE_WHITELIST_GOD=enabled +_APP_CONSOLE_WHITELIST_GOD=disabled _APP_CONSOLE_WHITELIST_EMAILS= _APP_CONSOLE_WHITELIST_IPS= _APP_SYSTEM_EMAIL_NAME=Appwrite @@ -26,7 +26,7 @@ _APP_INFLUXDB_HOST=influxdb _APP_INFLUXDB_PORT=8086 _APP_STATSD_HOST=telegraf _APP_STATSD_PORT=8125 -_APP_SMTP_HOST= +_APP_SMTP_HOST=maildev _APP_SMTP_PORT=25 _APP_SMTP_SECURE= _APP_SMTP_USERNAME= From 61a73eec1f2e3bd83afb885549e71d354c9ff9a4 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Fri, 23 Apr 2021 23:33:19 +0300 Subject: [PATCH 06/33] Updated vars order --- .env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env b/.env index 67aae2a267..4cc3d2ea94 100644 --- a/.env +++ b/.env @@ -1,9 +1,9 @@ _APP_ENV=production _APP_ENV=development +_APP_LOCALE=en _APP_CONSOLE_WHITELIST_GOD=disabled _APP_CONSOLE_WHITELIST_EMAILS= _APP_CONSOLE_WHITELIST_IPS= -_APP_LOCALE=en _APP_SYSTEM_EMAIL_NAME=Appwrite _APP_SYSTEM_EMAIL_ADDRESS=team@appwrite.io _APP_SYSTEM_SECURITY_EMAIL_ADDRESS=security@appwrite.io From eaac52b9b71e9b20c8ebc54794e28da6218c0f86 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Wed, 5 May 2021 00:22:46 +0530 Subject: [PATCH 07/33] feat: allow external users to update password without oldPassword --- app/controllers/api/account.php | 10 +-- app/controllers/api/teams.php | 16 ++++- src/Appwrite/Utopia/Response/Model/User.php | 6 ++ tests/e2e/Services/Account/AccountBase.php | 27 +++++++ .../Account/AccountCustomClientTest.php | 41 ++++++++++- tests/e2e/Services/Teams/TeamsBaseClient.php | 70 +++++++++++++++++-- 6 files changed, 156 insertions(+), 14 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2cf9770fc8..56cf2ab923 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -514,7 +514,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') 'emailVerification' => true, 'status' => Auth::USER_STATUS_ACTIVATED, // Email should already be authenticated by OAuth2 provider 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'passwordUpdate' => \time(), + 'passwordUpdate' => 0, 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -682,7 +682,7 @@ App::post('/v1/account/sessions/anonymous') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => null, - 'passwordUpdate' => \time(), + 'passwordUpdate' => 0, 'registration' => \time(), 'reset' => false, 'name' => null @@ -1012,7 +1012,7 @@ App::patch('/v1/account/password') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('password', '', new Password(), 'New user password. Must be between 6 to 32 chars.') - ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.') + ->param('oldPassword', '', new Password(), 'Old user password. Must be between 6 to 32 chars.', true) ->inject('response') ->inject('user') ->inject('projectDB') @@ -1023,12 +1023,14 @@ App::patch('/v1/account/password') /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ - if (!Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password + // Check old password only if its an existing user. + if ($user->getAttribute('passwordUpdate') !== 0 && !Auth::passwordVerify($oldPassword, $user->getAttribute('password'))) { // Double check user password throw new Exception('Invalid credentials', 401); } $user = $projectDB->updateDocument(\array_merge($user->getArrayCopy(), [ 'password' => Auth::passwordHash($password), + 'passwordUpdate' => \time(), ])); if (false === $user) { diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 910427da14..49aaf3a005 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -341,7 +341,17 @@ App::post('/v1/teams/:teamId/memberships') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash(Auth::passwordGenerator()), - 'passwordUpdate' => \time(), + /** + * Set the password update time to 0 for users created + * with a team invite. This will allow us to distinguish b/w + * new and old appwrite users. + * if (passwordUpdate == 0) { + * // new appwrite user created with oauth/anonymous/team invite + * } else { + * // existing appwrite user + * } + */ + 'passwordUpdate' => 0, 'registration' => \time(), 'reset' => false, 'name' => $name, @@ -580,7 +590,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') } if ($userId != $membership->getAttribute('userId')) { - throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); + throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401); } if (empty($user->getId())) { @@ -594,7 +604,7 @@ App::patch('/v1/teams/:teamId/memberships/:inviteId/status') } if ($membership->getAttribute('userId') !== $user->getId()) { - throw new Exception('Invite not belong to current user ('.$user->getAttribute('email').')', 401); + throw new Exception('Invite does not belong to current user ('.$user->getAttribute('email').')', 401); } $membership // Attach user to team diff --git a/src/Appwrite/Utopia/Response/Model/User.php b/src/Appwrite/Utopia/Response/Model/User.php index 2df8bf7432..00353724cd 100644 --- a/src/Appwrite/Utopia/Response/Model/User.php +++ b/src/Appwrite/Utopia/Response/Model/User.php @@ -34,6 +34,12 @@ class User extends Model 'default' => 0, 'example' => 0, ]) + ->addRule('passwordUpdate', [ + 'type' => self::TYPE_INTEGER, + 'description' => 'Unix timestamp of the most recent password update', + 'default' => 0, + 'example' => 1592981250, + ]) ->addRule('email', [ 'type' => self::TYPE_STRING, 'description' => 'User email address.', diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 38d985ab06..46b9f127d0 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -509,6 +509,33 @@ trait AccountBase $this->assertEquals($response['headers']['status-code'], 400); + /** + * Existing user tries to update password by passing wrong old password -> SHOULD FAIL + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password', + 'oldPassword' => $password, + ]); + $this->assertEquals($response['headers']['status-code'], 401); + + /** + * Existing user tries to update password without passing old password -> SHOULD FAIL + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password' + ]); + $this->assertEquals($response['headers']['status-code'], 401); + $data['password'] = 'new-password'; return $data; diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index bc274996f9..ddad5d23fb 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -264,7 +264,7 @@ class AccountCustomClientTest extends Scope public function testUpdateAnonymousAccountPassword($session) { /** - * Test for FAILURE + * Anonymous account updates password without old password -> SHOULD PASS */ $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ 'origin' => 'http://localhost', @@ -276,7 +276,44 @@ class AccountCustomClientTest extends Scope 'oldPassword' => '', ]); - $this->assertEquals($response['headers']['status-code'], 400); + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertIsArray($response['body']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertIsNumeric($response['body']['registration']); + + /** + * Anonymous account tries to update password again without old password -> SHOULD FAIL + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password' + ]); + + $this->assertEquals($response['headers']['status-code'], 401); + + /** + * Anonymous account updates password with new and old password -> SHOULD PASS + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'newer-password', + 'oldPassword' => 'new-password', + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertIsArray($response['body']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertIsNumeric($response['body']['registration']); return $session; } diff --git a/tests/e2e/Services/Teams/TeamsBaseClient.php b/tests/e2e/Services/Teams/TeamsBaseClient.php index 72fc99d881..042a95c428 100644 --- a/tests/e2e/Services/Teams/TeamsBaseClient.php +++ b/tests/e2e/Services/Teams/TeamsBaseClient.php @@ -43,6 +43,7 @@ trait TeamsBaseClient $teamUid = $data['teamUid'] ?? ''; $teamName = $data['teamName'] ?? ''; $email = uniqid().'friend@localhost.test'; + $name = 'Friend User'; /** * Test for SUCCESS @@ -52,7 +53,7 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'email' => $email, - 'name' => 'Friend User', + 'name' => $name, 'roles' => ['admin', 'editor'], 'url' => 'http://localhost:5000/join-us#title' ]); @@ -68,7 +69,7 @@ trait TeamsBaseClient $lastEmail = $this->getLastEmail(); $this->assertEquals($email, $lastEmail['to'][0]['address']); - $this->assertEquals('Friend User', $lastEmail['to'][0]['name']); + $this->assertEquals($name, $lastEmail['to'][0]['name']); $this->assertEquals('Invitation to '.$teamName.' Team at '.$this->getProject()['name'], $lastEmail['subject']); $secret = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); @@ -83,7 +84,7 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'email' => 'dasdkaskdjaskdjasjkd', - 'name' => 'Friend User', + 'name' => $name, 'roles' => ['admin', 'editor'], 'url' => 'http://localhost:5000/join-us#title' ]); @@ -95,7 +96,7 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'email' => $email, - 'name' => 'Friend User', + 'name' => $name, 'roles' => 'bad string', 'url' => 'http://localhost:5000/join-us#title' ]); @@ -107,7 +108,7 @@ trait TeamsBaseClient 'x-appwrite-project' => $this->getProject()['$id'], ], $this->getHeaders()), [ 'email' => $email, - 'name' => 'Friend User', + 'name' => $name, 'roles' => ['admin', 'editor'], 'url' => 'http://example.com/join-us#title' // bad url ]); @@ -119,6 +120,8 @@ trait TeamsBaseClient 'secret' => $secret, 'inviteUid' => $inviteUid, 'userUid' => $userUid, + 'email' => $email, + 'name' => $name ]; } @@ -131,6 +134,8 @@ trait TeamsBaseClient $secret = $data['secret'] ?? ''; $inviteUid = $data['inviteUid'] ?? ''; $userUid = $data['userUid'] ?? ''; + $email = $data['email'] ?? ''; + $name = $data['name'] ?? ''; /** * Test for SUCCESS @@ -151,6 +156,61 @@ trait TeamsBaseClient $this->assertCount(2, $response['body']['roles']); $this->assertIsInt($response['body']['joined']); $this->assertEquals(true, $response['body']['confirm']); + $session = $this->client->parseCookie((string)$response['headers']['set-cookie'])['a_session_'.$this->getProject()['$id']]; + + /** + * New User tries to update password without old password -> SHOULD PASS + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password' + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertIsArray($response['body']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertIsNumeric($response['body']['registration']); + $this->assertEquals($response['body']['email'], $email); + $this->assertEquals($response['body']['name'], $name); + + /** + * New User again tries to update password with ONLY new password -> SHOULD FAIL + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'new-password', + ]); + $this->assertEquals(401, $response['headers']['status-code']); + + /** + * New User tries to update password by passing both old and new password -> SHOULD PASS + */ + $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, + ]), [ + 'password' => 'newer-password', + 'oldPassword' => 'new-password' + ]); + + $this->assertEquals($response['headers']['status-code'], 200); + $this->assertIsArray($response['body']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertIsNumeric($response['body']['registration']); + $this->assertEquals($response['body']['email'], $email); + $this->assertEquals($response['body']['name'], $name); /** * Test for FAILURE From 80328f1dc73f0cae808b86fbb2dce9c9f8156f03 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 6 May 2021 18:51:36 +0530 Subject: [PATCH 08/33] feat: review comments --- app/controllers/api/account.php | 2 +- .../Account/AccountCustomClientTest.php | 42 +------------------ 2 files changed, 3 insertions(+), 41 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 56cf2ab923..8857da774d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -682,7 +682,7 @@ App::post('/v1/account/sessions/anonymous') 'emailVerification' => false, 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => null, - 'passwordUpdate' => 0, + 'passwordUpdate' => \time(), 'registration' => \time(), 'reset' => false, 'name' => null diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index ddad5d23fb..6c4de08fde 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -264,7 +264,7 @@ class AccountCustomClientTest extends Scope public function testUpdateAnonymousAccountPassword($session) { /** - * Anonymous account updates password without old password -> SHOULD PASS + * Test for FAILURE */ $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ 'origin' => 'http://localhost', @@ -272,48 +272,10 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, ]), [ - 'password' => 'new-password', 'oldPassword' => '', ]); - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertIsArray($response['body']); - $this->assertNotEmpty($response['body']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertIsNumeric($response['body']['registration']); - - /** - * Anonymous account tries to update password again without old password -> SHOULD FAIL - */ - $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'password' => 'new-password' - ]); - - $this->assertEquals($response['headers']['status-code'], 401); - - /** - * Anonymous account updates password with new and old password -> SHOULD PASS - */ - $response = $this->client->call(Client::METHOD_PATCH, '/account/password', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'cookie' => 'a_session_'.$this->getProject()['$id'].'=' . $session, - ]), [ - 'password' => 'newer-password', - 'oldPassword' => 'new-password', - ]); - - $this->assertEquals($response['headers']['status-code'], 200); - $this->assertIsArray($response['body']); - $this->assertNotEmpty($response['body']); - $this->assertNotEmpty($response['body']['$id']); - $this->assertIsNumeric($response['body']['registration']); + $this->assertEquals(400, $response['headers']['status-code']); return $session; } From e8172f72e373bc17ae6c9bda14c7d456b5cdbb50 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 6 May 2021 22:00:44 +0530 Subject: [PATCH 09/33] feat: review comments --- app/controllers/api/teams.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 49aaf3a005..20a4d197ff 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -342,14 +342,9 @@ App::post('/v1/teams/:teamId/memberships') 'status' => Auth::USER_STATUS_UNACTIVATED, 'password' => Auth::passwordHash(Auth::passwordGenerator()), /** - * Set the password update time to 0 for users created - * with a team invite. This will allow us to distinguish b/w - * new and old appwrite users. - * if (passwordUpdate == 0) { - * // new appwrite user created with oauth/anonymous/team invite - * } else { - * // existing appwrite user - * } + * Set the password update time to 0 for users created using + * team Invite and OAuth to allow password updates without an + * old password */ 'passwordUpdate' => 0, 'registration' => \time(), From d99f0cbc5cb93dcc27515bcef4e0858b7aaefd35 Mon Sep 17 00:00:00 2001 From: Christy Jacob Date: Thu, 6 May 2021 22:00:57 +0530 Subject: [PATCH 10/33] feat: review comments --- app/controllers/api/teams.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 20a4d197ff..1ec806eb8d 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -343,7 +343,7 @@ App::post('/v1/teams/:teamId/memberships') 'password' => Auth::passwordHash(Auth::passwordGenerator()), /** * Set the password update time to 0 for users created using - * team Invite and OAuth to allow password updates without an + * team invite and OAuth to allow password updates without an * old password */ 'passwordUpdate' => 0, From a0d30aef8cf4f05163712eb50c0ca2b42689f5c0 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 7 May 2021 09:06:36 +0200 Subject: [PATCH 11/33] chore(vars): fix typo on god account description --- app/config/variables.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config/variables.php b/app/config/variables.php index ee8818c6b3..4b27e88aa2 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -65,7 +65,7 @@ return [ ], [ 'name' => '_APP_CONSOLE_WHITELIST_GOD', - 'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registartion form. New users can be added by invting them to your project. By default this option is enabled.', + 'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by invting them to your project. By default this option is enabled.', 'introduction' => '', 'default' => 'enabled', 'required' => false, From 0c3bcb40a4061d99d5f6430e9a905bbdd02dacfc Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 10 May 2021 14:33:27 +0545 Subject: [PATCH 12/33] poc certificate auto generation --- app/http.php | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/app/http.php b/app/http.php index d144c2e54a..0e88757ab3 100644 --- a/app/http.php +++ b/app/http.php @@ -12,6 +12,7 @@ use Swoole\Http\Request as SwooleRequest; use Swoole\Http\Response as SwooleResponse; use Utopia\App; use Utopia\CLI\Console; +use Utopia\Config\Config; // xdebug_start_trace('/tmp/trace'); @@ -65,22 +66,35 @@ Files::load(__DIR__ . '/../public'); include __DIR__ . '/controllers/general.php'; -$domain = App::getEnv('_APP_DOMAIN', ''); +function certificateSetup($domain) +{ + $domains = Config::getParam('domains', []); + if (!in_array($domain, $domains)) { + //schedule + Console::info('adding ' . $domain . ' to list of domains already checked'); + array_push($domains, $domain); + Config::setParam('domains', $domains); -Console::info('Issuing a TLS certificate for the master domain ('.$domain.') in 30 seconds. - Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script + Console::info('Issuing a TLS certificate for the master domain (' . $domain . ') in 30 seconds. + Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script -ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], - 'domain' => $domain, - 'validateTarget' => false, - 'validateCNAME' => false, -]); + ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ + 'document' => [], + 'domain' => $domain, + 'validateTarget' => false, + 'validateCNAME' => false, + ]); + } + +} $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { $request = new Request($swooleRequest); $response = new Response($swooleResponse); + $domain = $request->getHostname(); + certificateSetup($domain); + if(Files::isFileLoaded($request->getURI())) { $time = (60 * 60 * 24 * 365 * 2); // 45 days cache From 75d8076ac95477208dd5ade1231f8bbdd4b9144d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 10 May 2021 16:23:05 +0545 Subject: [PATCH 13/33] skip certificate generation when localhost or hostname is IP --- app/http.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/http.php b/app/http.php index 0e88757ab3..d71db4a6a3 100644 --- a/app/http.php +++ b/app/http.php @@ -68,6 +68,7 @@ include __DIR__ . '/controllers/general.php'; function certificateSetup($domain) { + if($domain=='localhost' || (bool)ip2long($domain)) return; $domains = Config::getParam('domains', []); if (!in_array($domain, $domains)) { //schedule From c22dd1571f9b9d0f47b6cd83f9f26f686b6e210b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 11 May 2021 11:41:11 +0545 Subject: [PATCH 14/33] implementing review suggestions --- app/http.php | 54 +++++++++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/app/http.php b/app/http.php index d71db4a6a3..d38d33b7c8 100644 --- a/app/http.php +++ b/app/http.php @@ -13,6 +13,7 @@ use Swoole\Http\Response as SwooleResponse; use Utopia\App; use Utopia\CLI\Console; use Utopia\Config\Config; +use Utopia\Domains\Domain; // xdebug_start_trace('/tmp/trace'); @@ -66,35 +67,40 @@ Files::load(__DIR__ . '/../public'); include __DIR__ . '/controllers/general.php'; -function certificateSetup($domain) -{ - if($domain=='localhost' || (bool)ip2long($domain)) return; - $domains = Config::getParam('domains', []); - if (!in_array($domain, $domains)) { - //schedule - Console::info('adding ' . $domain . ' to list of domains already checked'); - array_push($domains, $domain); - Config::setParam('domains', $domains); - - Console::info('Issuing a TLS certificate for the master domain (' . $domain . ') in 30 seconds. - Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script - - ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], - 'domain' => $domain, - 'validateTarget' => false, - 'validateCNAME' => false, - ]); - } - -} - $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) { $request = new Request($swooleRequest); $response = new Response($swooleResponse); $domain = $request->getHostname(); - certificateSetup($domain); + $validDomains = Config::getParam('validDomains', []); + if (!array_key_exists($domain, $validDomains)) { + $domainCheck = new Domain(!empty($domain) ? $domain : ''); + if (empty($domainCheck->get()) || !$domainCheck->isKnown() || $domainCheck->isTest()()) { + $validDomains[$domain] = false; + } else { + $validDomains[$domain] = true; + } + Config::setParam('validDomains', $validDomains); + } + if ($validDomains[$domain]) { + $issuedDomains = Config::getParam('issuedDomains', []); + if (!array_key_exists($domain, $issuedDomains)) { + //schedule + Console::info('adding ' . $domain . ' to list of domains already checked'); + $issuedDomains[$domain] = true; + Config::setParam('issuedDomains', $issuedDomains); + + Console::info('Issuing a TLS certificate for the master domain (' . $domain . ') in 30 seconds. + Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script + + ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ + 'document' => [], + 'domain' => $domain, + 'validateTarget' => false, + 'validateCNAME' => false, + ]); + } + } if(Files::isFileLoaded($request->getURI())) { $time = (60 * 60 * 24 * 365 * 2); // 45 days cache From 35b76c1aee5f3fe3b87e9fa092e9e935bc3f14ae Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 11 May 2021 16:32:02 +0545 Subject: [PATCH 15/33] refactoring moving ssl generation to init function --- app/controllers/general.php | 54 +++++++++++++++++++++++++++++++++++-- app/http.php | 31 --------------------- 2 files changed, 52 insertions(+), 33 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 1f7d4ebaeb..9b0a6e7fde 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -23,15 +23,65 @@ Config::setParam('domainVerification', false); Config::setParam('cookieDomain', 'localhost'); Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE); -App::init(function ($utopia, $request, $response, $console, $project, $user, $locale, $clients) { +App::init(function ($utopia, $request, $response, $console, $project, $consoleDB, $user, $locale, $clients) { /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $consoleDB */ /** @var Appwrite\Database\Document $console */ /** @var Appwrite\Database\Document $project */ /** @var Appwrite\Database\Document $user */ /** @var Utopia\Locale\Locale $locale */ /** @var bool $mode */ /** @var array $clients */ + + $domain = $request->getHostname(); + $checkedDomains = Config::getParam('checkedDomains', []); + if (!array_key_exists($domain, $checkedDomains)) { + $domain = new Domain(!empty($domain) ? $domain : ''); + + if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()()) { + $checkedDomains[$domain->get()] = false; + Console::info($domain->get() . ' is not a valid domain. Skipping certificate generation.'); + } else { + Console::info($domain->get() . ' is a valid domain.'); + + $dbDomain = $consoleDB->getCollectionFirst([ + 'limit' => 1, + 'offset' => 0, + 'filters' => [ + '$collection=' . Database::SYSTEM_COLLECTION_CERTIFICATES, + 'domain=' . $domain->get(), + ], + ]); + + if (empty($dbDomain)) { + $dbDomain = [ + '$collection' => Database::SYSTEM_COLLECTION_CERTIFICATES, + '$permissions' => [ + 'read' => [], + 'write' => [], + ], + 'domain' => $domain->get(), + ]; + $dbDomain = $consoleDB->createDocument($dbDomain); + + Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in 30 seconds. + Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script + + ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ + 'document' => [], + 'domain' => $domain->get(), + 'validateTarget' => false, + 'validateCNAME' => false, + ]); + } + + $checkedDomains[$domain] = true; + + } + Console::info('adding ' . $domain->get() . ' to list of domains already checked'); + Config::setParam('checkedDomains', $checkedDomains); + } $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); @@ -226,7 +276,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $user, $lo throw new Exception('Password reset is required', 412); } -}, ['utopia', 'request', 'response', 'console', 'project', 'user', 'locale', 'clients']); +}, ['utopia', 'request', 'response', 'console', 'project', 'consoleDB', 'user', 'locale', 'clients']); App::options(function ($request, $response) { /** @var Utopia\Swoole\Request $request */ diff --git a/app/http.php b/app/http.php index d38d33b7c8..efa47ffbd1 100644 --- a/app/http.php +++ b/app/http.php @@ -71,37 +71,6 @@ $http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swo $request = new Request($swooleRequest); $response = new Response($swooleResponse); - $domain = $request->getHostname(); - $validDomains = Config::getParam('validDomains', []); - if (!array_key_exists($domain, $validDomains)) { - $domainCheck = new Domain(!empty($domain) ? $domain : ''); - if (empty($domainCheck->get()) || !$domainCheck->isKnown() || $domainCheck->isTest()()) { - $validDomains[$domain] = false; - } else { - $validDomains[$domain] = true; - } - Config::setParam('validDomains', $validDomains); - } - if ($validDomains[$domain]) { - $issuedDomains = Config::getParam('issuedDomains', []); - if (!array_key_exists($domain, $issuedDomains)) { - //schedule - Console::info('adding ' . $domain . ' to list of domains already checked'); - $issuedDomains[$domain] = true; - Config::setParam('issuedDomains', $issuedDomains); - - Console::info('Issuing a TLS certificate for the master domain (' . $domain . ') in 30 seconds. - Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script - - ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], - 'domain' => $domain, - 'validateTarget' => false, - 'validateCNAME' => false, - ]); - } - } - if(Files::isFileLoaded($request->getURI())) { $time = (60 * 60 * 24 * 365 * 2); // 45 days cache From 4170eaa45a729829d59f630a9cb2ce8d46ea8b83 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 11 May 2021 17:00:26 +0545 Subject: [PATCH 16/33] fix error --- app/controllers/general.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 9b0a6e7fde..a7df8426fb 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -39,7 +39,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB if (!array_key_exists($domain, $checkedDomains)) { $domain = new Domain(!empty($domain) ? $domain : ''); - if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()()) { + if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { $checkedDomains[$domain->get()] = false; Console::info($domain->get() . ' is not a valid domain. Skipping certificate generation.'); } else { From dc92498d93e77c1f236ff2b86944ceb94337e1e1 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 11 May 2021 17:06:55 +0545 Subject: [PATCH 17/33] disable auth for writing --- app/controllers/general.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/controllers/general.php b/app/controllers/general.php index a7df8426fb..559e3baab3 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -63,7 +63,9 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB ], 'domain' => $domain->get(), ]; + Authorization::disable(); $dbDomain = $consoleDB->createDocument($dbDomain); + Authorization::enable(); Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in 30 seconds. Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script From dc52c6975a8fbd4ce43a7eea2f56d651ca9eba6e Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 11 May 2021 17:09:23 +0545 Subject: [PATCH 18/33] fix error --- app/controllers/general.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 559e3baab3..9323d0a219 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -78,7 +78,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB ]); } - $checkedDomains[$domain] = true; + $checkedDomains[$domain->get()] = true; } Console::info('adding ' . $domain->get() . ' to list of domains already checked'); From f83ad43187e4634e5d50c14110114122a78d1ca8 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 11 May 2021 17:21:27 +0545 Subject: [PATCH 19/33] fix auth issue --- app/controllers/general.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 9323d0a219..6ceca25e0f 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -44,7 +44,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB Console::info($domain->get() . ' is not a valid domain. Skipping certificate generation.'); } else { Console::info($domain->get() . ' is a valid domain.'); - + Authorization::disable(); $dbDomain = $consoleDB->getCollectionFirst([ 'limit' => 1, 'offset' => 0, @@ -63,7 +63,6 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB ], 'domain' => $domain->get(), ]; - Authorization::disable(); $dbDomain = $consoleDB->createDocument($dbDomain); Authorization::enable(); From be5b882fe74071b3759021a91da1b685c9f0af32 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 11:02:34 +0545 Subject: [PATCH 20/33] Apply suggestions from code review Co-authored-by: Eldad A. Fux --- app/controllers/general.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 6ceca25e0f..695ac93d17 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -41,9 +41,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { $checkedDomains[$domain->get()] = false; - Console::info($domain->get() . ' is not a valid domain. Skipping certificate generation.'); + Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.'); } else { - Console::info($domain->get() . ' is a valid domain.'); Authorization::disable(); $dbDomain = $consoleDB->getCollectionFirst([ 'limit' => 1, @@ -66,8 +65,8 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $dbDomain = $consoleDB->createDocument($dbDomain); Authorization::enable(); - Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in 30 seconds. - Make sure your domain points to your server IP or restart your Appwrite server to try again.'); // TODO move this to installation script + Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in ~30 seconds. +.'); // TODO move this to installation script ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ 'document' => [], @@ -475,4 +474,4 @@ include_once __DIR__ . '/shared/web.php'; foreach (Config::getParam('services', []) as $service) { include_once $service['controller']; -} \ No newline at end of file +} From 0e90c75a1226ba3c554abf38f9100bab4ccc329c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 11:04:48 +0545 Subject: [PATCH 21/33] refactor based on review --- app/controllers/general.php | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 695ac93d17..8f1f75be81 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -14,8 +14,6 @@ use Appwrite\Database\Database; use Appwrite\Database\Document; use Appwrite\Database\Validator\Authorization; use Appwrite\Network\Validator\Origin; -use Utopia\Storage\Device\Local; -use Utopia\Storage\Storage; use Appwrite\Utopia\Response\Filters\V06; use Utopia\CLI\Console; @@ -35,12 +33,12 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB /** @var array $clients */ $domain = $request->getHostname(); - $checkedDomains = Config::getParam('checkedDomains', []); - if (!array_key_exists($domain, $checkedDomains)) { + $domains = Config::getParam('domains', []); + if (!array_key_exists($domain, $domains)) { $domain = new Domain(!empty($domain) ? $domain : ''); if (empty($domain->get()) || !$domain->isKnown() || $domain->isTest()) { - $checkedDomains[$domain->get()] = false; + $domains[$domain->get()] = false; Console::warning($domain->get() . ' is not a publicly accessible domain. Skipping SSL certificate generation.'); } else { Authorization::disable(); @@ -65,22 +63,21 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $dbDomain = $consoleDB->createDocument($dbDomain); Authorization::enable(); - Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in ~30 seconds. -.'); // TODO move this to installation script + Console::info('Issuing a TLS certificate for the master domain (' . $domain->get() . ') in ~30 seconds..'); // TODO move this to installation script ResqueScheduler::enqueueAt(\time() + 30, 'v1-certificates', 'CertificatesV1', [ - 'document' => [], + 'document' => $dbDomain, 'domain' => $domain->get(), 'validateTarget' => false, 'validateCNAME' => false, ]); } - $checkedDomains[$domain->get()] = true; + $domains[$domain->get()] = true; } Console::info('adding ' . $domain->get() . ' to list of domains already checked'); - Config::setParam('checkedDomains', $checkedDomains); + Config::setParam('domains', $domains); } $localeParam = (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')); From 3330841b9e0c95cf7d25e07cdaa2d6a4a20f8c0a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 12:57:08 +0545 Subject: [PATCH 22/33] changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b8867b0e53..01e32bb79e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ # Version 0.8.0 (Not Released Yet) ## Features - +- Refactoring SSL generation to work on every request so no domain environment variable is required for SSL generation (#1133) - Added Anonymous Login ([RFC-010](https://github.com/appwrite/rfc/blob/main/010-anonymous-login.md), #914) - Added events for functions and executions (#971) - Added JWT support (#784) From 7a75c3558f7e67970d2c2e94e324671cff4c92e6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 16:10:57 +0545 Subject: [PATCH 23/33] remove log --- app/controllers/general.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 8f1f75be81..98db96a58a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -76,7 +76,6 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB $domains[$domain->get()] = true; } - Console::info('adding ' . $domain->get() . ' to list of domains already checked'); Config::setParam('domains', $domains); } From e2000dbdf81e1639ab1f4e7d0bd8a3f2d0f90b53 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 16:42:13 +0545 Subject: [PATCH 24/33] remove extra white space --- app/controllers/general.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/controllers/general.php b/app/controllers/general.php index 98db96a58a..9d360f8292 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -72,9 +72,7 @@ App::init(function ($utopia, $request, $response, $console, $project, $consoleDB 'validateCNAME' => false, ]); } - $domains[$domain->get()] = true; - } Config::setParam('domains', $domains); } From bd73b3aa580354f33cf2852684792d571016ffae Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 16:58:31 +0545 Subject: [PATCH 25/33] discord link in the footer --- app/views/console/comps/footer.phtml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/views/console/comps/footer.phtml b/app/views/console/comps/footer.phtml index be5696ce48..923304a9f4 100644 --- a/app/views/console/comps/footer.phtml +++ b/app/views/console/comps/footer.phtml @@ -12,6 +12,14 @@ $version = $this->getParam('version', '').'.'.APP_CACHE_BUSTER; data-analytics-label="GitHub Link" href="https://github.com/appwrite/appwrite" target="_blank" rel="noopener"> GitHub +
  • + Discord +
  • Date: Wed, 12 May 2021 14:35:46 +0300 Subject: [PATCH 26/33] Leverage auth limit for god feature --- app/config/collections.php | 3 +-- app/config/variables.php | 2 +- app/controllers/api/account.php | 20 -------------------- app/tasks/doctor.php | 2 -- 4 files changed, 2 insertions(+), 25 deletions(-) diff --git a/app/config/collections.php b/app/config/collections.php index f616985389..15a6e0abe4 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -44,10 +44,9 @@ $collections = [ 'legalCity' => '', 'legalAddress' => '', 'legalTaxId' => '', - 'authWhitelistGod' => App::getEnv('_APP_CONSOLE_WHITELIST_GOD', 'enabled'), 'authWhitelistEmails' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null)) : [], 'authWhitelistIPs' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null)) : [], - 'authWhitelistDomains' => (!empty(App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null))) ? \explode(',', App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null)) : [], + 'usersAuthLimit' => (App::getEnv('_APP_CONSOLE_WHITELIST_GOD', 'enabled') === 'enabled') ? 1 : 0, // limit signup to 1 user ], Database::SYSTEM_COLLECTION_COLLECTIONS => [ '$collection' => Database::SYSTEM_COLLECTION_COLLECTIONS, diff --git a/app/config/variables.php b/app/config/variables.php index becafcd1ee..11bf59af24 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -66,7 +66,7 @@ return [ [ 'name' => '_APP_CONSOLE_WHITELIST_GOD', 'description' => 'This option allows you to disable the creation of new users on the Appwrite console. When enabled only 1 user will be able to use the registration form. New users can be added by invting them to your project. By default this option is enabled.', - 'introduction' => '', + 'introduction' => '0.8.0', 'default' => 'enabled', 'required' => false, 'question' => '', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 1773848765..294ae817bc 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -59,24 +59,8 @@ App::post('/v1/account') /** @var Appwrite\Event\Event $audits */ if ('console' === $project->getId()) { - $whitlistGod = $project->getAttribute('authWhitelistGod'); $whitlistEmails = $project->getAttribute('authWhitelistEmails'); $whitlistIPs = $project->getAttribute('authWhitelistIPs'); - $whitlistDomains = $project->getAttribute('authWhitelistDomains'); - - if($whitlistGod !== 'disabled') { - $projectDB->getCollection([ // Count users - 'filters' => [ - '$collection='.Database::SYSTEM_COLLECTION_USERS, - ], - ]); - - $sum = $projectDB->getSum(); - - if($sum !== 0) { - throw new Exception('Console registration is restricted. Contact your administrator for more information.', 401); - } - } if (!empty($whitlistEmails) && !\in_array($email, $whitlistEmails)) { throw new Exception('Console registration is restricted to specific emails. Contact your administrator for more information.', 401); @@ -85,10 +69,6 @@ App::post('/v1/account') if (!empty($whitlistIPs) && !\in_array($request->getIP(), $whitlistIPs)) { throw new Exception('Console registration is restricted to specific IPs. Contact your administrator for more information.', 401); } - - if (!empty($whitlistDomains) && !\in_array(\substr(\strrchr($email, '@'), 1), $whitlistDomains)) { - throw new Exception('Console registration is restricted to specific domains. Contact your administrator for more information.', 401); - } } $limit = $project->getAttribute('usersAuthLimit', 0); diff --git a/app/tasks/doctor.php b/app/tasks/doctor.php index 20f4e01bc4..5feb960479 100644 --- a/app/tasks/doctor.php +++ b/app/tasks/doctor.php @@ -64,11 +64,9 @@ $cli $authWhitelistGod = App::getEnv('_APP_CONSOLE_WHITELIST_GOD', null); $authWhitelistEmails = App::getEnv('_APP_CONSOLE_WHITELIST_EMAILS', null); $authWhitelistIPs = App::getEnv('_APP_CONSOLE_WHITELIST_IPS', null); - $authWhitelistDomains = App::getEnv('_APP_CONSOLE_WHITELIST_DOMAINS', null); if(empty($authWhitelistGod) && empty($authWhitelistEmails) - && empty($authWhitelistDomains) && empty($authWhitelistIPs) ) { Console::log('🔴 Console access limits are disabled'); From d94771471c0a47df3853620306e55ec80e4f9795 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Wed, 12 May 2021 17:40:16 +0545 Subject: [PATCH 27/33] format update for travis reboot --- app/views/console/comps/footer.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/console/comps/footer.phtml b/app/views/console/comps/footer.phtml index 923304a9f4..065dabb14b 100644 --- a/app/views/console/comps/footer.phtml +++ b/app/views/console/comps/footer.phtml @@ -1,6 +1,6 @@ getParam('home', ''); -$version = $this->getParam('version', '').'.'.APP_CACHE_BUSTER; +$version = $this->getParam('version', '') . '.' . APP_CACHE_BUSTER; ?>