From 22ceb1dc7c43dcc71966e0e8a198bd89f67a6065 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Aug 2021 16:29:52 +0545 Subject: [PATCH 01/22] create session with magic url --- app/config/locale/translations/en.json | 6 + app/controllers/api/account.php | 267 +++++++++++++++++++++++++ app/init.php | 1 + app/workers/mails.php | 5 + src/Appwrite/Auth/Auth.php | 2 + 5 files changed, 281 insertions(+) diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index de6d8bb71..663e6b39d 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -9,6 +9,12 @@ "emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.", "emails.verification.thanks": "Thanks", "emails.verification.signature": "{{project}} team", + "emails.magicSession.subject": "Create Session", + "emails.magicSession.hello": "Hey,", + "emails.magicSession.body": "Follow this link to create a session.", + "emails.magicSession.footer": "If you didn’t ask to create session for this email, you can ignore this message.", + "emails.magicSession.thanks": "Thanks", + "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", "emails.recovery.hello": "Hello {{name}}", "emails.recovery.body": "Follow this link to reset your {{project}} password.", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 55bcde6ca..33f7cf51d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -611,6 +611,273 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') ; }); + +App::post('/v1/account/sessions/url') + ->desc('Create Magic URL for creating sessions') + ->groups(['api', 'account']) + ->label('scope', 'account') + ->label('sdk.auth', []) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'createMagicURL') + ->label('sdk.description', '/docs/references/account/create-magic-url.md') + ->label('sdk.response.code', Response::STATUS_CODE_CREATED) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_TOKEN) + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},userId:{userId}') + ->param('email', '', new Email(), 'User email.') + ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) + ->inject('request') + ->inject('response') + ->inject('project') + ->inject('projectDB') + ->inject('locale') + ->inject('audits') + ->inject('events') + ->inject('mails') + ->action(function ($email, $url, $request, $response, $project, $projectDB, $locale, $audits, $events, $mails) { + /** @var Utopia\Swoole\Request $request */ + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Document $project */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Locale\Locale $locale */ + /** @var Appwrite\Event\Event $audits */ + /** @var Appwrite\Event\Event $events */ + /** @var Appwrite\Event\Event $mails */ + + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); + $isAppUser = Auth::isAppUser(Authorization::$roles); + + $user = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + 'email='.$email, + ], + ]); + + if (empty($user)) { + $limit = $project->getAttribute('usersAuthLimit', 0); + + if ($limit !== 0) { + $projectDB->getCollection([ // Count users + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + ], + ]); + + $sum = $projectDB->getSum(); + + if($sum >= $limit) { + throw new Exception('Project registration is restricted. Contact your administrator for more information.', 501); + } + } + + Authorization::disable(); + + $user = $projectDB->createDocument([ + '$collection' => Database::SYSTEM_COLLECTION_USERS, + '$permissions' => [ + 'read' => ['*'], + 'write' => ['user:{self}'], + ], + 'email' => $email, + 'emailVerification' => false, + 'status' => Auth::USER_STATUS_UNACTIVATED, + 'password' => null, + 'passwordUpdate' => \time(), + 'registration' => \time(), + 'reset' => false, + 'name' => null, + ], ['email' => $email]); + + Authorization::reset(); + $userAdded = true; + } + + $loginSecret = Auth::tokenGenerator(); + + $expire = \time() + Auth::TOKEN_EXPIRATION_CONFIRM; + + $token = new Document([ + '$collection' => Database::SYSTEM_COLLECTION_TOKENS, + '$permissions' => ['read' => ['user:'.$user->getId()], 'write' => ['user:'.$user->getId()]], + 'userId' => $user->getId(), + 'type' => Auth::TOKEN_TYPE_MAGIC_URL, + 'secret' => Auth::hash($loginSecret), // One way hash encryption to protect DB leak + 'expire' => $expire, + 'userAgent' => $request->getUserAgent('UNKNOWN'), + 'ip' => $request->getIP(), + ]); + + Authorization::setRole('user:'.$user->getId()); + + $token = $projectDB->createDocument($token->getArrayCopy()); + + if (false === $token) { + throw new Exception('Failed saving verification to DB', 500); + } + + $user->setAttribute('tokens', $token, Document::SET_TYPE_APPEND); + + $user = $projectDB->updateDocument($user->getArrayCopy()); + + if (false === $user) { + throw new Exception('Failed to save user to DB', 500); + } + + $url = Template::parseURL($url); + $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire]); + $url = Template::unParseURL($url); + + $mails + ->setParam('event', ($userAdded ?? false) ? 'users.create' : '') + ->setParam('from', $project->getId()) + ->setParam('recipient', $user->getAttribute('email')) + ->setParam('url', $url) + ->setParam('locale', $locale->default) + ->setParam('project', $project->getAttribute('name', ['[APP-NAME]'])) + ->setParam('type', MAIL_TYPE_MAGIC_SESSION) + ->trigger() + ; + + $events + ->setParam('eventData', + $response->output($token->setAttribute('secret', $loginSecret), + Response::MODEL_TOKEN + )) + ; + + $token // Hide secret for clients, sp + ->setAttribute('secret', + ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); + + $audits + ->setParam('userId', $user->getId()) + ->setParam('event', ($userAdded ?? false) ? 'users.create' : '') + ->setParam('resource', 'users/'.$user->getId()) + ; + + $response + ->setStatusCode(Response::STATUS_CODE_CREATED) + ->dynamic($token, Response::MODEL_TOKEN) + ; + }); + +App::put('/v1/account/sessions/url') + ->desc('Create session with Magic URL') + ->groups(['api', 'account']) + ->label('scope', 'public') + ->label('event', 'account.sessions.create') + ->label('sdk.auth', []) + ->label('sdk.namespace', 'account') + ->label('sdk.method', 'createMagicUrlSession') + ->label('sdk.description', '/docs/references/account/create-magic-url-session.md') + ->label('sdk.response.code', Response::STATUS_CODE_OK) + ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) + ->label('sdk.response.model', Response::MODEL_SESSION) + ->label('abuse-limit', 10) + ->label('abuse-key', 'url:{url},userId:{param-userId}') + ->param('userId', '', new UID(), 'User unique ID.') + ->param('secret', '', new Text(256), 'Valid verification token.') + ->inject('request') + ->inject('response') + ->inject('projectDB') + ->inject('locale') + ->inject('geodb') + ->inject('audits') + ->action(function ($userId, $secret, $request, $response, $projectDB, $locale, $geodb, $audits) { + /** @var Appwrite\Utopia\Response $response */ + /** @var Appwrite\Database\Database $projectDB */ + /** @var Appwrite\Event\Event $audits */ + + $profile = $projectDB->getCollectionFirst([ // Get user by email address + 'limit' => 1, + 'filters' => [ + '$collection='.Database::SYSTEM_COLLECTION_USERS, + '$id='.$userId, + ], + ]); + + if (empty($profile)) { + throw new Exception('User not found', 404); + } + + $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret); + + if (!$token) { + throw new Exception('Invalid login token', 401); + } + + $detector = new Detector($request->getUserAgent('UNKNOWN')); + $record = $geodb->get($request->getIP()); + $secret = Auth::tokenGenerator(); + $expiry = \time() + Auth::TOKEN_EXPIRATION_LOGIN_LONG; + $session = new Document(array_merge( + [ + '$collection' => Database::SYSTEM_COLLECTION_SESSIONS, + '$permissions' => ['read' => ['user:' . $profile->getId()], 'write' => ['user:' . $profile->getId()]], + 'userId' => $profile->getId(), + 'provider' => Auth::SESSION_PROVIDER_MAGIC_URL, + 'secret' => Auth::hash($secret), // One way hash encryption to protect DB leak + 'expire' => $expiry, + 'userAgent' => $request->getUserAgent('UNKNOWN'), + 'ip' => $request->getIP(), + 'countryCode' => ($record) ? \strtolower($record['country']['iso_code']) : '--', + ], + $detector->getOS(), + $detector->getClient(), + $detector->getDevice() + )); + + Authorization::setRole('user:'.$profile->getId()); + + $session = $projectDB->createDocument($session->getArrayCopy()); + + if (false === $session) { + throw new Exception('Failed saving session to DB', 500); + } + + $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); + + $user = $projectDB->updateDocument($profile->getArrayCopy()); + + if (false === $user) { + throw new Exception('Failed saving user to DB', 500); + } + + $audits + ->setParam('userId', $user->getId()) + ->setParam('event', 'account.sessions.create') + ->setParam('resource', 'users/'.$user->getId()) + ; + + if (!Config::getParam('domainVerification')) { + $response + ->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)])) + ; + } + + $protocol = $request->getProtocol(); + + $response + ->addCookie(Auth::$cookieName.'_legacy', Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, null) + ->addCookie(Auth::$cookieName, Auth::encodeSession($user->getId(), $secret), $expiry, '/', Config::getParam('cookieDomain'), ('https' == $protocol), true, Config::getParam('cookieSamesite')) + ->setStatusCode(Response::STATUS_CODE_CREATED) + ; + + $countryName = (isset($countries[strtoupper($session->getAttribute('countryCode'))])) + ? $countries[strtoupper($session->getAttribute('countryCode'))] + : $locale->getText('locale.country.unknown'); + + $session + ->setAttribute('current', true) + ->setAttribute('countryName', $countryName) + ; + + $response->dynamic($session, Response::MODEL_SESSION); + }); + App::post('/v1/account/sessions/anonymous') ->desc('Create Anonymous Session') ->groups(['api', 'account', 'auth']) diff --git a/app/init.php b/app/init.php index df71d60de..4b8f78fc2 100644 --- a/app/init.php +++ b/app/init.php @@ -72,6 +72,7 @@ const DELETE_TYPE_ABUSE = 'abuse'; const DELETE_TYPE_CERTIFICATES = 'certificates'; // Mail Types const MAIL_TYPE_VERIFICATION = 'verification'; +const MAIL_TYPE_MAGIC_SESSION = 'magicSession'; const MAIL_TYPE_RECOVERY = 'recovery'; const MAIL_TYPE_INVITATION = 'invitation'; // Auth Types diff --git a/app/workers/mails.php b/app/workers/mails.php index 25abe54aa..578c71ce9 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -58,6 +58,9 @@ class MailsV1 extends Worker case MAIL_TYPE_VERIFICATION: $subject = $locale->getText("$prefix.subject"); break; + case MAIL_TYPE_MAGIC_SESSION: + $subject = $locale->getText("$prefix.subject"); + break; default: throw new Exception('Undefined Mail Type : ' . $type, 500); } @@ -132,6 +135,8 @@ class MailsV1 extends Worker return 'emails.invitation'; case MAIL_TYPE_VERIFICATION: return 'emails.verification'; + case MAIL_TYPE_MAGIC_SESSION: + return 'emails.magicSession'; default: throw new Exception('Undefined Mail Type : ' . $type, 500); } diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index 9fa181486..ec47871b9 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -32,12 +32,14 @@ class Auth const TOKEN_TYPE_VERIFICATION = 2; const TOKEN_TYPE_RECOVERY = 3; const TOKEN_TYPE_INVITE = 4; + const TOKEN_TYPE_MAGIC_URL = 5; /** * Session Providers. */ const SESSION_PROVIDER_EMAIL = 'email'; const SESSION_PROVIDER_ANONYMOUS = 'anonymous'; + const SESSION_PROVIDER_MAGIC_URL = 'magicUrl'; /** * Token Expiration times. From 56fdbf9e3ef2ca610cfd06944ef64bdc19ecb3b6 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Aug 2021 16:54:45 +0545 Subject: [PATCH 02/22] fix scope --- 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 33f7cf51d..4db6036a0 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -615,7 +615,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') App::post('/v1/account/sessions/url') ->desc('Create Magic URL for creating sessions') ->groups(['api', 'account']) - ->label('scope', 'account') + ->label('scope', 'public') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createMagicURL') From 13ba85036b232e12ba7e8a5990e1716081731b37 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Mon, 30 Aug 2021 17:17:05 +0545 Subject: [PATCH 03/22] fix token type for verification --- 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 4db6036a0..6983c9f9d 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -803,7 +803,7 @@ App::put('/v1/account/sessions/url') throw new Exception('User not found', 404); } - $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_VERIFICATION, $secret); + $token = Auth::tokenVerify($profile->getAttribute('tokens', []), Auth::TOKEN_TYPE_MAGIC_URL, $secret); if (!$token) { throw new Exception('Invalid login token', 401); From 45955f0c41ee3e457913f1534e779b4d6c56e434 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 11:42:07 +0545 Subject: [PATCH 04/22] abuse limit by email param --- 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 6983c9f9d..69390247c 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -624,7 +624,7 @@ App::post('/v1/account/sessions/url') ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOKEN) ->label('abuse-limit', 10) - ->label('abuse-key', 'url:{url},userId:{userId}') + ->label('abuse-key', 'url:{url},email:{param-email}') ->param('email', '', new Email(), 'User email.') ->param('url', '', function ($clients) { return new Host($clients); }, 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients']) ->inject('request') From 9a8f4c49e437e1ca81a13b26bd06444561d8e7cb Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 11:42:15 +0545 Subject: [PATCH 05/22] email update --- app/config/locale/translations/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 663e6b39d..c00740b13 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -9,10 +9,10 @@ "emails.verification.footer": "If you didn’t ask to verify this address, you can ignore this message.", "emails.verification.thanks": "Thanks", "emails.verification.signature": "{{project}} team", - "emails.magicSession.subject": "Create Session", + "emails.magicSession.subject": "Login", "emails.magicSession.hello": "Hey,", - "emails.magicSession.body": "Follow this link to create a session.", - "emails.magicSession.footer": "If you didn’t ask to create session for this email, you can ignore this message.", + "emails.magicSession.body": "Follow this link to login.", + "emails.magicSession.footer": "If you didn’t ask to login using this email, you can ignore this message.", "emails.magicSession.thanks": "Thanks", "emails.magicSession.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", From d67c754dad4537c6c16860605363fbe77f7a4e4c Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 12:58:30 +0545 Subject: [PATCH 06/22] update descriptions --- app/controllers/api/account.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 69390247c..eb9327ae9 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -612,14 +612,14 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect') }); -App::post('/v1/account/sessions/url') - ->desc('Create Magic URL for creating sessions') +App::post('/v1/account/sessions/magic-url') + ->desc('Create Magic URL session') ->groups(['api', 'account']) ->label('scope', 'public') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') - ->label('sdk.method', 'createMagicURL') - ->label('sdk.description', '/docs/references/account/create-magic-url.md') + ->label('sdk.method', 'createMagicURLSession') + ->label('sdk.description', '/docs/references/account/create-magic-url-session.md') ->label('sdk.response.code', Response::STATUS_CODE_CREATED) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_TOKEN) @@ -764,15 +764,15 @@ App::post('/v1/account/sessions/url') ; }); -App::put('/v1/account/sessions/url') - ->desc('Create session with Magic URL') +App::put('/v1/account/sessions/magic-url') + ->desc('Create Magic URL session (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') ->label('event', 'account.sessions.create') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') - ->label('sdk.method', 'createMagicUrlSession') - ->label('sdk.description', '/docs/references/account/create-magic-url-session.md') + ->label('sdk.method', 'updateMagicURLSession') + ->label('sdk.description', '/docs/references/account/update-magic-url-session.md') ->label('sdk.response.code', Response::STATUS_CODE_OK) ->label('sdk.response.type', Response::CONTENT_TYPE_JSON) ->label('sdk.response.model', Response::MODEL_SESSION) @@ -1818,7 +1818,7 @@ App::post('/v1/account/recovery') }); App::put('/v1/account/recovery') - ->desc('Complete Password Recovery') + ->desc('Create Password Recovery (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') ->label('event', 'account.recovery.update') @@ -2005,7 +2005,7 @@ App::post('/v1/account/verification') }); App::put('/v1/account/verification') - ->desc('Complete Email Verification') + ->desc('Create Email Verification (confirmation)') ->groups(['api', 'account']) ->label('scope', 'public') ->label('event', 'account.verification.update') From dfe8bc6d5367202bf3dffd39a8947fa1f9d9670d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 13:11:36 +0545 Subject: [PATCH 07/22] docs for magic urls --- docs/references/account/create-magic-url-session.md | 1 + docs/references/account/update-magic-url-session.md | 3 +++ 2 files changed, 4 insertions(+) create mode 100644 docs/references/account/create-magic-url-session.md create mode 100644 docs/references/account/update-magic-url-session.md diff --git a/docs/references/account/create-magic-url-session.md b/docs/references/account/create-magic-url-session.md new file mode 100644 index 000000000..83b1534c7 --- /dev/null +++ b/docs/references/account/create-magic-url-session.md @@ -0,0 +1 @@ +Sends the user an email with a secret key for login. When the user clicks the link he is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string params to submit a request to the [PUT /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) endpoint to complete the login process. The login link sent to the user's email address is valid for 1 hour. If you are on mobile devices you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. \ No newline at end of file diff --git a/docs/references/account/update-magic-url-session.md b/docs/references/account/update-magic-url-session.md new file mode 100644 index 000000000..5bf610d86 --- /dev/null +++ b/docs/references/account/update-magic-url-session.md @@ -0,0 +1,3 @@ +Use this endpoint to complete session creation with magic URL. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) endpoint. + +Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. \ No newline at end of file From ee5745d3ce02170650c3903d4f73851cc64457d2 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 13:17:58 +0545 Subject: [PATCH 08/22] throw exception if SMTP is not available --- app/controllers/api/account.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index eb9327ae9..ea4f4728e 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -645,6 +645,10 @@ App::post('/v1/account/sessions/magic-url') /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $mails */ + if(empty(App::getEnv('_APP_SMTP_HOST'))) { + throw new Exception('SMTP Disabled', 503); + } + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); From 6a3f90a3a4a3b3a39d7761966ac10c06fc9ae611 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 13:20:04 +0545 Subject: [PATCH 09/22] for endpoints that send mails, throw exception when smtp is disabled --- app/controllers/api/account.php | 8 ++++++++ app/controllers/api/teams.php | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index ea4f4728e..9649c5e2f 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1732,6 +1732,10 @@ App::post('/v1/account/recovery') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $events */ + if(empty(App::getEnv('_APP_SMTP_HOST'))) { + throw new Exception('SMTP Disabled', 503); + } + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); @@ -1935,6 +1939,10 @@ App::post('/v1/account/verification') /** @var Appwrite\Event\Event $events */ /** @var Appwrite\Event\Event $mails */ + if(empty(App::getEnv('_APP_SMTP_HOST'))) { + throw new Exception('SMTP Disabled', 503); + } + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); diff --git a/app/controllers/api/teams.php b/app/controllers/api/teams.php index 05b8a1542..7421d7a88 100644 --- a/app/controllers/api/teams.php +++ b/app/controllers/api/teams.php @@ -274,6 +274,10 @@ App::post('/v1/teams/:teamId/memberships') /** @var Appwrite\Event\Event $audits */ /** @var Appwrite\Event\Event $mails */ + if(empty(App::getEnv('_APP_SMTP_HOST'))) { + throw new Exception('SMTP Disabled', 503); + } + $isPrivilegedUser = Auth::isPrivilegedUser(Authorization::$roles); $isAppUser = Auth::isAppUser(Authorization::$roles); From 4996a80e2233ec06dd8b7e40d7318efbe5596301 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 13:20:51 +0545 Subject: [PATCH 10/22] publicly allow 503 errors --- app/controllers/general.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/general.php b/app/controllers/general.php index 0af6d92fd..09631cf0a 100644 --- a/app/controllers/general.php +++ b/app/controllers/general.php @@ -337,6 +337,7 @@ App::error(function ($error, $utopia, $request, $response, $layout, $project) { case 412: // Error allowed publicly case 429: // Error allowed publicly case 501: // Error allowed publicly + case 503: // Error allowed publicly $code = $error->getCode(); $message = $error->getMessage(); break; From e9e83d14c308d0e7645f4cbc847d0762974ab68a Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 15:07:48 +0545 Subject: [PATCH 11/22] updates and fixes, default handler for magic login --- app/config/auth.php | 7 ++ app/controllers/api/account.php | 11 +- app/controllers/web/home.php | 29 +++++ app/views/home/auth/magicURL.phtml | 35 ++++++ public/dist/scripts/app-all.js | 2 +- public/dist/scripts/app.js | 2 +- public/images/users/magic-url.png | Bin 0 -> 2328 bytes public/scripts/routes.js | 4 + tests/e2e/Services/Account/AccountBase.php | 126 +++++++++++++++++++++ 9 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 app/views/home/auth/magicURL.phtml create mode 100644 public/images/users/magic-url.png diff --git a/app/config/auth.php b/app/config/auth.php index 11b89dd9e..170b87344 100644 --- a/app/config/auth.php +++ b/app/config/auth.php @@ -10,6 +10,13 @@ return [ 'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateSession', 'enabled' => true, ], + 'magic-url' => [ + 'name' => 'Magic URL', + 'key' => 'usersAuthMagicURL', + 'icon' => '/images/users/magic-url.png', + 'docs' => 'https://appwrite.io/docs/client/account?sdk=web#accountCreateMagicURLSession', + 'enabled' => true, + ], 'anonymous' => [ 'name' => 'Anonymous', 'key' => 'usersAuthAnonymous', diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 9649c5e2f..a18d85105 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -616,6 +616,7 @@ App::post('/v1/account/sessions/magic-url') ->desc('Create Magic URL session') ->groups(['api', 'account']) ->label('scope', 'public') + ->label('auth.type', 'magic-url') ->label('sdk.auth', []) ->label('sdk.namespace', 'account') ->label('sdk.method', 'createMagicURLSession') @@ -730,8 +731,12 @@ App::post('/v1/account/sessions/magic-url') throw new Exception('Failed to save user to DB', 500); } + if(empty($url)) { + $url = $request->getProtocol().'://'.$request->getHostname().'/auth/magic-url/success'; + } + $url = Template::parseURL($url); - $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire]); + $url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire, 'project' => $project->getId()]); $url = Template::unParseURL($url); $mails @@ -849,6 +854,10 @@ App::put('/v1/account/sessions/magic-url') if (false === $user) { throw new Exception('Failed saving user to DB', 500); } + + if (!$projectDB->deleteDocument($token)) { + throw new Exception('Failed to remove login token from DB', 500); + } $audits ->setParam('userId', $user->getId()) diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index f3eeb71c9..5805473b9 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -197,6 +197,35 @@ App::get('/auth/oauth2/success') ; }); +App::get('/auth/magic-url') + ->groups(['web', 'home']) + ->label('permission', 'public') + ->label('scope', 'home') + ->inject('request') + // ->inject('response') + ->inject('layout') + ->action(function ($request, $layout) { + /** @var Utopia\Swoole\Request $request */ + /** @var Utopia\Swoole\Response $response */ + + $page = new View(__DIR__.'/../../views/home/auth/magicURL.phtml'); + + $userId = $request->getQuery('userId'); + $secret = $request->getQuery('secret'); + $project = $request->getQuery('project'); + $page + ->setParam('userId', $userId) + ->setParam('secret', $secret) + ->setParam('project', $project); + + $layout + ->setParam('title', APP_NAME) + ->setParam('body', $page) + ->setParam('header', []) + ->setParam('footer', []) + ; + }); + App::get('/auth/oauth2/failure') ->groups(['web', 'home']) ->label('permission', 'public') diff --git a/app/views/home/auth/magicURL.phtml b/app/views/home/auth/magicURL.phtml new file mode 100644 index 000000000..1b07a2951 --- /dev/null +++ b/app/views/home/auth/magicURL.phtml @@ -0,0 +1,35 @@ + + + +
\ No newline at end of file diff --git a/public/dist/scripts/app-all.js b/public/dist/scripts/app-all.js index e4db3f821..b9e75f8b8 100644 --- a/public/dist/scripts/app-all.js +++ b/public/dist/scripts/app-all.js @@ -2239,7 +2239,7 @@ return slf.renderToken(tokens,idx,opts);} md.renderer.rules.strong_open=renderEm;md.renderer.rules.strong_close=renderEm;return md;},true);})(window);(function(window){"use strict";window.ls.container.set('rtl',function(){var rtlStock="^ا^ب^ت^ث^ج^ح^خ^د^ذ^ر^ز^س^ش^ص^ض^ط^ظ^ع^غ^ف^ق^ك^ل^م^ن^ه^و^ي^א^ב^ג^ד^ה^ו^ז^ח^ט^י^כ^ך^ל^מ^ם^נ^ן^ס^ע^פ^ף^צ^ץ^ק^ר^ש^ת^";var special=["\n"," "," ","״",'"',"_","'","!","@","#","$","^","&","%","*","(",")","+","=","-","[","]","\\","/","{","}","|",":","<",">","?",",",".","0","1","2","3","4","5","6","7","8","9"];var isRTL=function(value){for(var i=0;i","?",",",".","0","1","2","3","4","5","6","7","8","9"];var isRTL=function(value){for(var i=0;iclient->call(Client::METHOD_POST, '/account/sessions/magic-url', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + // 'url' => 'http://localhost/magiclogin', + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertEmpty($response['body']['secret']); + $this->assertIsNumeric($response['body']['expire']); + + $userId = $response['body']['userId']; + + $lastEmail = $this->getLastEmail(); + $this->assertEquals($email, $lastEmail['to'][0]['address']); + $this->assertEquals('Login', $lastEmail['subject']); + + $token = substr($lastEmail['text'], strpos($lastEmail['text'], '&secret=', 0) + 8, 256); + + $expireTime = strpos($lastEmail['text'], 'expire='.$response['body']['expire'], 0); + + $this->assertNotFalse($expireTime); + + $secretTest = strpos($lastEmail['text'], 'secret='.$response['body']['secret'], 0); + + $this->assertNotFalse($secretTest); + + $userIDTest = strpos($lastEmail['text'], 'userId='.$response['body']['userId'], 0); + + $this->assertNotFalse($userIDTest); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'url' => 'localhost/magiclogin', + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_POST, '/account/sessions/magic-url', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'email' => $email, + 'url' => 'http://remotehost/magiclogin', + ]); + + $this->assertEquals(400, $response['headers']['status-code']); + + $data['token'] = $token; + $data['id'] = $userId; + + return $data; + } + + /** + * @depends testCreateMagicUrl + */ + public function testCreateSessionWithMagicUrl($data):array + { + $id = $data['id'] ?? ''; + $token = $data['token'] ?? ''; + + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => $id, + 'secret' => $token, + ]); + + $this->assertEquals(201, $response['headers']['status-code']); + $this->assertIsArray($response['body']); + $this->assertNotEmpty($response['body']); + $this->assertNotEmpty($response['body']['$id']); + $this->assertNotEmpty($response['body']['userId']); + + /** + * Test for FAILURE + */ + $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => 'ewewe', + 'secret' => $token, + ]); + + $this->assertEquals(404, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_PUT, '/account/sessions/magic-url', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + ]), [ + 'userId' => $id, + 'secret' => 'sdasdasdasd', + ]); + + $this->assertEquals(401, $response['headers']['status-code']); + + return $data; + } } \ No newline at end of file From 2b6b08c5e16f79ab8ef149ab893feca469288ebb Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 15:30:51 +0545 Subject: [PATCH 12/22] disable auth settings for smtp dependent auth if smtp is not enabled --- app/controllers/web/console.php | 1 + app/views/console/users/index.phtml | 26 +++++++++++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/controllers/web/console.php b/app/controllers/web/console.php index 8017eda76..69d42b8aa 100644 --- a/app/controllers/web/console.php +++ b/app/controllers/web/console.php @@ -326,6 +326,7 @@ App::get('/console/users') $page ->setParam('auth', Config::getParam('auth')) ->setParam('providers', Config::getParam('providers')) + ->setParam('smtpEnabled', (!empty(App::getEnv('_APP_SMTP_HOST')))) ; $layout diff --git a/app/views/console/users/index.phtml b/app/views/console/users/index.phtml index 65b611be2..3e62f772b 100644 --- a/app/views/console/users/index.phtml +++ b/app/views/console/users/index.phtml @@ -1,6 +1,7 @@ getParam('providers', []); $auth = $this->getParam('auth', []); +$smtpEnabled = $this->getParam('smtpEnabled', false); ?>
@@ -372,19 +373,26 @@ $auth = $this->getParam('auth', []); data-failure-param-alert-text="Failed to update project auth status settings" data-failure-param-alert-classname="error"> - - + + + - + Email/Password Logo - escape($name); ?> soon - - -

- Docs -

+ escape($name); ?> soon + + +

+ SMTP Disabled +

+ + +

+ Docs +

+
From b8466063e13c61dd13ac9564327737353d219d5b Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 16:11:20 +0545 Subject: [PATCH 13/22] Apply suggestions from code review Co-authored-by: Torsten Dittmann --- app/controllers/api/account.php | 4 ++-- app/views/home/auth/magicURL.phtml | 6 +++--- docs/references/account/create-magic-url-session.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index a18d85105..c1dcafa7a 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -697,7 +697,7 @@ App::post('/v1/account/sessions/magic-url') ], ['email' => $email]); Authorization::reset(); - $userAdded = true; + $mails->setParam('event', 'users.create'); } $loginSecret = Auth::tokenGenerator(); @@ -720,7 +720,7 @@ App::post('/v1/account/sessions/magic-url') $token = $projectDB->createDocument($token->getArrayCopy()); if (false === $token) { - throw new Exception('Failed saving verification to DB', 500); + throw new Exception('Failed saving token to DB', 500); } $user->setAttribute('tokens', $token, Document::SET_TYPE_APPEND); diff --git a/app/views/home/auth/magicURL.phtml b/app/views/home/auth/magicURL.phtml index 1b07a2951..8dc8e48ed 100644 --- a/app/views/home/auth/magicURL.phtml +++ b/app/views/home/auth/magicURL.phtml @@ -10,9 +10,9 @@ document.getElementById('message').style.display = 'block'; }, 25); - getParam('userId') . "';"; ?> - getParam('secret') . "';"; ?> - getParam('project') . "';"; ?> + getParam('userId') . "';"; ?> + getParam('secret') . "';"; ?> + getParam('project') . "';"; ?> const formData = new FormData(); formData.append('userId', userId); diff --git a/docs/references/account/create-magic-url-session.md b/docs/references/account/create-magic-url-session.md index 83b1534c7..555959856 100644 --- a/docs/references/account/create-magic-url-session.md +++ b/docs/references/account/create-magic-url-session.md @@ -1 +1 @@ -Sends the user an email with a secret key for login. When the user clicks the link he is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string params to submit a request to the [PUT /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) endpoint to complete the login process. The login link sent to the user's email address is valid for 1 hour. If you are on mobile devices you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. \ No newline at end of file +Sends the user an email with a secret key for creating a session. When the user clicks the link he is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [PUT /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) endpoint to complete the login process. The login link sent to the user's email address is valid for 1 hour. If you are on mobile devices you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. \ No newline at end of file From fe51f50b3f3949eccb29a66be4dfe89cf534fd3d Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 16:14:54 +0545 Subject: [PATCH 14/22] requested changes --- app/controllers/api/account.php | 3 +-- app/controllers/shared/api.php | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index c1dcafa7a..3ced3d069 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -698,6 +698,7 @@ App::post('/v1/account/sessions/magic-url') Authorization::reset(); $mails->setParam('event', 'users.create'); + $audits->setParam('event', 'users.create'); } $loginSecret = Auth::tokenGenerator(); @@ -740,7 +741,6 @@ App::post('/v1/account/sessions/magic-url') $url = Template::unParseURL($url); $mails - ->setParam('event', ($userAdded ?? false) ? 'users.create' : '') ->setParam('from', $project->getId()) ->setParam('recipient', $user->getAttribute('email')) ->setParam('url', $url) @@ -763,7 +763,6 @@ App::post('/v1/account/sessions/magic-url') $audits ->setParam('userId', $user->getId()) - ->setParam('event', ($userAdded ?? false) ? 'users.create' : '') ->setParam('resource', 'users/'.$user->getId()) ; diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 0df72d057..c3134b4f8 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -141,6 +141,12 @@ App::init(function ($utopia, $request, $response, $project, $user) { } break; + case 'magic-url': + if($project->getAttribute('usersAuthMagicURL', true) === false) { + throw new Exception('Magic URL authentication is disabled for this project', 501); + } + break; + case 'anonymous': if($project->getAttribute('usersAuthAnonymous', true) === false) { throw new Exception('Anonymous authentication is disabled for this project', 501); From 4267221970eac57e96a119673920defec3c337a3 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Tue, 31 Aug 2021 16:37:55 +0545 Subject: [PATCH 15/22] Update src/Appwrite/Auth/Auth.php Co-authored-by: Torsten Dittmann --- src/Appwrite/Auth/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Auth/Auth.php b/src/Appwrite/Auth/Auth.php index ec47871b9..e4e7aa5ff 100644 --- a/src/Appwrite/Auth/Auth.php +++ b/src/Appwrite/Auth/Auth.php @@ -39,7 +39,7 @@ class Auth */ const SESSION_PROVIDER_EMAIL = 'email'; const SESSION_PROVIDER_ANONYMOUS = 'anonymous'; - const SESSION_PROVIDER_MAGIC_URL = 'magicUrl'; + const SESSION_PROVIDER_MAGIC_URL = 'magic-url'; /** * Token Expiration times. From c729972325e183cb13c0c20d7d40d9d89cb63680 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Tue, 31 Aug 2021 16:05:43 +0200 Subject: [PATCH 16/22] fix(security): replace getQuery with frontend equivalent --- app/controllers/web/home.php | 15 ++------------- app/views/home/auth/magicURL.phtml | 11 +++++++---- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/app/controllers/web/home.php b/app/controllers/web/home.php index 5805473b9..51c7c5206 100644 --- a/app/controllers/web/home.php +++ b/app/controllers/web/home.php @@ -201,22 +201,11 @@ App::get('/auth/magic-url') ->groups(['web', 'home']) ->label('permission', 'public') ->label('scope', 'home') - ->inject('request') - // ->inject('response') ->inject('layout') - ->action(function ($request, $layout) { - /** @var Utopia\Swoole\Request $request */ - /** @var Utopia\Swoole\Response $response */ + ->action(function ($layout) { + /** @var Utopia\View $layout */ $page = new View(__DIR__.'/../../views/home/auth/magicURL.phtml'); - - $userId = $request->getQuery('userId'); - $secret = $request->getQuery('secret'); - $project = $request->getQuery('project'); - $page - ->setParam('userId', $userId) - ->setParam('secret', $secret) - ->setParam('project', $project); $layout ->setParam('title', APP_NAME) diff --git a/app/views/home/auth/magicURL.phtml b/app/views/home/auth/magicURL.phtml index 8dc8e48ed..d3cae6885 100644 --- a/app/views/home/auth/magicURL.phtml +++ b/app/views/home/auth/magicURL.phtml @@ -10,11 +10,14 @@ document.getElementById('message').style.display = 'block'; }, 25); - getParam('userId') . "';"; ?> - getParam('secret') . "';"; ?> - getParam('project') . "';"; ?> + const urlSearchParams = new URLSearchParams(window.location.search); + const { + userId, + secret, + project + } = Object.fromEntries(urlSearchParams.entries()); -const formData = new FormData(); + const formData = new FormData(); formData.append('userId', userId); formData.append('secret', secret); From 8346710d4f93dbd0e589528fa3b42239453037d6 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Sep 2021 09:22:51 +0200 Subject: [PATCH 17/22] 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 3ced3d069..0eeea0483 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -757,7 +757,7 @@ App::post('/v1/account/sessions/magic-url') )) ; - $token // Hide secret for clients, sp + $token // Hide secret for clients ->setAttribute('secret', ($isPrivilegedUser || $isAppUser) ? $loginSecret : ''); From 5bdebe55d7661b0f19ce7384b5f2c3a80999a742 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Sep 2021 09:23:08 +0200 Subject: [PATCH 18/22] 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 0eeea0483..2c7bb1b76 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -799,7 +799,7 @@ App::put('/v1/account/sessions/magic-url') /** @var Appwrite\Database\Database $projectDB */ /** @var Appwrite\Event\Event $audits */ - $profile = $projectDB->getCollectionFirst([ // Get user by email address + $profile = $projectDB->getCollectionFirst([ // Get user by user ID 'limit' => 1, 'filters' => [ '$collection='.Database::SYSTEM_COLLECTION_USERS, From bcc8c0a9cd13595723cd172f65b400cb97e41153 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Sep 2021 09:29:12 +0200 Subject: [PATCH 19/22] fix(magic-url): verify e-mail address on user --- app/controllers/api/account.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 2c7bb1b76..ba9b13cc5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -795,8 +795,13 @@ App::put('/v1/account/sessions/magic-url') ->inject('geodb') ->inject('audits') ->action(function ($userId, $secret, $request, $response, $projectDB, $locale, $geodb, $audits) { + /** @var string $userId */ + /** @var string $secret */ + /** @var Utopia\Swoole\Request $request */ /** @var Appwrite\Utopia\Response $response */ /** @var Appwrite\Database\Database $projectDB */ + /** @var Utopia\Locale\Locale $locale */ + /** @var MaxMind\Db\Reader $geodb */ /** @var Appwrite\Event\Event $audits */ $profile = $projectDB->getCollectionFirst([ // Get user by user ID @@ -846,6 +851,7 @@ App::put('/v1/account/sessions/magic-url') throw new Exception('Failed saving session to DB', 500); } + $profile->setAttribute('emailVerification', true); $profile->setAttribute('sessions', $session, Document::SET_TYPE_APPEND); $user = $projectDB->updateDocument($profile->getArrayCopy()); @@ -853,7 +859,7 @@ App::put('/v1/account/sessions/magic-url') if (false === $user) { throw new Exception('Failed saving user to DB', 500); } - + if (!$projectDB->deleteDocument($token)) { throw new Exception('Failed to remove login token from DB', 500); } From 98a21c475d7df5d8c461f799517a80e841c12231 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Sep 2021 09:29:25 +0200 Subject: [PATCH 20/22] fix(mails-worker): shorter switch case --- app/workers/mails.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/workers/mails.php b/app/workers/mails.php index 578c71ce9..9480cf8e3 100644 --- a/app/workers/mails.php +++ b/app/workers/mails.php @@ -47,17 +47,13 @@ class MailsV1 extends Worker $body = Template::fromFile(__DIR__ . '/../config/locale/templates/email-base.tpl'); $subject = ''; switch ($type) { - case MAIL_TYPE_RECOVERY: - $subject = $locale->getText("$prefix.subject"); - break; case MAIL_TYPE_INVITATION: $subject = \sprintf($locale->getText("$prefix.subject"), $this->args['team'], $project); $body->setParam('{{owner}}', $this->args['owner']); $body->setParam('{{team}}', $this->args['team']); break; + case MAIL_TYPE_RECOVERY: case MAIL_TYPE_VERIFICATION: - $subject = $locale->getText("$prefix.subject"); - break; case MAIL_TYPE_MAGIC_SESSION: $subject = $locale->getText("$prefix.subject"); break; From 08a70414395c6c277f38c7632f61e7cdd769d2c5 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Wed, 1 Sep 2021 18:45:30 +0200 Subject: [PATCH 21/22] update docs --- docs/references/account/create-magic-url-session.md | 2 +- docs/references/account/update-magic-url-session.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/references/account/create-magic-url-session.md b/docs/references/account/create-magic-url-session.md index 555959856..4be53f138 100644 --- a/docs/references/account/create-magic-url-session.md +++ b/docs/references/account/create-magic-url-session.md @@ -1 +1 @@ -Sends the user an email with a secret key for creating a session. When the user clicks the link he is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [PUT /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) endpoint to complete the login process. The login link sent to the user's email address is valid for 1 hour. If you are on mobile devices you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. \ No newline at end of file +Sends the user an email with a secret key for creating a session. When the user clicks the link in the email, the user is redirected back to the URL you provided with the secret key and userId values attached to the URL query string. Use the query string parameters to submit a request to the [PUT /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) endpoint to complete the login process. The link sent to the user's email address is valid for 1 hour. If you are on a mobile device you can leave the URL parameter empty, so that the login completion will be handled by your Appwrite instance by default. \ No newline at end of file diff --git a/docs/references/account/update-magic-url-session.md b/docs/references/account/update-magic-url-session.md index 5bf610d86..b85036082 100644 --- a/docs/references/account/update-magic-url-session.md +++ b/docs/references/account/update-magic-url-session.md @@ -1,3 +1,3 @@ -Use this endpoint to complete session creation with magic URL. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) endpoint. +Use this endpoint to complete creating the session with the Magic URL. Both the **userId** and **secret** arguments will be passed as query parameters to the redirect URL you have provided when sending your request to the [POST /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) endpoint. Please note that in order to avoid a [Redirect Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) the only valid redirect URLs are the ones from domains you have set when adding your platforms in the console interface. \ No newline at end of file From cf912993b41d1b80a654c03cf77eaa9eb7153763 Mon Sep 17 00:00:00 2001 From: Damodar Lohani Date: Thu, 2 Sep 2021 11:06:25 +0545 Subject: [PATCH 22/22] fix default magic login handling url --- 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 ba9b13cc5..d007ea1fa 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -733,7 +733,7 @@ App::post('/v1/account/sessions/magic-url') } if(empty($url)) { - $url = $request->getProtocol().'://'.$request->getHostname().'/auth/magic-url/success'; + $url = $request->getProtocol().'://'.$request->getHostname().'/auth/magic-url'; } $url = Template::parseURL($url);