Compare commits

...
Author SHA1 Message Date
Bradley SchofieldandGitHub 4c5451febe Merge branch '1.6.x' into feat-1.5.x-mfa-emails 2024-10-04 10:58:35 +09:00
Bradley Schofield 8533b1d3ce Merge remote-tracking branch 'origin/1.6.x' into feat-1.5.x-mfa-emails 2024-10-03 11:35:05 +09:00
Bradley Schofield 4df94de1d9 Fix mfaAltered not working 2024-10-03 11:34:59 +09:00
Bradley Schofield 5eb83b5de3 Address Comments 2024-10-03 11:18:50 +09:00
Bradley Schofield 3468b25a52 Remove dialogue for API facing calls 2024-09-27 17:02:36 +09:00
Bradley Schofield f3e6b18b9f Remove console from this branch 2024-09-27 15:07:01 +09:00
Bradley Schofield 01fc574d36 Merge branch '1.6.x' into feat-1.5.x-mfa-emails
# Conflicts:
#	composer.lock
2024-09-27 15:03:33 +09:00
Bradley Schofield 3cfbe07744 Split Console and Normal Translations 2024-09-27 15:02:47 +09:00
Bradley Schofield 4100e4f019 Split Console and Client templates for MFA Altered 2024-08-29 14:42:16 +09:00
Bradley Schofield 96d39e1421 Merge remote-tracking branch 'origin/1.6.x' into feat-1.5.x-mfa-emails
# Conflicts:
#	app/config/locale/templates.php
#	app/controllers/api/account.php
2024-08-28 09:29:00 +09:00
Bradley Schofield 14e248f321 Update copy 2024-08-19 16:23:37 +09:00
Bradley Schofield db74bed93e Run Linter 2024-08-19 15:22:47 +09:00
Bradley Schofield 6487b5f980 Address Comments, update weird grammar, use correct redirect 2024-08-19 15:06:09 +09:00
Bradley SchofieldandGitHub f20e7d4beb Merge branch '1.5.x' into feat-1.5.x-mfa-emails 2024-08-19 13:24:24 +09:00
Bradley Schofield e7dc727102 Run Linter 2024-08-12 13:24:51 +09:00
Bradley Schofield f7e591eeaa Remove redirect from user facing emails 2024-08-12 13:23:15 +09:00
Bradley Schofield 7f45078ff5 Clean up code and switch to mfaEnabled/Disabled 2024-08-12 13:07:30 +09:00
Bradley Schofield dcb19c3f80 Update account.php 2024-08-02 13:32:54 +09:00
Bradley Schofield f7a115cff1 Implement MFA Emails for authenticator creation and deletion 2024-07-29 17:30:52 +09:00
7 changed files with 156 additions and 4 deletions
+2
View File
@@ -7,6 +7,8 @@ return [
'recovery',
'invitation',
'mfaChallenge',
'mfaEnabled',
'mfaDisabled',
'sessionAlert',
'otpSession'
],
@@ -0,0 +1,8 @@
<p>{{hello}},</p>
<p>{{body}}</p>
<p>{{footer}}</p>
<p style="margin-bottom: 32px">
{{thanks}},
<br/>
{{signature}}
</p>
@@ -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>
@@ -0,0 +1,6 @@
{
"emails.mfaEnabled.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.mfaEnabled.buttonText": "Visit your account",
"emails.mfaDisabled.body": "You've disabled two-factor authentication for your {{project}} account. You can always enable it again through your {{project}} account.",
"emails.mfaDisabled.buttonText": "Visit your account"
}
+14
View File
@@ -40,6 +40,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.mfaEnabled.subject": "Two-factor authentication is enabled for your {{project}} account",
"emails.mfaEnabled.hello": "Hello {{user}}",
"emails.mfaEnabled.body": "Two-factor authentication has been successfully enabled for your {{project}} account.",
"emails.mfaEnabled.footer": "If you didnt enable two-factor authentication, please contact us immediately.",
"emails.mfaEnabled.thanks": "Thanks",
"emails.mfaEnabled.signature": "{{project}} team",
"emails.mfaEnabled.buttonText": "Visit your account",
"emails.mfaDisabled.subject": "Two-factor authentication is disabled for your {{project}} account",
"emails.mfaDisabled.hello": "Hello {{user}}",
"emails.mfaDisabled.body": "You've disabled two-factor authentication for your {{project}} account. You can always enable it again through your {{project}} account.",
"emails.mfaDisabled.footer": "If you didnt disable two-factor authentication, please contact us immediately.",
"emails.mfaDisabled.thanks": "Thanks",
"emails.mfaDisabled.signature": "{{project}} team",
"emails.mfaDisabled.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.",
+110 -4
View File
@@ -3648,13 +3648,17 @@ App::patch('/v1/account/mfa')
->label('sdk.offline.key', 'current')
->param('mfa', null, new Boolean(), 'Enable or disable MFA.')
->inject('requestTimestamp')
->inject('request')
->inject('response')
->inject('user')
->inject('project')
->inject('session')
->inject('locale')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (bool $mfa, ?\DateTime $requestTimestamp, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents) {
->inject('queueForMails')
->action(function (bool $mfa, ?\DateTime $requestTimestamp, Request $request, Response $response, Document $user, Document $project, Document $session, Locale $locale, Database $dbForProject, Event $queueForEvents, Mail $queueForMails) {
$mfaAltered = $user->getAttribute('mfa') !== $mfa;
$user->setAttribute('mfa', $mfa);
$user = $dbForProject->withRequestTimestamp($requestTimestamp, fn () => $dbForProject->updateDocument('users', $user->getId(), $user));
@@ -3679,6 +3683,110 @@ App::patch('/v1/account/mfa')
$queueForEvents->setParam('userId', $user->getId());
// If MFA Changes then we need to send a email
if ($mfaAltered && !empty($user->getAttribute('email'))) {
$domain = $request->getHostname();
$protocol = $request->getProtocol();
$redirect = $protocol . '://' . $domain . '/console/account';
$type = $mfa ? 'mfaEnabled' : 'mfaDisabled';
$subject = $locale->getText("emails.{$type}.subject");
$customTemplate = $project->getAttribute('templates', [])["email.{$type}-" . $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-console.tpl');
$consoleLocale = clone $locale;
$consoleLocale->default = 'console-' . $locale->default;
$buttonText = $consoleLocale->getText("emails.{$type}.buttonText", null) ?? $locale->getText("emails.{$type}.buttonText");
$bodyText = $consoleLocale->getText("emails.{$type}.body", null) ?? $locale->getText("emails.{$type}.body");
$body
->setParam('{{buttonText}}', $buttonText)
->setParam('{{redirect}}', $redirect)
->setParam('{{body}}', $bodyText, escapeHtml: false);
$message->setParam('{{body}}', $body->render(), escapeHtml: false);
} else {
$message = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-mfa-altered-client.tpl');
$message->setParam('{{body}}', $locale->getText("emails.{$type}.body"), escapeHtml: false);
}
$message
->setParam('{{hello}}', $locale->getText("emails.{$type}.hello"))
->setParam('{{footer}}', $locale->getText("emails.{$type}.footer"))
->setParam('{{thanks}}', $locale->getText("emails.{$type}.thanks"))
->setParam('{{signature}}', $locale->getText("emails.{$type}.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 = [
'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()
;
}
$response->dynamic($user, Response::MODEL_ACCOUNT);
});
@@ -3811,7 +3919,6 @@ App::put('/v1/account/mfa/authenticators/:type')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $type, string $otp, Response $response, Document $user, Document $session, Database $dbForProject, Event $queueForEvents) {
$authenticator = (match ($type) {
Type::TOTP => TOTP::getAuthenticatorFromUser($user),
default => null
@@ -3984,7 +4091,6 @@ App::delete('/v1/account/mfa/authenticators/:type')
->inject('dbForProject')
->inject('queueForEvents')
->action(function (string $type, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
$authenticator = (match ($type) {
Type::TOTP => TOTP::getAuthenticatorFromUser($user),
default => null
+5
View File
@@ -1068,6 +1068,11 @@ foreach ($locales as $locale) {
}
}
$consolePath = __DIR__ . '/config/locale/translations/console/' . \substr($code, 0, 2) . '.json';
if (file_exists($consolePath)) {
Locale::setLanguageFromJSON('console-'.$code, $consolePath);
}
Locale::setLanguageFromJSON($code, $path);
}