From a72b55025adee748e1aa6bd3440c70e20f629556 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 25 Nov 2020 17:38:10 +0100 Subject: [PATCH] Send a notification email when MFA is enabled (#3756) --- .../lib/form/MultifactorManageForm.class.php | 59 ++++++++++++++++++- wcfsetup/install/lang/de.xml | 11 ++++ wcfsetup/install/lang/en.xml | 11 ++++ 3 files changed, 80 insertions(+), 1 deletion(-) diff --git a/wcfsetup/install/files/lib/form/MultifactorManageForm.class.php b/wcfsetup/install/files/lib/form/MultifactorManageForm.class.php index 1dad21c363..ec5e8d6d34 100644 --- a/wcfsetup/install/files/lib/form/MultifactorManageForm.class.php +++ b/wcfsetup/install/files/lib/form/MultifactorManageForm.class.php @@ -4,6 +4,8 @@ use wcf\data\object\type\ObjectType; use wcf\data\object\type\ObjectTypeCache; use wcf\data\user\UserEditor; use wcf\form\AbstractFormBuilderForm; +use wcf\system\background\BackgroundQueueHandler; +use wcf\system\email\SimpleEmail; use wcf\system\exception\IllegalLinkException; use wcf\system\exception\PermissionDeniedException; use wcf\system\form\builder\FormDocument; @@ -123,14 +125,24 @@ class MultifactorManageForm extends AbstractFormBuilderForm { $this->setup = $setup; + $sendEmail = false; if (!$this->hasBackupCodes()) { $this->generateBackupCodes(); + $sendEmail = true; } $this->enableMultifactorAuth(); WCF::getDB()->commitTransaction(); + // Send the email outside of the transaction. + // + // Sending the email is not an absolute requirement and queueing some external + // process that might need to be rolled back is a bit wonky from an UX perspective. + if ($sendEmail) { + $this->sendEmail(); + } + $this->saved(); } @@ -186,7 +198,7 @@ class MultifactorManageForm extends AbstractFormBuilderForm { } /** - * Enables multifactor authentication for the user. + * Enables multi-factor authentication for the user. */ protected function enableMultifactorAuth(): void { // This method intentionally does not use UserAction to prevent @@ -207,6 +219,51 @@ class MultifactorManageForm extends AbstractFormBuilderForm { ]); } + /** + * Sends an email letting the user know that multi-factor authentication + * is enabled now. + */ + protected function sendEmail(): void { + $email = new SimpleEmail(); + $email->setRecipient(WCF::getUser()); + + $email->setSubject( + WCF::getLanguage()->getDynamicVariable('wcf.user.security.multifactor.setupEmail.subject', [ + 'user' => WCF::getUser(), + 'method' => $this->method, + 'backupMethod' => $this->getBackupCodesObjectType(), + ]) + ); + $email->setHtmlMessage( + WCF::getLanguage()->getDynamicVariable('wcf.user.security.multifactor.setupEmail.body.html', [ + 'user' => WCF::getUser(), + 'method' => $this->method, + 'backupMethod' => $this->getBackupCodesObjectType(), + ]) + ); + $email->setMessage( + WCF::getLanguage()->getDynamicVariable('wcf.user.security.multifactor.setupEmail.body.plain', [ + 'user' => WCF::getUser(), + 'method' => $this->method, + 'backupMethod' => $this->getBackupCodesObjectType(), + ]) + ); + + $jobs = $email->getEmail()->getJobs(); + foreach ($jobs as $job) { + // Wait 15 minutes to give the user a bit of time to complete the process of + // storing their backup codes somewhere without distracting them by causing + // a notification to arrive. + // It also allows us to remind the user of the importance of the backup codes + // in case they simply close the tab without reading, similar to remarketing + // emails. + BackgroundQueueHandler::getInstance()->enqueueIn( + $job, + 15 * 60 + ); + } + } + /** * @inheritDoc */ diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 6c0440346c..7f696d9765 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -4933,6 +4933,17 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email} {if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} den Notfall-Code „{$usedCode[identifier]}“ zur Mehrfaktor-Authentifizierung genutzt. Dieser Code ist nun nicht mehr gültig. {plural value=$remaining 0='**Es gibt keine weiteren gültigen Codes.**' 1='Es verbleibt ein gültiger Code.' other='Es verbleiben # gültige Codes.'} {* this line ends with a space *} {if LANGUAGE_USE_INFORMAL_VARIANT}Du kannst{else}Sie können{/if} die Mehrfaktor-Authentifizierung in der Account-Sicherheit [URL:{link controller='AccountSecurity' isEmail=true}{/link}] verwalten und dort neue Notfall-Codes generieren oder die Mehrfaktor-Authentifizierung deaktivieren.]]> + + Hallo {$user->username}, + +

{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} die Mehrfaktor-Authentifizierung mit dem Verfahren {lang}wcf.user.security.multifactor.{$method->objectType}{/lang} erfolgreich aktiviert. {if LANGUAGE_USE_INFORMAL_VARIANT}Du wirst{else}Sie werden{/if} von nun an bei jedem Login den zusätzlichen Faktor benötigen.

+ +

{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} auch eine Liste von Notfallcodes erhalten, falls der zusätzliche Faktor unbrauchbar werden sollte. {if LANGUAGE_USE_INFORMAL_VARIANT}Hast du dir{else}Haben Sie sich{/if} die Notfallcodes notiert? Falls {if LANGUAGE_USE_INFORMAL_VARIANT}du es vergessen hast, kannst du{else}Sie es vergessen haben, können Sie{/if} dies nachholen, indem {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} in der Verwaltung der Notfallcodes neue Notfallcodes {if LANGUAGE_USE_INFORMAL_VARIANT}generierst{else}generieren{/if}.

]]>
+ username}, + +{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} die Mehrfaktor-Authentifizierung mit dem Verfahren „{lang}wcf.user.security.multifactor.{$method->objectType}{/lang}“ erfolgreich aktiviert. {if LANGUAGE_USE_INFORMAL_VARIANT}Du wirst{else}Sie werden{/if} von nun an bei jedem Login den zusätzlichen Faktor benötigen. + +{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} auch eine Liste von Notfallcodes erhalten, falls der zusätzliche Faktor unbrauchbar werden sollte. **{if LANGUAGE_USE_INFORMAL_VARIANT}Hast du dir{else}Haben Sie sich{/if} die Notfallcodes notiert?** Falls {if LANGUAGE_USE_INFORMAL_VARIANT}du es vergessen hast, kannst du{else}Sie es vergessen haben, können Sie{/if} dies nachholen, indem {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} in der Verwaltung der Notfallcodes [URL:{link controller='MultifactorManage' object=$backupMethod isEmail=true}{/link}] neue Notfallcodes {if LANGUAGE_USE_INFORMAL_VARIANT}generierst{else}generieren{/if}.]]> diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 4baf049c1b..1db19ab13d 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -4930,6 +4930,17 @@ Open the link below to access the user profile: You used the emergency code “{$usedCode[identifier]}” for multi-factor authentication. This code no longer is valid. {plural value=$remaining 0='**You don't have any remaining codes.**' 1='You have one remaining code.' other='You have # remaining codes.'} {* this line ends with a space *} You can manage multi-factor authentication within the Account Security page [URL:{link controller='AccountSecurity' isEmail=true}{/link}]. Within account security you can generate new emergency codes or disable multi-factor authentication.]]> + + Dear {$user->username}, + +

You successfully enabled multi-factor authentication using the {lang}wcf.user.security.multifactor.{$method->objectType}{/lang} method. Going forward you will need to have your second factor handy for every login.

+ +

You also received a list of emergency codes to use when your second factor becomes unavailable. Did you store these emergency codes securely? If you forgot you can do now by generating new codes in the Emergency Code Management.

]]>
+ username}, + +You successfully enabled multi-factor authentication using the „{lang}wcf.user.security.multifactor.{$method->objectType}{/lang}“ method. Going forward you will need to have your second factor handy for every login. + +You also received a list of emergency codes to use when your second factor becomes unavailable. **Did you store these emergency codes securely?** If you forgot you can do now by generating new codes in the Emergency Code Management [URL:{link controller='MultifactorManage' object=$backupMethod isEmail=true}{/link}].]]>
-- 2.20.1