mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Implement MFA Emails for authenticator creation and deletion
This commit is contained in:
@@ -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>
|
||||
@@ -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 can’t 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 didn’t 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 didn’t 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.",
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user