Send a notification email when MFA is enabled (#3756)
authorTim Düsterhus <duesterhus@woltlab.com>
Wed, 25 Nov 2020 16:38:10 +0000 (17:38 +0100)
committerGitHub <noreply@github.com>
Wed, 25 Nov 2020 16:38:10 +0000 (17:38 +0100)
wcfsetup/install/files/lib/form/MultifactorManageForm.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 1dad21c3638f1a12846d517e532f9d57b84cf771..ec5e8d6d3430364b96233f8ec195820b7a77aeb2 100644 (file)
@@ -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
         */
index 6c0440346c669f6ec73569cefb3909fb779eb06e..7f696d9765d484e6d7b51f2c9bcb12a70a37bae5 100644 (file)
@@ -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.]]></item>
+               <item name="wcf.user.security.multifactor.setupEmail.subject"><![CDATA[Mehrfaktor-Authentifizierung auf {@PAGE_TITLE|language} aktiviert]]></item>
+               <item name="wcf.user.security.multifactor.setupEmail.body.html"><![CDATA[<h2>Hallo {$user->username},</h2>
+
+<p>{if LANGUAGE_USE_INFORMAL_VARIANT}Du hast{else}Sie haben{/if} die Mehrfaktor-Authentifizierung mit dem Verfahren <b>{lang}wcf.user.security.multifactor.{$method->objectType}{/lang}</b> 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.</p>
+
+<p>{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. <b>{if LANGUAGE_USE_INFORMAL_VARIANT}Hast du dir{else}Haben Sie sich{/if} die Notfallcodes notiert?</b> 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 <a href="{link controller='MultifactorManage' object=$backupMethod isHtmlEmail=true}{/link}">Verwaltung der Notfallcodes</a> neue Notfallcodes {if LANGUAGE_USE_INFORMAL_VARIANT}generierst{else}generieren{/if}.</p>]]></item>
+               <item name="wcf.user.security.multifactor.setupEmail.body.plain"><![CDATA[Hallo {$user->username},\r
+\r
+{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.\r
+\r
+{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}.]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophäen]]></item>
index 4baf049c1bcf1b0b66d3367dd0a27acc97ecef57..1db19ab13dfe3c8db818de1248405208f99fa806 100644 (file)
@@ -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.]]></item>
+               <item name="wcf.user.security.multifactor.setupEmail.subject"><![CDATA[Multi-factor Authentication Enabled on {@PAGE_TITLE|language}]]></item>
+               <item name="wcf.user.security.multifactor.setupEmail.body.html"><![CDATA[<h2>Dear {$user->username},</h2>
+
+<p>You successfully enabled multi-factor authentication using the <b>{lang}wcf.user.security.multifactor.{$method->objectType}{/lang}</b> method. Going forward you will need to have your second factor handy for every login.</p>
+
+<p>You also received a list of emergency codes to use when your second factor becomes unavailable. <b>Did you store these emergency codes securely?</b> If you forgot you can do now by generating new codes in the <a href="{link controller='MultifactorManage' object=$backupMethod isHtmlEmail=true}{/link}">Emergency Code Management</a>.</p>]]></item>
+               <item name="wcf.user.security.multifactor.setupEmail.body.plain"><![CDATA[Dear {$user->username},\r
+\r
+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.\r
+\r
+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}].]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophies]]></item>