Implement MFA Emails for authenticator creation and deletion

This commit is contained in:
Bradley Schofield
2024-07-29 17:30:52 +09:00
parent faf184f86e
commit f7a115cff1
4 changed files with 222 additions and 3 deletions
+3 -1
View File
@@ -6,7 +6,9 @@ return [
'magicSession',
'recovery',
'invitation',
'mfaChallenge'
'mfaChallenge',
'mfaCreated',
'mfaRemoved',
],
'sms' => [
'verification',
@@ -0,0 +1,11 @@
{{body}}
<br/><br/>
<table border="0" cellspacing="0" cellpadding="0" style="padding-top: 10px; padding-bottom: 10px; display: inline-block;">
<tr>
<td align="center" style="border-radius: 8px; background-color: #FD366E;">
<a rel="noopener" target="_blank" href="{{redirect}}" style="font-size: 14px; font-family: Inter; color: #ffffff; text-decoration: none; border-radius: 8px; padding: 9px 14px; border: 1px solid #FD366E; display: inline-block;">{{buttonText}}</a>
</td>
</tr>
</table>
+14
View File
@@ -31,6 +31,20 @@
"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.mfaCreated.subject": "Two-factor authentication is enabled in your {{project}} account",
"emails.mfaCreated.hello": "Hello {{user}}",
"emails.mfaCreated.body": "Two-factor authentication has been successfully enabled for your {{project}} account.<br/><br/>Please make sure to store your security codes safely, so you can use them if you lose or cant access your mobile device. Each recovery code can only be used once, but you can re-generate a new set of 6 codes at any time.",
"emails.mfaCreated.footer": "If you didnt enable two-factor authentication, please contact us immediately.",
"emails.mfaCreated.thanks": "Thanks,",
"emails.mfaCreated.signature": "{{project}} team",
"emails.mfaCreated.buttonText": "Visit your account",
"emails.mfaRemoved.subject": "Two-factor authentication is disabled in your {{project}} account",
"emails.mfaRemoved.hello": "Hello {{user}}",
"emails.mfaRemoved.body": "You've recently disabled two-factor authentication for your {{project}} account. You can always enable it again through your {{project}} account.",
"emails.mfaRemoved.footer": "If you didnt disable two-factor authentication, please contact us immediately.",
"emails.mfaRemoved.thanks": "Thanks,",
"emails.mfaRemoved.signature": "{{project}} team",
"emails.mfaRemoved.buttonText": "Visit your account",
"emails.recovery.subject": "Password Reset",
"emails.recovery.hello": "Hello {{user}}",
"emails.recovery.body": "Follow this link to reset your {{b}}{{project}}{{/b}} password.",
+194 -2
View File
@@ -3667,7 +3667,10 @@ App::put('/v1/account/mfa/authenticators/:type')
->inject('session')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $type, string $otp, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents) {
->inject('project')
->inject('locale')
->inject('queueForMails')
->action(function (string $type, string $otp, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents, Document $project, Locale $locale, Mail $queueForMails) {
$authenticator = (match ($type) {
Type::TOTP => TOTP::getAuthenticatorFromUser($user),
@@ -3703,6 +3706,99 @@ App::put('/v1/account/mfa/authenticators/:type')
$session->setAttribute('factors', $factors);
$dbForProject->updateDocument('sessions', $session->getId(), $session);
// Send email to user informing that MFA is enabled
$subject = $locale->getText("emails.mfaCreated.subject");
$customTemplate = $project->getAttribute('templates', [])['email.mfaCreated-' . $locale->default] ?? [];
// Appwrite theming for console
if ($project->getInternalId() === 'console') {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base-styled.tpl');
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-altered.tpl');
$body
->setParam('{{buttonText}}', $locale->getText("emails.mfaCreated.buttonText"))
->setParam('{{redirect}}', App::getEnv('_APP_DOMAIN') . '/console/account')
->setParam('{{body}}', $locale->getText("emails.mfaCreated.body"), escapeHtml: false)
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{user}}', $user->getAttribute('name'));
$message->setParam('{{body}}', $body->render(), escapeHtml: false);
} else {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $locale->getText("emails.mfaCreated.body"), escapeHtml: false);
}
$message
->setParam('{{hello}}', $locale->getText("emails.mfaCreated.hello"))
->setParam('{{footer}}', $locale->getText("emails.mfaCreated.footer"))
->setParam('{{thanks}}', $locale->getText("emails.mfaCreated.thanks"))
->setParam('{{signature}}', $locale->getText("emails.mfaCreated.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
$smtpEnabled = $smtp['enabled'] ?? false;
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$senderName = System::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 = [
'owner' => $user->getAttribute('name'),
'direction' => $locale->getText('settings.direction'),
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name')
];
$queueForMails
->setSubject($subject)
->setBody($body)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name'))
->setVariables($emailVariables)
->trigger()
;
$queueForEvents->setParam('userId', $user->getId());
$response->dynamic($user, Response::MODEL_ACCOUNT);
@@ -3841,7 +3937,10 @@ App::delete('/v1/account/mfa/authenticators/:type')
->inject('user')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $type, string $otp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
->inject('project')
->inject('locale')
->inject('queueForMails')
->action(function (string $type, string $otp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Locale $locale, Mail $queueForMails) {
$authenticator = (match ($type) {
Type::TOTP => TOTP::getAuthenticatorFromUser($user),
@@ -3876,6 +3975,99 @@ App::delete('/v1/account/mfa/authenticators/:type')
$dbForProject->deleteDocument('authenticators', $authenticator->getId());
$dbForProject->purgeCachedDocument('users', $user->getId());
// Send email to user informing that MFA is disabled
$subject = $locale->getText("emails.mfaRemoved.subject");
$customTemplate = $project->getAttribute('templates', [])['email.mfaRemoved-' . $locale->default] ?? [];
// Appwrite theming for console
if ($project->getInternalId() === 'console') {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base-styled.tpl');
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-altered.tpl');
$body
->setParam('{{buttonText}}', $locale->getText("emails.mfaRemoved.buttonText"))
->setParam('{{redirect}}', App::getEnv('_APP_DOMAIN') . '/console/account')
->setParam('{{body}}', $locale->getText("emails.mfaRemoved.body"), escapeHtml: false)
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{user}}', $user->getAttribute('name'));
$message->setParam('{{body}}', $body->render(), escapeHtml: false);
} else {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-inner-base.tpl');
$message->setParam('{{body}}', $locale->getText("emails.mfaRemoved.body"), escapeHtml: false);
}
$message
->setParam('{{hello}}', $locale->getText("emails.mfaRemoved.hello"))
->setParam('{{footer}}', $locale->getText("emails.mfaRemoved.footer"))
->setParam('{{thanks}}', $locale->getText("emails.mfaRemoved.thanks"))
->setParam('{{signature}}', $locale->getText("emails.mfaRemoved.signature"));
$body = $message->render();
$smtp = $project->getAttribute('smtp', []);
$smtpEnabled = $smtp['enabled'] ?? false;
$senderEmail = System::getEnv('_APP_SYSTEM_EMAIL_ADDRESS', APP_EMAIL_TEAM);
$senderName = System::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 = [
'owner' => $user->getAttribute('name'),
'direction' => $locale->getText('settings.direction'),
'user' => $user->getAttribute('name'),
'project' => $project->getAttribute('name')
];
$queueForMails
->setSubject($subject)
->setBody($body)
->setRecipient($user->getAttribute('email'))
->setName($user->getAttribute('name'))
->setVariables($emailVariables)
->trigger()
;
$queueForEvents->setParam('userId', $user->getId());
$response->noContent();