From 9ae2948d0ec2c7598d47f0adfe8102514dbbc1b7 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 22 Feb 2024 13:03:56 +0100 Subject: [PATCH 1/7] fix: mfa email template --- .../locale/templates/email-mfa-challenge.tpl | 16 ++++ app/config/locale/translations/en.json | 14 ++- app/controllers/api/account.php | 86 ++++++++++++++++++- src/Appwrite/Platform/Workers/Mails.php | 8 ++ 4 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 app/config/locale/templates/email-mfa-challenge.tpl diff --git a/app/config/locale/templates/email-mfa-challenge.tpl b/app/config/locale/templates/email-mfa-challenge.tpl new file mode 100644 index 0000000000..3fc4005cb2 --- /dev/null +++ b/app/config/locale/templates/email-mfa-challenge.tpl @@ -0,0 +1,16 @@ +

{{hello}}

+ +

{{description}}

+ + + + + +
+

{{otp}}

+
+ +

{{clientInfo}}

+ +

{{thanks}}

+

{{signature}}

diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index dfa5ebe32a..438cdbde58 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -4,13 +4,13 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Account Verification", - "emails.verification.hello": "Hey {{user}}", + "emails.verification.hello": "Hello {{user}}", "emails.verification.body": "Follow this link to verify your email address.", "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": "{{project}} Login", - "emails.magicSession.hello": "Hello,", + "emails.magicSession.hello": "Hello {{user}}", "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{project}} account. This link will expire in 1 hour.", "emails.magicSession.buttonText": "Sign in to {{project}}", "emails.magicSession.optionUrl": "If you are unable to sign in using the button above, please visit the following link:", @@ -19,12 +19,18 @@ "emails.magicSession.thanks": "Thanks,", "emails.magicSession.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", - "emails.otpSession.hello": "Hello,", + "emails.otpSession.hello": "Hello {{user}}", "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{project}} account. This code will expire in 15 minutes.", "emails.otpSession.clientInfo": "This sign in was requested using {{agentClient}} on {{agentDevice}} {{agentOs}}. If you didn't request the sign in, you can safely ignore this email.", "emails.otpSession.securityPhrase": "Security phrase for this email is {{phrase}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.otpSession.thanks": "Thanks,", "emails.otpSession.signature": "{{project}} team", + "emails.mfaChallenge.subject": "Verification Code for {{project}}", + "emails.mfaChallenge.hello": "Hello {{user}}", + "emails.mfaChallenge.description": "Enter the following verification code to verify your email and activate two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", + "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", + "emails.mfaChallenge.thanks": "Thanks,", + "emails.mfaChallenge.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", "emails.recovery.hello": "Hello {{user}}", "emails.recovery.body": "Follow this link to reset your {{project}} password.", @@ -32,7 +38,7 @@ "emails.recovery.thanks": "Thanks", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation to %s Team at %s", - "emails.invitation.hello": "Hello", + "emails.invitation.hello": "Hello {{user}}", "emails.invitation.body": "This mail was sent to you because {{owner}} wanted to invite you to become a member of the {{team}} team at {{project}}.", "emails.invitation.footer": "If you are not interested, you can ignore this message.", "emails.invitation.thanks": "Thanks", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3f29bc1313..d054af7f22 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3707,11 +3707,13 @@ App::post('/v1/account/mfa/challenge') ->inject('response') ->inject('dbForProject') ->inject('user') + ->inject('locale') + ->inject('project') + ->inject('request') ->inject('queueForEvents') ->inject('queueForMessaging') ->inject('queueForMails') - ->inject('locale') - ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails, Locale $locale) { + ->action(function (string $factor, Response $response, Database $dbForProject, Document $user, Locale $locale, Document $project, Request $request, Event $queueForEvents, Messaging $queueForMessaging, Mail $queueForMails) { $expire = DateTime::addSeconds(new \DateTime(), Auth::TOKEN_EXPIRATION_CONFIRM); $code = Auth::codeGenerator(); @@ -3764,9 +3766,85 @@ App::post('/v1/account/mfa/challenge') throw new Exception(Exception::USER_EMAIL_NOT_VERIFIED); } + $subject = $locale->getText("emails.mfaChallenge.subject"); + $customTemplate = $project->getAttribute('templates', [])['email.mfaChallenge-' . $locale->default] ?? []; + + $detector = new Detector($request->getUserAgent('UNKNOWN')); + $agentOs = $detector->getOS(); + $agentClient = $detector->getClient(); + $agentDevice = $detector->getDevice(); + + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-challenge.tpl'); + $message + ->setParam('{{hello}}', $locale->getText("emails.mfaChallenge.hello")) + ->setParam('{{description}}', $locale->getText("emails.mfaChallenge.description")) + ->setParam('{{clientInfo}}', $locale->getText("emails.mfaChallenge.clientInfo")) + ->setParam('{{thanks}}', $locale->getText("emails.mfaChallenge.thanks")) + ->setParam('{{signature}}', $locale->getText("emails.mfaChallenge.signature")); + + $body = $message->render(); + + $smtp = $project->getAttribute('smtp', []); + $smtpEnabled = $smtp['enabled'] ?? false; + + $senderEmail = App::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM); + $senderName = App::getEnv('_APP_SYSTEM_EMAIL_NAME', APP_NAME . ' Server'); + $replyTo = ""; + + if ($smtpEnabled) { + if (!empty($smtp['senderEmail'])) { + $senderEmail = $smtp['senderEmail']; + } + if (!empty($smtp['senderName'])) { + $senderName = $smtp['senderName']; + } + if (!empty($smtp['replyTo'])) { + $replyTo = $smtp['replyTo']; + } + + $queueForMails + ->setSmtpHost($smtp['host'] ?? '') + ->setSmtpPort($smtp['port'] ?? '') + ->setSmtpUsername($smtp['username'] ?? '') + ->setSmtpPassword($smtp['password'] ?? '') + ->setSmtpSecure($smtp['secure'] ?? ''); + + if (!empty($customTemplate)) { + if (!empty($customTemplate['senderEmail'])) { + $senderEmail = $customTemplate['senderEmail']; + } + if (!empty($customTemplate['senderName'])) { + $senderName = $customTemplate['senderName']; + } + if (!empty($customTemplate['replyTo'])) { + $replyTo = $customTemplate['replyTo']; + } + + $body = $customTemplate['message'] ?? ''; + $subject = $customTemplate['subject'] ?? $subject; + } + + $queueForMails + ->setSmtpReplyTo($replyTo) + ->setSmtpSenderEmail($senderEmail) + ->setSmtpSenderName($senderName); + } + + $emailVariables = [ + 'direction' => $locale->getText('settings.direction'), + /* {{user}}, {{project}} and {{otp}} are required in the templates */ + 'user' => $user->getAttribute('name'), + 'project' => $project->getAttribute('name'), + 'otp' => $code, + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN' + ]; + $queueForMails - ->setSubject("{$code} is your 6-digit code") - ->setBody($code) + ->setSubject($subject) + ->setBody($body) + ->setVariables($emailVariables) ->setRecipient($user->getAttribute('email')) ->trigger(); break; diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php index ac9c44c3b9..b331f83d06 100644 --- a/src/Appwrite/Platform/Workers/Mails.php +++ b/src/Appwrite/Platform/Workers/Mails.php @@ -32,6 +32,11 @@ class Mails extends Action ->callback(fn (Message $message, Registry $register, Log $log) => $this->action($message, $register, $log)); } + protected array $richTextParams = [ + 'b' => '', + '/b' => '', + ]; + /** * @param Message $message * @param Registry $register @@ -81,6 +86,9 @@ class Mails extends Action // TODO: hotfix for redirect param $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: $key !== 'redirect'); } + foreach ($this->richTextParams as $key => $value) { + $bodyTemplate->setParam('{{' . $key . '}}', $value, escapeHtml: false); + } $body = $bodyTemplate->render(); $subjectTemplate = Template::fromString($subject); From f577811c044e1a0486648100ac23951f2d9fd581 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 22 Feb 2024 13:47:01 +0100 Subject: [PATCH 2/7] fix: formatting on emails --- .../locale/templates/email-inner-base.tpl | 2 +- app/config/locale/translations/en.json | 18 +++---- app/controllers/api/account.php | 51 ++++++++++++------- phpunit.xml | 2 +- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/app/config/locale/templates/email-inner-base.tpl b/app/config/locale/templates/email-inner-base.tpl index 52e1093ffb..aa3457412d 100644 --- a/app/config/locale/templates/email-inner-base.tpl +++ b/app/config/locale/templates/email-inner-base.tpl @@ -1,4 +1,4 @@ -

{{hello}},

+

{{hello}}

{{body}}

{{redirect}}

{{footer}}

diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 438cdbde58..22a132964e 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -5,24 +5,24 @@ "emails.sender": "%s Team", "emails.verification.subject": "Account Verification", "emails.verification.hello": "Hello {{user}}", - "emails.verification.body": "Follow this link to verify your email address.", + "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "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": "{{project}} Login", "emails.magicSession.hello": "Hello {{user}}", - "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{project}} account. This link will expire in 1 hour.", + "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{b}}{{project}}{{/b}} account. This link will expire in 1 hour.", "emails.magicSession.buttonText": "Sign in to {{project}}", "emails.magicSession.optionUrl": "If you are unable to sign in using the button above, please visit the following link:", - "emails.magicSession.clientInfo": "This sign in was requested using {{agentClient}} on {{agentDevice}} {{agentOs}}. If you didn't request the sign in, you can safely ignore this email.", - "emails.magicSession.securityPhrase": "Security phrase for this email is {{phrase}}. You can trust this email if this phrase matches the phrase shown during sign in.", + "emails.magicSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", + "emails.magicSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.magicSession.thanks": "Thanks,", "emails.magicSession.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", "emails.otpSession.hello": "Hello {{user}}", - "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{project}} account. This code will expire in 15 minutes.", - "emails.otpSession.clientInfo": "This sign in was requested using {{agentClient}} on {{agentDevice}} {{agentOs}}. If you didn't request the sign in, you can safely ignore this email.", - "emails.otpSession.securityPhrase": "Security phrase for this email is {{phrase}}. You can trust this email if this phrase matches the phrase shown during sign in.", + "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", + "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", + "emails.otpSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.otpSession.thanks": "Thanks,", "emails.otpSession.signature": "{{project}} team", "emails.mfaChallenge.subject": "Verification Code for {{project}}", @@ -33,13 +33,13 @@ "emails.mfaChallenge.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", "emails.recovery.hello": "Hello {{user}}", - "emails.recovery.body": "Follow this link to reset your {{project}} password.", + "emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.", "emails.recovery.footer": "If you didn’t ask to reset your password, you can ignore this message.", "emails.recovery.thanks": "Thanks", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation to %s Team at %s", "emails.invitation.hello": "Hello {{user}}", - "emails.invitation.body": "This mail was sent to you because {{owner}} wanted to invite you to become a member of the {{team}} team at {{project}}.", + "emails.invitation.body": "This mail was sent to you because {{b}}{{owner}}{{/b}} wanted to invite you to become a member of the {{b}}{{team}}{{/b}} team at {{b}}{{project}}{{/b}}.", "emails.invitation.footer": "If you are not interested, you can ignore this message.", "emails.invitation.thanks": "Thanks", "emails.invitation.signature": "{{project}} team", diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index d054af7f22..a68a882e43 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1259,15 +1259,14 @@ App::post('/v1/account/tokens/magic-url') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */ - 'user' => '', - 'team' => '', + /* {{user}}, {{redirect}} and {{project}} are required in default and custom templates */ + 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'redirect' => $url, - 'agentDevice' => '' . ( $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN') . '', - 'agentClient' => '' . ($agentClient['clientName'] ?? 'UNKNOWN') . '', - 'agentOs' => '' . ($agentOs['osName'] ?? 'UNKNOWN') . '', - 'phrase' => '' . (!empty($phrase) ? $phrase : '') . '' + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + 'phrase' => !empty($phrase) ? $phrase : '' ]; $queueForMails @@ -1487,15 +1486,14 @@ App::post('/v1/account/tokens/email') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}} ,{{team}}, {{project}} and {{otp}} are required in the templates */ - 'user' => '', - 'team' => '', + /* {{user}}, {{project}} and {{otp}} are required in the templates */ + 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'otp' => $tokenSecret, - 'agentDevice' => '' . ( $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN') . '', - 'agentClient' => '' . ($agentClient['clientName'] ?? 'UNKNOWN') . '', - 'agentOs' => '' . ($agentOs['osName'] ?? 'UNKNOWN') . '', - 'phrase' => '' . (!empty($phrase) ? $phrase : '') . '' + 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', + 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', + 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', + 'phrase' => !empty($phrase) ? $phrase : '' ]; $queueForMails @@ -2953,9 +2951,8 @@ App::post('/v1/account/recovery') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */ + /* {{user}}, {{redirect}} and {{project}} are required in default and custom templates */ 'user' => $profile->getAttribute('name'), - 'team' => '', 'redirect' => $url, 'project' => $projectName ]; @@ -3200,9 +3197,8 @@ App::post('/v1/account/verification') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{team}}, {{redirect}} and {{project}} are required in default and custom templates */ + /* {{user}}, {{redirect}} and {{project}} are required in default and custom templates */ 'user' => $user->getAttribute('name'), - 'team' => '', 'redirect' => $url, 'project' => $projectName ]; @@ -3745,6 +3741,22 @@ App::post('/v1/account/mfa/challenge') throw new Exception(Exception::USER_PHONE_NOT_VERIFIED); } + $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl'); + + $customTemplate = $project->getAttribute('templates', [])['sms.mfa-challenge-' . $locale->default] ?? []; + if (!empty($customTemplate)) { + $message = $customTemplate['message'] ?? $message; + } + + $messageContent = Template::fromString($locale->getText("sms.verification.body")); + $messageContent + ->setParam('{{project}}', $project->getAttribute('name')) + ->setParam('{{secret}}', $code); + $messageContent = \strip_tags($messageContent->render()); + $message = $message->setParam('{{token}}', $messageContent); + + $message = $message->render(); + $queueForMessaging ->setType(MESSAGE_SEND_TYPE_INTERNAL) ->setMessage(new Document([ @@ -3753,7 +3765,8 @@ App::post('/v1/account/mfa/challenge') 'content' => $code, ], ])) - ->setRecipients([$user->getAttribute('phone')]); + ->setRecipients([$user->getAttribute('phone')]) + ->setProviderType(MESSAGE_TYPE_SMS); break; case 'email': if (empty(App::getEnv('_APP_SMTP_HOST'))) { diff --git a/phpunit.xml b/phpunit.xml index 90ebd4225f..e772866051 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="false" + stopOnFailure="true" > From c1ae63777e34e5cd533157246eb1f18f9a07ab80 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 22 Feb 2024 13:50:38 +0100 Subject: [PATCH 3/7] revert: phpunit.xml change --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index e772866051..90ebd4225f 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -6,7 +6,7 @@ convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" - stopOnFailure="true" + stopOnFailure="false" > From f5cdd5e4cc16876d0223ad15916505a21bbd10da Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 22 Feb 2024 14:34:46 +0100 Subject: [PATCH 4/7] fix: comma in email templates --- app/config/locale/translations/en.json | 12 ++++++------ src/Appwrite/Platform/Workers/Mails.php | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 22a132964e..12c296d78f 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -4,13 +4,13 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Account Verification", - "emails.verification.hello": "Hello {{user}}", + "emails.verification.hello": "Hello {{user}},", "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "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": "{{project}} Login", - "emails.magicSession.hello": "Hello {{user}}", + "emails.magicSession.hello": "Hello {{user}},", "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{b}}{{project}}{{/b}} account. This link will expire in 1 hour.", "emails.magicSession.buttonText": "Sign in to {{project}}", "emails.magicSession.optionUrl": "If you are unable to sign in using the button above, please visit the following link:", @@ -19,26 +19,26 @@ "emails.magicSession.thanks": "Thanks,", "emails.magicSession.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", - "emails.otpSession.hello": "Hello {{user}}", + "emails.otpSession.hello": "Hello {{user}},", "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", "emails.otpSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.otpSession.thanks": "Thanks,", "emails.otpSession.signature": "{{project}} team", "emails.mfaChallenge.subject": "Verification Code for {{project}}", - "emails.mfaChallenge.hello": "Hello {{user}}", + "emails.mfaChallenge.hello": "Hello {{user}},", "emails.mfaChallenge.description": "Enter the following verification code to verify your email and activate two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", "emails.mfaChallenge.thanks": "Thanks,", "emails.mfaChallenge.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", - "emails.recovery.hello": "Hello {{user}}", + "emails.recovery.hello": "Hello {{user}},", "emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.", "emails.recovery.footer": "If you didn’t ask to reset your password, you can ignore this message.", "emails.recovery.thanks": "Thanks", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation to %s Team at %s", - "emails.invitation.hello": "Hello {{user}}", + "emails.invitation.hello": "Hello {{user}},", "emails.invitation.body": "This mail was sent to you because {{b}}{{owner}}{{/b}} wanted to invite you to become a member of the {{b}}{{team}}{{/b}} team at {{b}}{{project}}{{/b}}.", "emails.invitation.footer": "If you are not interested, you can ignore this message.", "emails.invitation.thanks": "Thanks", diff --git a/src/Appwrite/Platform/Workers/Mails.php b/src/Appwrite/Platform/Workers/Mails.php index b331f83d06..57d1baa978 100644 --- a/src/Appwrite/Platform/Workers/Mails.php +++ b/src/Appwrite/Platform/Workers/Mails.php @@ -32,6 +32,9 @@ class Mails extends Action ->callback(fn (Message $message, Registry $register, Log $log) => $this->action($message, $register, $log)); } + /** + * @var array + */ protected array $richTextParams = [ 'b' => '', '/b' => '', From af6091e04aafcfd5f7d74b1f2fe884c12d51a1cd Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Thu, 22 Feb 2024 14:37:01 +0100 Subject: [PATCH 5/7] fix: template name typo for mfa challenge --- 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 a68a882e43..3dcda9d898 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -3743,7 +3743,7 @@ App::post('/v1/account/mfa/challenge') $message = Template::fromFile(__DIR__ . '/../../config/locale/templates/sms-base.tpl'); - $customTemplate = $project->getAttribute('templates', [])['sms.mfa-challenge-' . $locale->default] ?? []; + $customTemplate = $project->getAttribute('templates', [])['sms.mfaChallenge-' . $locale->default] ?? []; if (!empty($customTemplate)) { $message = $customTemplate['message'] ?? $message; } From 6a8140a80f5009b427c6d7149c6c21205bf104d6 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 23 Feb 2024 00:52:20 +0100 Subject: [PATCH 6/7] fix: move comma from language files into templates --- app/config/locale/templates/email-inner-base.tpl | 2 +- app/config/locale/templates/email-magic-url.tpl | 2 +- app/config/locale/templates/email-mfa-challenge.tpl | 2 +- app/config/locale/templates/email-otp.tpl | 2 +- app/config/locale/translations/en.json | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/config/locale/templates/email-inner-base.tpl b/app/config/locale/templates/email-inner-base.tpl index aa3457412d..52e1093ffb 100644 --- a/app/config/locale/templates/email-inner-base.tpl +++ b/app/config/locale/templates/email-inner-base.tpl @@ -1,4 +1,4 @@ -

{{hello}}

+

{{hello}},

{{body}}

{{redirect}}

{{footer}}

diff --git a/app/config/locale/templates/email-magic-url.tpl b/app/config/locale/templates/email-magic-url.tpl index 21988c5bc1..def1ea2395 100644 --- a/app/config/locale/templates/email-magic-url.tpl +++ b/app/config/locale/templates/email-magic-url.tpl @@ -1,4 +1,4 @@ -

{{hello}}

+

{{hello}},

{{optionButton}}

diff --git a/app/config/locale/templates/email-mfa-challenge.tpl b/app/config/locale/templates/email-mfa-challenge.tpl index 3fc4005cb2..cf09448ca5 100644 --- a/app/config/locale/templates/email-mfa-challenge.tpl +++ b/app/config/locale/templates/email-mfa-challenge.tpl @@ -1,4 +1,4 @@ -

{{hello}}

+

{{hello}},

{{description}}

diff --git a/app/config/locale/templates/email-otp.tpl b/app/config/locale/templates/email-otp.tpl index 84802c1603..9552185f84 100644 --- a/app/config/locale/templates/email-otp.tpl +++ b/app/config/locale/templates/email-otp.tpl @@ -1,4 +1,4 @@ -

{{hello}}

+

{{hello}},

{{description}}

diff --git a/app/config/locale/translations/en.json b/app/config/locale/translations/en.json index 12c296d78f..22a132964e 100644 --- a/app/config/locale/translations/en.json +++ b/app/config/locale/translations/en.json @@ -4,13 +4,13 @@ "settings.direction": "ltr", "emails.sender": "%s Team", "emails.verification.subject": "Account Verification", - "emails.verification.hello": "Hello {{user}},", + "emails.verification.hello": "Hello {{user}}", "emails.verification.body": "Follow this link to verify your email address to your {{b}}{{project}}{{/b}} account.", "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": "{{project}} Login", - "emails.magicSession.hello": "Hello {{user}},", + "emails.magicSession.hello": "Hello {{user}}", "emails.magicSession.optionButton": "Click the button below to securely sign in to your {{b}}{{project}}{{/b}} account. This link will expire in 1 hour.", "emails.magicSession.buttonText": "Sign in to {{project}}", "emails.magicSession.optionUrl": "If you are unable to sign in using the button above, please visit the following link:", @@ -19,26 +19,26 @@ "emails.magicSession.thanks": "Thanks,", "emails.magicSession.signature": "{{project}} team", "emails.otpSession.subject": "OTP for {{project}} Login", - "emails.otpSession.hello": "Hello {{user}},", + "emails.otpSession.hello": "Hello {{user}}", "emails.otpSession.description": "Enter the following verification code when prompted to securely sign in to your {{b}}{{project}}{{/b}} account. This code will expire in 15 minutes.", "emails.otpSession.clientInfo": "This sign in was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the sign in, you can safely ignore this email.", "emails.otpSession.securityPhrase": "Security phrase for this email is {{b}}{{phrase}}{{/b}}. You can trust this email if this phrase matches the phrase shown during sign in.", "emails.otpSession.thanks": "Thanks,", "emails.otpSession.signature": "{{project}} team", "emails.mfaChallenge.subject": "Verification Code for {{project}}", - "emails.mfaChallenge.hello": "Hello {{user}},", + "emails.mfaChallenge.hello": "Hello {{user}}", "emails.mfaChallenge.description": "Enter the following verification code to verify your email and activate two-step verification in {{b}}{{project}}{{/b}}. This code will expire in 15 minutes.", "emails.mfaChallenge.clientInfo": "This verification code was requested using {{b}}{{agentClient}}{{/b}} on {{b}}{{agentDevice}}{{/b}} {{b}}{{agentOs}}{{/b}}. If you didn't request the verification code, you can safely ignore this email.", "emails.mfaChallenge.thanks": "Thanks,", "emails.mfaChallenge.signature": "{{project}} team", "emails.recovery.subject": "Password Reset", - "emails.recovery.hello": "Hello {{user}},", + "emails.recovery.hello": "Hello {{user}}", "emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.", "emails.recovery.footer": "If you didn’t ask to reset your password, you can ignore this message.", "emails.recovery.thanks": "Thanks", "emails.recovery.signature": "{{project}} team", "emails.invitation.subject": "Invitation to %s Team at %s", - "emails.invitation.hello": "Hello {{user}},", + "emails.invitation.hello": "Hello {{user}}", "emails.invitation.body": "This mail was sent to you because {{b}}{{owner}}{{/b}} wanted to invite you to become a member of the {{b}}{{team}}{{/b}} team at {{b}}{{project}}{{/b}}.", "emails.invitation.footer": "If you are not interested, you can ignore this message.", "emails.invitation.thanks": "Thanks", From 076ffe8866d50c64e8c7c1e35c4525958541c7e3 Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Fri, 23 Feb 2024 01:46:13 +0100 Subject: [PATCH 7/7] fix: missing team variable and added mfa templates to config --- app/config/locale/templates.php | 4 +++- app/controllers/api/account.php | 26 +++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/app/config/locale/templates.php b/app/config/locale/templates.php index f2672c04a0..ac5a2acf1d 100644 --- a/app/config/locale/templates.php +++ b/app/config/locale/templates.php @@ -6,10 +6,12 @@ return [ 'magicSession', 'recovery', 'invitation', + 'mfaChallenge' ], 'sms' => [ 'verification', 'login', - 'invitation' + 'invitation', + 'mfaChallenge' ] ]; diff --git a/app/controllers/api/account.php b/app/controllers/api/account.php index 3dcda9d898..4d3a592bc5 100644 --- a/app/controllers/api/account.php +++ b/app/controllers/api/account.php @@ -1259,14 +1259,16 @@ App::post('/v1/account/tokens/magic-url') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{redirect}} and {{project}} are required in default and custom templates */ + // {{user}}, {{redirect}} and {{project}} are required in default and custom templates 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'redirect' => $url, 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', - 'phrase' => !empty($phrase) ? $phrase : '' + 'phrase' => !empty($phrase) ? $phrase : '', + // TODO: remove unnecessary team variable from this email + 'team' => '', ]; $queueForMails @@ -1486,14 +1488,16 @@ App::post('/v1/account/tokens/email') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{project}} and {{otp}} are required in the templates */ + // {{user}}, {{project}} and {{otp}} are required in the templates 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'otp' => $tokenSecret, 'agentDevice' => $agentDevice['deviceBrand'] ?? $agentDevice['deviceBrand'] ?? 'UNKNOWN', 'agentClient' => $agentClient['clientName'] ?? 'UNKNOWN', 'agentOs' => $agentOs['osName'] ?? 'UNKNOWN', - 'phrase' => !empty($phrase) ? $phrase : '' + 'phrase' => !empty($phrase) ? $phrase : '', + // TODO: remove unnecessary team variable from this email + 'team' => '', ]; $queueForMails @@ -2951,10 +2955,12 @@ App::post('/v1/account/recovery') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{redirect}} and {{project}} are required in default and custom templates */ + // {{user}}, {{redirect}} and {{project}} are required in default and custom templates 'user' => $profile->getAttribute('name'), 'redirect' => $url, - 'project' => $projectName + 'project' => $projectName, + // TODO: remove unnecessary team variable from this email + 'team' => '' ]; $queueForMails @@ -3197,10 +3203,12 @@ App::post('/v1/account/verification') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{redirect}} and {{project}} are required in default and custom templates */ + // {{user}}, {{redirect}} and {{project}} are required in default and custom templates 'user' => $user->getAttribute('name'), 'redirect' => $url, - 'project' => $projectName + 'project' => $projectName, + // TODO: remove unnecessary team variable from this email + 'team' => '', ]; $queueForMails @@ -3845,7 +3853,7 @@ App::post('/v1/account/mfa/challenge') $emailVariables = [ 'direction' => $locale->getText('settings.direction'), - /* {{user}}, {{project}} and {{otp}} are required in the templates */ + // {{user}}, {{project}} and {{otp}} are required in the templates 'user' => $user->getAttribute('name'), 'project' => $project->getAttribute('name'), 'otp' => $code,