From 25f176875dfc8b61a2c06065e6028587d18ce896 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 16:43:41 +0300 Subject: [PATCH 01/18] Changed provider name from phone to sms --- .env | 4 +-- CHANGES.md | 2 +- Dockerfile | 4 +-- app/config/errors.php | 2 +- app/config/variables.php | 6 ++--- app/controllers/api/account.php | 23 ++++++++-------- app/init.php | 16 ++++++------ app/views/install/compose.phtml | 8 +++--- app/workers/messaging.php | 26 +++++++++---------- docker-compose.yml | 8 +++--- .../{Auth/Phone => SMS/Adapter}/Mock.php | 6 ++--- .../{Auth/Phone => SMS/Adapter}/Msg91.php | 14 +++++----- .../{Auth/Phone => SMS/Adapter}/Telesign.php | 6 ++--- .../{Auth/Phone => SMS/Adapter}/TextMagic.php | 6 ++--- .../{Auth/Phone => SMS/Adapter}/Twilio.php | 6 ++--- .../{Auth/Phone => SMS/Adapter}/Vonage.php | 6 ++--- src/Appwrite/{Auth/Phone.php => SMS/SMS.php} | 8 +++--- .../Account/AccountCustomClientTest.php | 2 +- 18 files changed, 76 insertions(+), 77 deletions(-) rename src/Appwrite/{Auth/Phone => SMS/Adapter}/Mock.php (85%) rename src/Appwrite/{Auth/Phone => SMS/Adapter}/Msg91.php (72%) rename src/Appwrite/{Auth/Phone => SMS/Adapter}/Telesign.php (90%) rename src/Appwrite/{Auth/Phone => SMS/Adapter}/TextMagic.php (90%) rename src/Appwrite/{Auth/Phone => SMS/Adapter}/Twilio.php (89%) rename src/Appwrite/{Auth/Phone => SMS/Adapter}/Vonage.php (90%) rename src/Appwrite/{Auth/Phone.php => SMS/SMS.php} (94%) diff --git a/.env b/.env index 351447a8b4..0b8c625a36 100644 --- a/.env +++ b/.env @@ -56,8 +56,8 @@ _APP_SMTP_PORT=1025 _APP_SMTP_SECURE= _APP_SMTP_USERNAME= _APP_SMTP_PASSWORD= -_APP_PHONE_PROVIDER=phone://mock -_APP_PHONE_FROM=+123456789 +_APP_SMS_PROVIDER=sms://mock +_APP_SMS_FROM=+123456789 _APP_STORAGE_LIMIT=30000000 _APP_STORAGE_PREVIEW_LIMIT=20000000 _APP_FUNCTIONS_SIZE_LIMIT=30000000 diff --git a/CHANGES.md b/CHANGES.md index 5ba47ab638..ea0dabf8d9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -48,7 +48,7 @@ - Added Endpoint to update Account Phone Number (`PATCH:/v1/account/phone`) - Added Endpoint to create Account Phone Verification (`POST:/v1/account/verification/phone`) - Added Endpoint to confirm Account Phone Verification (`PUT:/v1/account/verification/phone`) - - Added `_APP_PHONE_PROVIDER` and `_APP_PHONE_FROM` Environment Variable + - Added `_APP_SMS_PROVIDER` and `_APP_SMS_FROM` Environment Variable - Added `phone` and `phoneVerification` Attribute to User - Added `$createdAt` and `$updatedAt` Attributes by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3382 - Bucket diff --git a/Dockerfile b/Dockerfile index bd437d047c..4930bdcb87 100755 --- a/Dockerfile +++ b/Dockerfile @@ -220,8 +220,8 @@ ENV _APP_SERVER=swoole \ _APP_SMTP_SECURE= \ _APP_SMTP_USERNAME= \ _APP_SMTP_PASSWORD= \ - _APP_PHONE_PROVIDER= \ - _APP_PHONE_FROM= \ + _APP_SMS_PROVIDER= \ + _APP_SMS_FROM= \ _APP_FUNCTIONS_SIZE_LIMIT=30000000 \ _APP_FUNCTIONS_TIMEOUT=900 \ _APP_FUNCTIONS_CONTAINERS=10 \ diff --git a/app/config/errors.php b/app/config/errors.php index 8420ddbb74..0c284027c1 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -50,7 +50,7 @@ return [ ], Exception::GENERAL_PHONE_DISABLED => [ 'name' => Exception::GENERAL_PHONE_DISABLED, - 'description' => 'Phone provider is not configured. Please check the _APP_PHONE_PROVIDER environment variable of your Appwrite server.', + 'description' => 'Phone provider is not configured. Please check the _APP_SMS_PROVIDER environment variable of your Appwrite server.', 'code' => 503, ], Exception::GENERAL_ARGUMENT_INVALID => [ diff --git a/app/config/variables.php b/app/config/variables.php index 7b4c4b4ace..ca86f3cc6d 100644 --- a/app/config/variables.php +++ b/app/config/variables.php @@ -394,8 +394,8 @@ return [ 'description' => '', 'variables' => [ [ - 'name' => '_APP_PHONE_PROVIDER', - 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'phone://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.", + 'name' => '_APP_SMS_PROVIDER', + 'description' => "Provider used for delivering SMS for Phone authentication. Use the following format: 'sms://[USER]:[SECRET]@[PROVIDER]'. \n\nAvailable providers are twilio, text-magic and telesign.", 'introduction' => '0.15.0', 'default' => '', 'required' => false, @@ -403,7 +403,7 @@ return [ 'filter' => '' ], [ - 'name' => '_APP_PHONE_FROM', + 'name' => '_APP_SMS_FROM', 'description' => 'Phone number used for sending out messages. Must start with a leading \'+\' and maximum of 15 digits without spaces (+123456789).', 'introduction' => '0.15.0', 'default' => '', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 27439cb2e1..4a3cd7608d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2,7 +2,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Auth\Phone; +use Appwrite\Auth\SMS; use Appwrite\Auth\Validator\Password; use Appwrite\Auth\Validator\Phone as ValidatorPhone; use Appwrite\Detector\Detector; @@ -58,6 +58,7 @@ App::post('/v1/account') ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "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('phone', '', 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') @@ -863,7 +864,7 @@ App::post('/v1/account/sessions/phone') ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},email:{param-email}') ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "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('number', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') + ->param('phone', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->inject('request') ->inject('response') ->inject('project') @@ -871,9 +872,9 @@ App::post('/v1/account/sessions/phone') ->inject('audits') ->inject('events') ->inject('messaging') - ->inject('phone') - ->action(function (string $userId, string $number, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging, Phone $phone) { - if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) { + ->inject('sms') + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging, SMS $sms) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED); } @@ -881,7 +882,7 @@ App::post('/v1/account/sessions/phone') $isPrivilegedUser = Auth::isPrivilegedUser($roles); $isAppUser = Auth::isAppUser($roles); - $user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$number])]); + $user = $dbForProject->findOne('users', [new Query('phone', Query::TYPE_EQUAL, [$phone])]); if (!$user) { $limit = $project->getAttribute('auths', [])['limit'] ?? 0; @@ -901,7 +902,7 @@ App::post('/v1/account/sessions/phone') '$read' => ['role:all'], '$write' => ['user:' . $userId], 'email' => null, - 'phone' => $number, + 'phone' => $phone, 'emailVerification' => false, 'phoneVerification' => false, 'status' => true, @@ -913,11 +914,11 @@ App::post('/v1/account/sessions/phone') 'sessions' => null, 'tokens' => null, 'memberships' => null, - 'search' => implode(' ', [$userId, $number]) + 'search' => implode(' ', [$userId, $phone]) ]))); } - $secret = $phone->generateSecretDigits(); + $secret = $sms->generateSecretDigits(); $expire = \time() + Auth::TOKEN_EXPIRATION_PHONE; @@ -941,7 +942,7 @@ App::post('/v1/account/sessions/phone') $dbForProject->deleteCachedDocument('users', $user->getId()); $messaging - ->setRecipient($number) + ->setRecipient($phone) ->setMessage($secret) ->trigger(); @@ -2277,7 +2278,7 @@ App::post('/v1/account/verification/phone') ->inject('messaging') ->action(function (Request $request, Response $response, Phone $phone, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) { - if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED); } diff --git a/app/init.php b/app/init.php index 71407699b8..1d628ede96 100644 --- a/app/init.php +++ b/app/init.php @@ -23,12 +23,12 @@ use Ahc\Jwt\JWT; use Ahc\Jwt\JWTException; use Appwrite\Extend\Exception; use Appwrite\Auth\Auth; -use Appwrite\Auth\Phone\Mock; -use Appwrite\Auth\Phone\Telesign; -use Appwrite\Auth\Phone\TextMagic; -use Appwrite\Auth\Phone\Twilio; -use Appwrite\Auth\Phone\Msg91; -use Appwrite\Auth\Phone\Vonage; +use Appwrite\SMS\Adapter\Mock; +use Appwrite\SMS\Adapter\Telesign; +use Appwrite\SMS\Adapter\TextMagic; +use Appwrite\SMS\Adapter\Twilio; +use Appwrite\SMS\Adapter\Msg91; +use Appwrite\SMS\Adapter\Vonage; use Appwrite\DSN\DSN; use Appwrite\Event\Audit; use Appwrite\Event\Database as EventDatabase; @@ -982,8 +982,8 @@ App::setResource('geodb', function ($register) { return $register->get('geodb'); }, ['register']); -App::setResource('phone', function () { - $dsn = new DSN(App::getEnv('_APP_PHONE_PROVIDER')); +App::setResource('sms', function () { + $dsn = new DSN(App::getEnv('_APP_SMS_PROVIDER')); $user = $dsn->getUser(); $secret = $dsn->getPassword(); diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 45628f5bcc..827e8c4d62 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -149,8 +149,8 @@ services: - _APP_MAINTENANCE_RETENTION_EXECUTION - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT - - _APP_PHONE_PROVIDER - - _APP_PHONE_SECRET + - _APP_SMS_PROVIDER + - _APP_SMS_FROM appwrite-realtime: image: /: @@ -514,8 +514,8 @@ services: - _APP_REDIS_PORT - _APP_REDIS_USER - _APP_REDIS_PASS - - _APP_PHONE_PROVIDER - - _APP_PHONE_FROM + - _APP_SMS_PROVIDER + - _APP_SMS_FROM - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG diff --git a/app/workers/messaging.php b/app/workers/messaging.php index fd6eed6c22..1fc5deaf44 100644 --- a/app/workers/messaging.php +++ b/app/workers/messaging.php @@ -1,12 +1,12 @@ getUser(); $secret = $dsn->getPassword(); - $this->phone = match ($dsn->getHost()) { + $this->sms = match ($dsn->getHost()) { 'mock' => new Mock('', ''), // used for tests 'twilio' => new Twilio($user, $secret), 'text-magic' => new TextMagic($user, $secret), @@ -43,12 +43,12 @@ class MessagingV1 extends Worker default => null }; - $this->from = App::getEnv('_APP_PHONE_FROM'); + $this->from = App::getEnv('_APP_SMS_FROM'); } public function run(): void { - if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { Console::info('Skipped sms processing. No Phone provider has been set.'); return; } @@ -62,7 +62,7 @@ class MessagingV1 extends Worker $message = $this->args['message']; try { - $this->phone->send($this->from, $recipient, $message); + $this->sms->send($this->from, $recipient, $message); } catch (\Exception $error) { throw new Exception('Error sending message: ' . $error->getMessage(), 500); } diff --git a/docker-compose.yml b/docker-compose.yml index 20d2c5c2f9..ecb0d9fe2d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -175,8 +175,8 @@ services: - _APP_MAINTENANCE_RETENTION_EXECUTION - _APP_MAINTENANCE_RETENTION_ABUSE - _APP_MAINTENANCE_RETENTION_AUDIT - - _APP_PHONE_PROVIDER - - _APP_PHONE_SECRET + - _APP_SMS_PROVIDER + - _APP_SMS_FROM appwrite-realtime: entrypoint: realtime @@ -545,8 +545,8 @@ services: - _APP_REDIS_PORT - _APP_REDIS_USER - _APP_REDIS_PASS - - _APP_PHONE_PROVIDER - - _APP_PHONE_FROM + - _APP_SMS_PROVIDER + - _APP_SMS_FROM - _APP_LOGGING_PROVIDER - _APP_LOGGING_CONFIG diff --git a/src/Appwrite/Auth/Phone/Mock.php b/src/Appwrite/SMS/Adapter/Mock.php similarity index 85% rename from src/Appwrite/Auth/Phone/Mock.php rename to src/Appwrite/SMS/Adapter/Mock.php index 03f4f7073f..fad97650b9 100644 --- a/src/Appwrite/Auth/Phone/Mock.php +++ b/src/Appwrite/SMS/Adapter/Mock.php @@ -1,10 +1,10 @@ utilized from for flow id * @param string $to * @param string $message diff --git a/src/Appwrite/Auth/Phone/Telesign.php b/src/Appwrite/SMS/Adapter/Telesign.php similarity index 90% rename from src/Appwrite/Auth/Phone/Telesign.php rename to src/Appwrite/SMS/Adapter/Telesign.php index 520dfc61bf..1ac6c1b284 100644 --- a/src/Appwrite/Auth/Phone/Telesign.php +++ b/src/Appwrite/SMS/Adapter/Telesign.php @@ -1,13 +1,13 @@ = 400) { - throw new Exception($response); + throw new \Exception($response); } return $response; diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 688bdb9df9..a2c0e33a6b 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -2,7 +2,7 @@ namespace Tests\E2E\Services\Account; -use Appwrite\Auth\Phone\Mock; +use Appwrite\SMS\Adapter\Mock; use Tests\E2E\Client; use Tests\E2E\Scopes\Scope; use Tests\E2E\Scopes\ProjectCustom; From 1da208ad10cf4295bbb60b5d0bb52f58f41d2da6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 16:53:19 +0300 Subject: [PATCH 02/18] Fix adapter name --- src/Appwrite/SMS/{SMS.php => Adapter.php} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Appwrite/SMS/{SMS.php => Adapter.php} (100%) diff --git a/src/Appwrite/SMS/SMS.php b/src/Appwrite/SMS/Adapter.php similarity index 100% rename from src/Appwrite/SMS/SMS.php rename to src/Appwrite/SMS/Adapter.php From b36c24c5087da547ac25ff99d24e98b847dff74b Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 17:19:58 +0300 Subject: [PATCH 03/18] Decouple Auth code from SMS provider adapter --- app/controllers/api/account.php | 11 +++++------ src/Appwrite/Auth/Auth.php | 16 ++++++++++++++++ src/Appwrite/SMS/Adapter.php | 12 +----------- src/Appwrite/SMS/Adapter/Mock.php | 11 +---------- .../Services/Account/AccountCustomClientTest.php | 6 +++--- 5 files changed, 26 insertions(+), 30 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 4a3cd7608d..480426c26a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2,7 +2,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; -use Appwrite\Auth\SMS; +use Appwrite\Auth\SMS\Mock; use Appwrite\Auth\Validator\Password; use Appwrite\Auth\Validator\Phone as ValidatorPhone; use Appwrite\Detector\Detector; @@ -873,7 +873,7 @@ App::post('/v1/account/sessions/phone') ->inject('events') ->inject('messaging') ->inject('sms') - ->action(function (string $userId, string $phone, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging, SMS $sms) { + ->action(function (string $userId, string $phone, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Event $events, EventPhone $messaging) { if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED); } @@ -918,7 +918,7 @@ App::post('/v1/account/sessions/phone') ]))); } - $secret = $sms->generateSecretDigits(); + $secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$defaultDigits : Auth::codeGenerator(); $expire = \time() + Auth::TOKEN_EXPIRATION_PHONE; @@ -2269,14 +2269,13 @@ App::post('/v1/account/verification/phone') ->label('abuse-key', 'userId:{userId}') ->inject('request') ->inject('response') - ->inject('phone') ->inject('user') ->inject('dbForProject') ->inject('audits') ->inject('events') ->inject('usage') ->inject('messaging') - ->action(function (Request $request, Response $response, Phone $phone, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) { + ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) { if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED); @@ -2292,7 +2291,7 @@ App::post('/v1/account/verification/phone') $verificationSecret = Auth::tokenGenerator(); - $secret = $phone->generateSecretDigits(); + $secret = (App::getEnv('_APP_SMS_PROVIDER') === 'sms://mock') ? Mock::$defaultDigits : Auth::codeGenerator(); $expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM; $verification = new Document([ diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 28b3d22ef9..70120ed357 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -280,6 +280,22 @@ class Auth return \bin2hex(\random_bytes($length)); } + /** + * Code Generator. + * + * Generate random code string + * + * @param int $length + * + * @return string + * + * @throws \Exception + */ + public static function codeGenerator(int $length = 6): string + { + return substr(str_shuffle("0123456789"), 0, $length); + } + /** * Verify token and check that its not expired. * diff --git a/src/Appwrite/SMS/Adapter.php b/src/Appwrite/SMS/Adapter.php index 4ed67668d5..ed737964b5 100644 --- a/src/Appwrite/SMS/Adapter.php +++ b/src/Appwrite/SMS/Adapter.php @@ -25,6 +25,7 @@ abstract class Adapter /** * Send Message to phone. + * * @param string $from * @param string $to * @param string $message @@ -72,15 +73,4 @@ abstract class Adapter return $response; } - - /** - * Generate 6 random digits for phone verification. - * - * @param int $digits - * @return string - */ - public function generateSecretDigits(int $digits = 6): string - { - return substr(str_shuffle("0123456789"), 0, $digits); - } } diff --git a/src/Appwrite/SMS/Adapter/Mock.php b/src/Appwrite/SMS/Adapter/Mock.php index fad97650b9..147f7ec9b2 100644 --- a/src/Appwrite/SMS/Adapter/Mock.php +++ b/src/Appwrite/SMS/Adapter/Mock.php @@ -9,7 +9,7 @@ class Mock extends Adapter /** * @var string */ - public static string $defaultDigits = '123456'; + public static string $digits = '123456'; /** * @param string $from @@ -21,13 +21,4 @@ class Mock extends Adapter { return; } - - /** - * @param int $digits - * @return string - */ - public function generateSecretDigits(int $digits = 6): string - { - return self::$defaultDigits; - } } diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index a2c0e33a6b..474fe612eb 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -713,7 +713,7 @@ class AccountCustomClientTest extends Scope $this->assertEquals(400, $response['headers']['status-code']); - $data['token'] = Mock::$defaultDigits; + $data['token'] = Mock::$digits; $data['id'] = $userId; $data['number'] = $number; @@ -949,7 +949,7 @@ class AccountCustomClientTest extends Scope 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'userId' => $id, - 'secret' => Mock::$defaultDigits, + 'secret' => Mock::$digits, ]); $this->assertEquals(200, $response['headers']['status-code']); @@ -964,7 +964,7 @@ class AccountCustomClientTest extends Scope 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ 'userId' => 'ewewe', - 'secret' => Mock::$defaultDigits, + 'secret' => Mock::$digits, ]); $this->assertEquals(404, $response['headers']['status-code']); From 53b60c92444db0da66311767a44bf5f1fe370cca Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 17:22:51 +0300 Subject: [PATCH 04/18] Fix for linter --- src/Appwrite/SMS/Adapter.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Appwrite/SMS/Adapter.php b/src/Appwrite/SMS/Adapter.php index ed737964b5..9f029bca69 100644 --- a/src/Appwrite/SMS/Adapter.php +++ b/src/Appwrite/SMS/Adapter.php @@ -25,7 +25,6 @@ abstract class Adapter /** * Send Message to phone. - * * @param string $from * @param string $to * @param string $message From 2ed9199bf1dd78191212eea2a57ce4746e7d5cbb Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 18:10:12 +0300 Subject: [PATCH 05/18] Added the ability to create a user with a phone number --- app/controllers/api/account.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 480426c26a..c403b09552 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -4,7 +4,7 @@ use Ahc\Jwt\JWT; use Appwrite\Auth\Auth; use Appwrite\Auth\SMS\Mock; use Appwrite\Auth\Validator\Password; -use Appwrite\Auth\Validator\Phone as ValidatorPhone; +use Appwrite\Auth\Validator\Phone; use Appwrite\Detector\Detector; use Appwrite\Event\Event; use Appwrite\Event\Mail; @@ -56,9 +56,9 @@ App::post('/v1/account') ->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 "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('phone', '', new Password(), 'User password. Must be at least 8 chars.') + ->param('email', '', new Email(), 'User email.', true) + ->param('password', '', new Password(), 'User password. Must be at least 8 chars.', true) + ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('request') ->inject('response') @@ -67,7 +67,7 @@ App::post('/v1/account') ->inject('audits') ->inject('usage') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { + ->action(function (string $userId, string $email, string $password, string $phone, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { $whitelistEmails = $project->getAttribute('authWhitelistEmails'); @@ -100,6 +100,8 @@ App::post('/v1/account') '$write' => ['user:' . $userId], 'email' => $email, 'emailVerification' => false, + 'phone' => $phone, + 'phoneVerification' => false, 'status' => true, 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, @@ -864,7 +866,7 @@ App::post('/v1/account/sessions/phone') ->label('abuse-limit', 10) ->label('abuse-key', 'url:{url},email:{param-email}') ->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string "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('phone', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') + ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') ->inject('request') ->inject('response') ->inject('project') @@ -1583,7 +1585,7 @@ 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) - ->param('number', '', new ValidatorPhone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.') + ->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') ->inject('user') From be36b31c4e4266814e3c903278e803385c51a6a6 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 19:53:55 +0300 Subject: [PATCH 06/18] Added missing test for new codeGenerator --- tests/unit/Auth/AuthTest.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 2d9a799790..6665d60b55 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -189,6 +189,14 @@ class AuthTest extends TestCase $this->assertEquals(\mb_strlen(Auth::tokenGenerator(5)), 10); } + public function testCodeGenerator(): void + { + $this->assertCount(6, \strlen(Auth::codeGenerator())); + $this->assertEquals(\mb_strlen(Auth::codeGenerator()), 256); + $this->assertEquals(\mb_strlen(Auth::codeGenerator(5)), 10); + $this->assertTrue(is_numeric(Auth::codeGenerator(5))); + } + public function testSessionVerify(): void { $secret = 'secret1'; From bd77354926d77eaeae24c7ee6d7c84c52fb42945 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 20:22:01 +0300 Subject: [PATCH 07/18] Fixed test --- app/controllers/api/account.php | 2 +- tests/unit/Auth/AuthTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 94dff149f3..88c1b18a3c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -103,7 +103,7 @@ App::post('/v1/account') 'phone' => $phone, 'phoneVerification' => false, 'status' => true, - 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), + 'password' => (!empty($password)) ? Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS) : '', 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => \time(), diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 6665d60b55..0c6d99b15a 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -191,7 +191,7 @@ class AuthTest extends TestCase public function testCodeGenerator(): void { - $this->assertCount(6, \strlen(Auth::codeGenerator())); + $this->assertEquals(6, \strlen(Auth::codeGenerator())); $this->assertEquals(\mb_strlen(Auth::codeGenerator()), 256); $this->assertEquals(\mb_strlen(Auth::codeGenerator(5)), 10); $this->assertTrue(is_numeric(Auth::codeGenerator(5))); From 95181546b430e393b3ef3c1fa592a974294db7df Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 21:09:24 +0300 Subject: [PATCH 08/18] Adpated tests --- app/controllers/api/account.php | 8 ++--- app/views/install/compose.phtml | 2 +- docker-compose.yml | 2 +- tests/e2e/Services/Account/AccountBase.php | 35 ------------------- .../Account/AccountCustomClientTest.php | 4 +-- 5 files changed, 8 insertions(+), 43 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 88c1b18a3c..8eb6be6390 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -56,9 +56,9 @@ App::post('/v1/account') ->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 "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.', true) + ->param('email', null, new Email(), 'User email.', true) ->param('password', '', new Password(), 'User password. Must be at least 8 chars.', true) - ->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) ->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('request') ->inject('response') @@ -67,7 +67,7 @@ App::post('/v1/account') ->inject('audits') ->inject('usage') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $phone, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { + ->action(function (string $userId, ?string $email, string $password, ?string $phone, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { $whitelistEmails = $project->getAttribute('authWhitelistEmails'); @@ -2278,7 +2278,7 @@ App::post('/v1/account/verification/phone') ->inject('messaging') ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) { - if (empty(App::getEnv('_APP_PHONE_PROVIDER'))) { + if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED); } diff --git a/app/views/install/compose.phtml b/app/views/install/compose.phtml index 4669066d7c..96bf005074 100644 --- a/app/views/install/compose.phtml +++ b/app/views/install/compose.phtml @@ -595,7 +595,7 @@ services: - _APP_REDIS_PASS mariadb: - image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p + image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p container_name: appwrite-mariadb <<: *x-logging restart: unless-stopped diff --git a/docker-compose.yml b/docker-compose.yml index a9b7c25eb7..88ad6f8d43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -638,7 +638,7 @@ services: - _APP_REDIS_PASS mariadb: - image: mariadb:10.8.3 # fix issues when upgrading using: mysql_upgrade -u root -p + image: mariadb:10.7 # fix issues when upgrading using: mysql_upgrade -u root -p container_name: appwrite-mariadb <<: *x-logging networks: diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 283c76a34b..aaca0987b2 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -51,41 +51,6 @@ trait AccountBase $this->assertEquals($response['headers']['status-code'], 409); - $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'userId' => 'unique()', - 'email' => '', - 'password' => '', - ]); - - $this->assertEquals($response['headers']['status-code'], 400); - - $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'userId' => 'unique()', - 'email' => $email, - 'password' => '', - ]); - - $this->assertEquals($response['headers']['status-code'], 400); - - $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ - 'origin' => 'http://localhost', - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ]), [ - 'userId' => 'unique()', - 'email' => '', - 'password' => $password, - ]); - - $this->assertEquals($response['headers']['status-code'], 400); return [ 'id' => $id, diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index 474fe612eb..3d525d766a 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -690,7 +690,7 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], ]), [ 'userId' => 'unique()', - 'number' => $number, + 'phone' => $number, ]); $this->assertEquals(201, $response['headers']['status-code']); @@ -869,7 +869,7 @@ class AccountCustomClientTest extends Scope 'x-appwrite-project' => $this->getProject()['$id'], 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, ]), [ - 'number' => $newPhone, + 'phone' => $newPhone, 'password' => 'new-password' ]); From fbd5835bd96a26069329b06754618463b6699237 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 21:33:52 +0300 Subject: [PATCH 09/18] Fixed test --- tests/unit/Auth/AuthTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 0c6d99b15a..32fda9551f 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -192,7 +192,7 @@ class AuthTest extends TestCase public function testCodeGenerator(): void { $this->assertEquals(6, \strlen(Auth::codeGenerator())); - $this->assertEquals(\mb_strlen(Auth::codeGenerator()), 256); + $this->assertEquals(\mb_strlen(Auth::codeGenerator(256)), 256); $this->assertEquals(\mb_strlen(Auth::codeGenerator(5)), 10); $this->assertTrue(is_numeric(Auth::codeGenerator(5))); } From 9b897629ba8571fd218f51eda1083bd781a7f706 Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 21:57:23 +0300 Subject: [PATCH 10/18] Fixed code generator --- src/Appwrite/Auth/Auth.php | 8 +++++++- tests/unit/Auth/AuthTest.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 70120ed357..7da989dbad 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -293,7 +293,13 @@ class Auth */ public static function codeGenerator(int $length = 6): string { - return substr(str_shuffle("0123456789"), 0, $length); + $value = ''; + + for ($i = 0; $i < $length; $i++) { + $value .= random_int(0,9); + } + + return $value; } /** diff --git a/tests/unit/Auth/AuthTest.php b/tests/unit/Auth/AuthTest.php index 32fda9551f..14d8fa275d 100644 --- a/tests/unit/Auth/AuthTest.php +++ b/tests/unit/Auth/AuthTest.php @@ -193,7 +193,7 @@ class AuthTest extends TestCase { $this->assertEquals(6, \strlen(Auth::codeGenerator())); $this->assertEquals(\mb_strlen(Auth::codeGenerator(256)), 256); - $this->assertEquals(\mb_strlen(Auth::codeGenerator(5)), 10); + $this->assertEquals(\mb_strlen(Auth::codeGenerator(10)), 10); $this->assertTrue(is_numeric(Auth::codeGenerator(5))); } From f88a64df1c5261f5bee80ef1d7f675ce5b5f725a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Sun, 14 Aug 2022 22:01:58 +0300 Subject: [PATCH 11/18] Fix for linter --- src/Appwrite/Auth/Auth.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 7da989dbad..159282c5f4 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -296,9 +296,9 @@ class Auth $value = ''; for ($i = 0; $i < $length; $i++) { - $value .= random_int(0,9); + $value .= random_int(0, 9); } - + return $value; } From 6f74c565e84802db774b04af9859ee716fb7a70a Mon Sep 17 00:00:00 2001 From: Eldad Fux Date: Mon, 15 Aug 2022 09:55:37 +0300 Subject: [PATCH 12/18] Fixed docblock --- src/Appwrite/Auth/Auth.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 159282c5f4..bc1fa854c4 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -256,8 +256,6 @@ class Auth * @param int $length * * @return string - * - * @throws \Exception */ public static function passwordGenerator(int $length = 20): string { @@ -272,8 +270,6 @@ class Auth * @param int $length * * @return string - * - * @throws \Exception */ public static function tokenGenerator(int $length = 128): string { @@ -288,8 +284,6 @@ class Auth * @param int $length * * @return string - * - * @throws \Exception */ public static function codeGenerator(int $length = 6): string { From 72e524963c577c402c27e5dfa6a110dd43470940 Mon Sep 17 00:00:00 2001 From: "Eldad A. Fux" Date: Mon, 15 Aug 2022 11:30:58 +0300 Subject: [PATCH 13/18] Update app/controllers/api/account.php Co-authored-by: Christy Jacob --- 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 8eb6be6390..47a264abb5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -2279,7 +2279,7 @@ App::post('/v1/account/verification/phone') ->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Audit $audits, Event $events, Stats $usage, EventPhone $messaging) { if (empty(App::getEnv('_APP_SMS_PROVIDER'))) { - throw new Exception('Phone provider not configured', 503, Exception::GENERAL_PHONE_DISABLED); + throw new Exception(Exception::GENERAL_PHONE_DISABLED); } if (empty($user->getAttribute('phone'))) { From e41de7575c8f09f70687b66680be355b72507a8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 16 Aug 2022 12:05:22 +0000 Subject: [PATCH 14/18] Remove unnessessary change --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 338270a04a..b3d3653f3e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -59,7 +59,7 @@ - Added Endpoint to update Account Phone Number (`PATCH:/v1/account/phone`) - Added Endpoint to create Account Phone Verification (`POST:/v1/account/verification/phone`) - Added Endpoint to confirm Account Phone Verification (`PUT:/v1/account/verification/phone`) - - Added `_APP_SMS_PROVIDER` and `_APP_SMS_FROM` Environment Variable + - Added `_APP_PHONE_PROVIDER` and `_APP_PHONE_FROM` Environment Variable - Added `phone` and `phoneVerification` Attribute to User - Added `$createdAt` and `$updatedAt` Attributes by @TorstenDittmann in https://github.com/appwrite/appwrite/pull/3382 - Bucket From 1e96f8c3d9de4dfc10d9c95198bf9460b85cfc0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 16 Aug 2022 12:05:46 +0000 Subject: [PATCH 15/18] Revert changes regarding AccountAPI createAccount --- app/controllers/api/account.php | 11 +++---- tests/e2e/Services/Account/AccountBase.php | 35 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 47a264abb5..385f8c4f4b 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -56,9 +56,8 @@ App::post('/v1/account') ->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 "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', null, new Email(), 'User email.', true) - ->param('password', '', new Password(), 'User password. Must be at least 8 chars.', true) - ->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true) + ->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') @@ -67,7 +66,7 @@ App::post('/v1/account') ->inject('audits') ->inject('usage') ->inject('events') - ->action(function (string $userId, ?string $email, string $password, ?string $phone, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { + ->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $project, Database $dbForProject, Audit $audits, Stats $usage, Event $events) { $email = \strtolower($email); if ('console' === $project->getId()) { $whitelistEmails = $project->getAttribute('authWhitelistEmails'); @@ -100,10 +99,8 @@ App::post('/v1/account') '$write' => ['user:' . $userId], 'email' => $email, 'emailVerification' => false, - 'phone' => $phone, - 'phoneVerification' => false, 'status' => true, - 'password' => (!empty($password)) ? Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS) : '', + 'password' => Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS), 'hash' => Auth::DEFAULT_ALGO, 'hashOptions' => Auth::DEFAULT_ALGO_OPTIONS, 'passwordUpdate' => \time(), diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index aaca0987b2..508a96f771 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -50,7 +50,42 @@ trait AccountBase ]); $this->assertEquals($response['headers']['status-code'], 409); + + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => 'unique()', + 'email' => '', + 'password' => '', + ]); + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => 'unique()', + 'email' => $email, + 'password' => '', + ]); + + $this->assertEquals($response['headers']['status-code'], 400); + + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => 'unique()', + 'email' => '', + 'password' => $password, + ]); + + $this->assertEquals($response['headers']['status-code'], 400); return [ 'id' => $id, From 308853580679f280ecac1694ed0729994983f705 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 16 Aug 2022 13:02:28 +0000 Subject: [PATCH 16/18] Added phone number to UsersAPI createUser + tests --- app/controllers/api/users.php | 37 ++++++---- tests/e2e/Services/Users/UsersBase.php | 96 +++++++++++++++++++++++++- 2 files changed, 116 insertions(+), 17 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index e2f485a71f..9ec96bccd6 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -31,21 +31,27 @@ use MaxMind\Db\Reader; use Utopia\Validator\Integer; /** TODO: Remove function when we move to using utopia/platform */ -function createUser(string $hash, mixed $hashOptions, string $userId, string $email, string $password, string $name, Database $dbForProject, Stats $usage, Event $events): Document +function createUser(string $hash, mixed $hashOptions, string $userId, ?string $email, ?string $password, ?string $phone, string $name, Database $dbForProject, Stats $usage, Event $events): Document { $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array - $email = \strtolower($email); + + if(!empty($email)) { + $email = \strtolower($email); + } try { $userId = $userId == 'unique()' ? $dbForProject->getId() : $userId; + $user = $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$read' => ['role:all'], '$write' => ['user:' . $userId], 'email' => $email, 'emailVerification' => false, + 'phone' => $phone, + 'phoneVerification' => false, 'status' => true, - 'password' => $hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password, + 'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null, 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions, 'passwordUpdate' => \time(), @@ -56,7 +62,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, string $em 'sessions' => null, 'tokens' => null, 'memberships' => null, - 'search' => implode(' ', [$userId, $email, $name]) + 'search' => implode(' ', [$userId, $email, $phone, $name]) ])); } catch (Duplicate $th) { throw new Exception(Exception::USER_ALREADY_EXISTS); @@ -86,15 +92,16 @@ App::post('/v1/users') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_USER) ->param('userId', '', new CustomId(), 'User ID. Choose your own unique ID or pass the string "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(), 'Plain text user password. Must be at least 8 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('name', '', new Text(128), 'User name. Max length: 128 chars.', true) ->inject('response') ->inject('dbForProject') ->inject('usage') ->inject('events') - ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { - $user = createUser('plaintext', '{}', $userId, $email, $password, $name, $dbForProject, $usage, $events); + ->action(function (string $userId, ?string $email, ?string $phone, ?string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { + $user = createUser('plaintext', '{}', $userId, $email, $password, $phone, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -121,7 +128,7 @@ App::post('/v1/users/bcrypt') ->inject('usage') ->inject('events') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { - $user = createUser('bcrypt', '{}', $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('bcrypt', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -148,7 +155,7 @@ App::post('/v1/users/md5') ->inject('usage') ->inject('events') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { - $user = createUser('md5', '{}', $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('md5', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -175,7 +182,7 @@ App::post('/v1/users/argon2') ->inject('usage') ->inject('events') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { - $user = createUser('argon2', '{}', $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('argon2', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -209,7 +216,7 @@ App::post('/v1/users/sha') $options = '{"version":"' . $passwordVersion . '"}'; } - $user = createUser('sha', $options, $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('sha', $options, $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -236,7 +243,7 @@ App::post('/v1/users/phpass') ->inject('usage') ->inject('events') ->action(function (string $userId, string $email, string $password, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { - $user = createUser('phpass', '{}', $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('phpass', '{}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -276,7 +283,7 @@ App::post('/v1/users/scrypt') 'length' => $passwordLength ]; - $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('scrypt', \json_encode($options), $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); @@ -306,7 +313,7 @@ App::post('/v1/users/scrypt-modified') ->inject('usage') ->inject('events') ->action(function (string $userId, string $email, string $password, string $passwordSalt, string $passwordSaltSeparator, string $passwordSignerKey, string $name, Response $response, Database $dbForProject, Stats $usage, Event $events) { - $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, $name, $dbForProject, $usage, $events); + $user = createUser('scryptMod', '{"signerKey":"' . $passwordSignerKey . '","saltSeparator":"' . $passwordSaltSeparator . '","salt":"' . $passwordSalt . '"}', $userId, $email, $password, null, $name, $dbForProject, $usage, $events); $response->setStatusCode(Response::STATUS_CODE_CREATED); $response->dynamic($user, Response::MODEL_USER); diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 6a2ae90e75..90c65e1306 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -264,11 +264,103 @@ trait UsersBase $this->assertEquals($response['body']['userId'], 'scrypt-modified'); } + /** + * Tests all optional parameters of createUser (email, phone, anonymous..) + * + * @depends testCreateUser + */ + public function testCreateUserTypes(array $data): void { + /** + * Test for SUCCESS + */ + + // Email + password + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => 'unique()', + 'email' => 'emailuser@appwrite.io', + 'password' => 'emailUserPassword', + ]); + + $this->assertNotEmpty($response['body']['email']); + $this->assertNotEmpty($response['body']['password']); + $this->assertEmpty($response['body']['phone']); + + // Phone + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => 'unique()', + 'phone' => '+123456789012', + ]); + + $this->assertEmpty($response['body']['email']); + $this->assertEmpty($response['body']['password']); + $this->assertNotEmpty($response['body']['phone']); + + // Anonymous + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => 'unique()', + ]); + + $this->assertEmpty($response['body']['email']); + $this->assertEmpty($response['body']['password']); + $this->assertEmpty($response['body']['phone']); + + // Email-only + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => 'unique()', + 'email' => 'emailonlyuser@appwrite.io', + ]); + + $this->assertNotEmpty($response['body']['email']); + $this->assertEmpty($response['body']['password']); + $this->assertEmpty($response['body']['phone']); + + // Password-only + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => 'unique()', + 'password' => 'passwordOnlyUser', + ]); + + $this->assertEmpty($response['body']['email']); + $this->assertNotEmpty($response['body']['password']); + $this->assertEmpty($response['body']['phone']); + + // Password and phone + $response = $this->client->call(Client::METHOD_POST, '/users', array_merge([ + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ], $this->getHeaders()), [ + 'userId' => 'unique()', + 'password' => 'passwordOnlyUser', + 'phone' => '+123456789013', + ]); + + $this->assertEmpty($response['body']['email']); + $this->assertNotEmpty($response['body']['password']); + $this->assertNotEmpty($response['body']['phone']); + } + /** * @depends testCreateUser */ public function testListUsers(array $data): void { + $totalUsers = 15; + /** * Test for SUCCESS listUsers */ @@ -280,7 +372,7 @@ trait UsersBase $this->assertEquals($response['headers']['status-code'], 200); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['users']); - $this->assertCount(9, $response['body']['users']); + $this->assertCount($totalUsers, $response['body']['users']); $this->assertEquals($response['body']['users'][0]['$id'], $data['userId']); $this->assertEquals($response['body']['users'][1]['$id'], 'user1'); @@ -295,7 +387,7 @@ trait UsersBase $this->assertEquals($response['headers']['status-code'], 200); $this->assertNotEmpty($response['body']); $this->assertNotEmpty($response['body']['users']); - $this->assertCount(8, $response['body']['users']); + $this->assertCount($totalUsers - 1, $response['body']['users']); $this->assertEquals($response['body']['users'][0]['$id'], 'user1'); $response = $this->client->call(Client::METHOD_GET, '/users', array_merge([ From 2814a6f680a760a699a97f9cf130685c5eac0739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 16 Aug 2022 13:03:38 +0000 Subject: [PATCH 17/18] Linter fix --- app/controllers/api/users.php | 4 ++-- tests/e2e/Services/Account/AccountBase.php | 2 +- tests/e2e/Services/Users/UsersBase.php | 11 ++++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index 9ec96bccd6..d416f72e01 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -35,13 +35,13 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e { $hashOptionsObject = (\is_string($hashOptions)) ? \json_decode($hashOptions, true) : $hashOptions; // Cast to JSON array - if(!empty($email)) { + if (!empty($email)) { $email = \strtolower($email); } try { $userId = $userId == 'unique()' ? $dbForProject->getId() : $userId; - + $user = $dbForProject->createDocument('users', new Document([ '$id' => $userId, '$read' => ['role:all'], diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 508a96f771..283c76a34b 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -50,7 +50,7 @@ trait AccountBase ]); $this->assertEquals($response['headers']['status-code'], 409); - + $response = $this->client->call(Client::METHOD_POST, '/account', array_merge([ 'origin' => 'http://localhost', 'content-type' => 'application/json', diff --git a/tests/e2e/Services/Users/UsersBase.php b/tests/e2e/Services/Users/UsersBase.php index 90c65e1306..1ea4621d75 100644 --- a/tests/e2e/Services/Users/UsersBase.php +++ b/tests/e2e/Services/Users/UsersBase.php @@ -269,7 +269,8 @@ trait UsersBase * * @depends testCreateUser */ - public function testCreateUserTypes(array $data): void { + public function testCreateUserTypes(array $data): void + { /** * Test for SUCCESS */ @@ -308,7 +309,7 @@ trait UsersBase ], $this->getHeaders()), [ 'userId' => 'unique()', ]); - + $this->assertEmpty($response['body']['email']); $this->assertEmpty($response['body']['password']); $this->assertEmpty($response['body']['phone']); @@ -321,7 +322,7 @@ trait UsersBase 'userId' => 'unique()', 'email' => 'emailonlyuser@appwrite.io', ]); - + $this->assertNotEmpty($response['body']['email']); $this->assertEmpty($response['body']['password']); $this->assertEmpty($response['body']['phone']); @@ -334,7 +335,7 @@ trait UsersBase 'userId' => 'unique()', 'password' => 'passwordOnlyUser', ]); - + $this->assertEmpty($response['body']['email']); $this->assertNotEmpty($response['body']['password']); $this->assertEmpty($response['body']['phone']); @@ -348,7 +349,7 @@ trait UsersBase 'password' => 'passwordOnlyUser', 'phone' => '+123456789013', ]); - + $this->assertEmpty($response['body']['email']); $this->assertNotEmpty($response['body']['password']); $this->assertNotEmpty($response['body']['phone']); From 7a1dfbfb8d5cfd4b6e0ba2a24205441a28a3c13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Ba=C4=8Do?= Date: Tue, 16 Aug 2022 15:56:54 +0000 Subject: [PATCH 18/18] Fix passwordUpdate time for non-pass users --- app/controllers/api/users.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/users.php b/app/controllers/api/users.php index d416f72e01..64e5f81615 100644 --- a/app/controllers/api/users.php +++ b/app/controllers/api/users.php @@ -54,7 +54,7 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e 'password' => (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null, 'hash' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO : $hash, 'hashOptions' => $hash === 'plaintext' ? Auth::DEFAULT_ALGO_OPTIONS : $hashOptions, - 'passwordUpdate' => \time(), + 'passwordUpdate' => (!empty($password)) ? \time() : 0, 'registration' => \time(), 'reset' => false, 'name' => $name,