Make the MFA authentication look a bit nicer
authorTim Düsterhus <duesterhus@woltlab.com>
Tue, 17 Nov 2020 11:04:43 +0000 (12:04 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 18 Nov 2020 13:13:17 +0000 (14:13 +0100)
13 files changed:
com.woltlab.wcf/templates/__backupCodeField.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/__emailCodeField.tpl [new file with mode: 0644]
com.woltlab.wcf/templates/multifactorAuthentication.tpl
wcfsetup/install/files/lib/system/user/multifactor/BackupMultifactorMethod.class.php
wcfsetup/install/files/lib/system/user/multifactor/EmailMultifactorMethod.class.php
wcfsetup/install/files/lib/system/user/multifactor/Helper.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/multifactor/TotpMultifactorMethod.class.php
wcfsetup/install/files/lib/system/user/multifactor/backup/CodeFormField.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/multifactor/email/CodeFormField.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/multifactor/totp/CodeFormField.class.php
wcfsetup/install/files/style/ui/accountSecurity.scss
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

diff --git a/com.woltlab.wcf/templates/__backupCodeField.tpl b/com.woltlab.wcf/templates/__backupCodeField.tpl
new file mode 100644 (file)
index 0000000..d762561
--- /dev/null
@@ -0,0 +1,17 @@
+<input type="text" {*
+       *}id="{@$field->getPrefixedId()}" {*
+       *}name="{@$field->getPrefixedId()}" {*
+       *}value="{if !$field->isI18n() || !$field->hasI18nValues() || $availableLanguages|count === 1}{$field->getValue()}{/if}" {*
+       *}class="multifactorBackupCode" {*
+       *}autocomplete="off" {*
+       *}pattern="[0-9\s]*" {*
+       *}inputmode="numeric"{*
+       *}{if $field->getChunks() && $field->getChunkLength()} size="{$field->getChunks() - 1 + $field->getChunks() * $field->getChunkLength()}"{/if}{*
+       *}{if $field->isAutofocused()} autofocus{/if}{*
+       *}{if $field->isRequired()} required{/if}{*
+       *}{if $field->isImmutable()} disabled{/if}{*
+       *}{if $field->getMinimumLength() !== null} minlength="{$field->getMinimumLength()}"{/if}{*
+       *}{if $field->getMaximumLength() !== null} maxlength="{$field->getMaximumLength()}"{/if}{*
+       *}{if $field->getPlaceholder() !== null} placeholder="{$field->getPlaceholder()}"{/if}{*
+       *}{if $field->getDocument()->isAjax()} data-dialog-submit-on-enter="true"{/if}{*
+*}>
diff --git a/com.woltlab.wcf/templates/__emailCodeField.tpl b/com.woltlab.wcf/templates/__emailCodeField.tpl
new file mode 100644 (file)
index 0000000..331481b
--- /dev/null
@@ -0,0 +1,17 @@
+<input type="text" {*
+       *}id="{@$field->getPrefixedId()}" {*
+       *}name="{@$field->getPrefixedId()}" {*
+       *}value="{if !$field->isI18n() || !$field->hasI18nValues() || $availableLanguages|count === 1}{$field->getValue()}{/if}" {*
+       *}class="multifactorEmailCode" {*
+       *}autocomplete="off" {*
+       *}{if $field->getMaximumLength() !== null}size="{$field->getMaximumLength()}" {/if}{*
+       *}pattern="[0-9]*" {*
+       *}inputmode="numeric"{*
+       *}{if $field->isAutofocused()} autofocus{/if}{*
+       *}{if $field->isRequired()} required{/if}{*
+       *}{if $field->isImmutable()} disabled{/if}{*
+       *}{if $field->getMinimumLength() !== null} minlength="{$field->getMinimumLength()}"{/if}{*
+       *}{if $field->getMaximumLength() !== null} maxlength="{$field->getMaximumLength()}"{/if}{*
+       *}{if $field->getPlaceholder() !== null} placeholder="{$field->getPlaceholder()}"{/if}{*
+       *}{if $field->getDocument()->isAjax()} data-dialog-submit-on-enter="true"{/if}{*
+*}>
index bfed7cf1117127cc556c8cf1d26d31993ccb5323..5beef7e7c10e597dd1ebe771d2ff6c6f8edbf819 100644 (file)
@@ -1,3 +1,6 @@
+{capture assign='pageTitle'}{lang}wcf.user.security.multifactor.authentication{/lang}{/capture}
+{capture assign='contentTitle'}{lang}wcf.user.security.multifactor.authentication{/lang}{/capture}
+
 {capture assign='sidebarLeft'}
 <section class="box">
                <h2 class="boxTitle">{lang}wcf.user.security.multifactor.methods{/lang}</h2>
index 57de3f104676d8e9830320c606947bf301fc6aa0..ce8ba6a07b1d8c183697040205d6bdab20d914a9 100644 (file)
@@ -11,6 +11,7 @@ use wcf\system\form\builder\TemplateFormNode;
 use wcf\system\user\authentication\password\algorithm\Bcrypt;
 use wcf\system\user\authentication\password\IPasswordAlgorithm;
 use wcf\system\user\authentication\password\PasswordAlgorithmManager;
+use wcf\system\user\multifactor\backup\CodeFormField;
 use wcf\system\WCF;
 
 /**
@@ -28,8 +29,8 @@ class BackupMultifactorMethod implements IMultifactorMethod {
         */
        private $algorithm;
        
-       private const CHUNKS = 4;
-       private const CHUNK_LENGTH = 5;
+       public const CHUNKS = 4;
+       public const CHUNK_LENGTH = 5;
        
        private const USER_ATTEMPTS_PER_HOUR = 5;
        
@@ -211,8 +212,9 @@ class BackupMultifactorMethod implements IMultifactorMethod {
                $codes = $statement->fetchAll(\PDO::FETCH_ASSOC);
                
                $form->appendChildren([
-                       TextFormField::create('code')
+                       CodeFormField::create()
                                ->label('wcf.user.security.multifactor.backup.code')
+                               ->description('wcf.user.security.multifactor.backup.code.description')
                                ->autoFocus()
                                ->required()
                                ->addValidator(new FormFieldValidator('code', function (TextFormField $field) use ($codes, $setup) {
index 55c4d0ea1d86ab0550fb30a61f53094b94055c68..e32e6cc2eb1a3eefbfe82f1afdde228da5c1188d 100644 (file)
@@ -10,6 +10,7 @@ use wcf\system\form\builder\field\validation\FormFieldValidationError;
 use wcf\system\form\builder\field\validation\FormFieldValidator;
 use wcf\system\form\builder\IFormDocument;
 use wcf\system\form\builder\TemplateFormNode;
+use wcf\system\user\multifactor\email\CodeFormField;
 use wcf\system\WCF;
 
 /**
@@ -25,7 +26,7 @@ class EmailMultifactorMethod implements IMultifactorMethod {
        private const LIFETIME = 10 * 60;
        private const REFRESH_AFTER = 2 * 60;
        
-       private const LENGTH = 8;
+       public const LENGTH = 8;
        
        private const USER_ATTEMPTS_PER_TEN_MINUTES = 5;
        
@@ -168,7 +169,7 @@ class EmailMultifactorMethod implements IMultifactorMethod {
                $emailDomain = substr($address, $atSign + 1);
                
                $form->appendChildren([
-                       TextFormField::create('code')
+                       CodeFormField::create()
                                ->label('wcf.user.security.multifactor.email.code')
                                ->description('wcf.user.security.multifactor.email.code.description', [
                                        'emailDomain' => $emailDomain,
diff --git a/wcfsetup/install/files/lib/system/user/multifactor/Helper.class.php b/wcfsetup/install/files/lib/system/user/multifactor/Helper.class.php
new file mode 100644 (file)
index 0000000..0181a69
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\system\user\multifactor;
+
+/**
+ * Provides re-usable helper methods for use in multi-factor authentication.
+ *
+ * @author     Tim Duesterhus
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\System\User\Multifactor
+ * @since      5.4
+ */
+final class Helper {
+       /**
+        * Generates a stream of digits.
+        */
+       public static function digitStream(): \Iterator {
+               $i = 1;
+               while (true) {
+                       yield $i++;
+                       if ($i > 9) $i = 0;
+               }
+       }
+}
index e323661e90043baf091ecf10e654ca9a37c944a5..1344fd257a58c28f0dca85c8ac86f36e1cc5c22a 100644 (file)
@@ -87,7 +87,7 @@ class TotpMultifactorMethod implements IMultifactorMethod {
                                        })),
                                TextFormField::create('deviceName')
                                        ->label('wcf.user.security.multifactor.totp.deviceName')
-                                       ->description('wcf.user.security.multifactor.totp.deviceName.description')
+                                       ->description('wcf.user.security.multifactor.totp.deviceName.description.setup')
                                        ->placeholder('wcf.user.security.multifactor.totp.deviceName.placeholder')
                                        ->maximumLength(200),
                                FormButton::create('submitButton')
@@ -265,6 +265,7 @@ class TotpMultifactorMethod implements IMultifactorMethod {
                        $form->appendChildren([
                                RadioButtonFormField::create('device')
                                        ->label('wcf.user.security.multifactor.totp.deviceName')
+                                       ->description('wcf.user.security.multifactor.totp.deviceName.description.auth')
                                        ->objectProperty('deviceID')
                                        ->options($deviceOptions)
                                        ->value($mostRecentlyUsed['deviceID']),
@@ -281,6 +282,7 @@ class TotpMultifactorMethod implements IMultifactorMethod {
                $form->appendChildren([
                        CodeFormField::create()
                                ->label('wcf.user.security.multifactor.totp.code')
+                               ->description('wcf.user.security.multifactor.totp.code.description')
                                ->autoFocus()
                                ->required()
                                ->addValidator(new FormFieldValidator('code', function (CodeFormField $field) use ($devices, $setup) {
diff --git a/wcfsetup/install/files/lib/system/user/multifactor/backup/CodeFormField.class.php b/wcfsetup/install/files/lib/system/user/multifactor/backup/CodeFormField.class.php
new file mode 100644 (file)
index 0000000..51b09ac
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+namespace wcf\system\user\multifactor\backup;
+use wcf\system\form\builder\field\TDefaultIdFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\user\multifactor\BackupMultifactorMethod;
+use wcf\system\user\multifactor\Helper;
+
+/**
+ * Handles the input of a emergency code.
+ *
+ * @author     Tim Duesterhus
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\System\User\Multifactor\Backup
+ * @since      5.4
+ */
+class CodeFormField extends TextFormField {
+       use TDefaultIdFormField;
+       
+       /**
+        * @var int
+        */
+       protected $chunks;
+       
+       /**
+        * @var int
+        */
+       protected $chunkLength;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $templateName = '__backupCodeField';
+       
+       public function __construct() {
+               $this->chunks(BackupMultifactorMethod::CHUNKS);
+               $this->chunkLength(BackupMultifactorMethod::CHUNK_LENGTH);
+               $this->minimumLength($this->getChunks() * $this->getChunkLength());
+               
+               $placeholder = '';
+               $gen = Helper::digitStream();
+               for ($i = 0; $i < BackupMultifactorMethod::CHUNKS; $i++) {
+                       for ($j = 0; $j < BackupMultifactorMethod::CHUNK_LENGTH; $j++) {
+                               $placeholder .= $gen->current();
+                               $gen->next();
+                       }
+                       $placeholder .= ' ';
+               }
+               $this->placeholder($placeholder);
+       }
+       
+       /**
+        * Sets the number of chunks.
+        */
+       public function chunks(int $chunks): self {
+               $this->chunks = $chunks;
+               return $this;
+       }
+       
+       /**
+        * Sets the length of a single chunk.
+        */
+       public function chunkLength(int $chunkLength): self {
+               $this->chunkLength = $chunkLength;
+               return $this;
+       }
+       
+       /**
+        * Returns the number of chunks.
+        */
+       public function getChunks() {
+               return $this->chunks;
+       }
+       
+       /**
+        * Returns the length of a single chunk.
+        */
+       public function getChunkLength() {
+               return $this->chunkLength;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function getDefaultId(): string {
+               return 'code';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/user/multifactor/email/CodeFormField.class.php b/wcfsetup/install/files/lib/system/user/multifactor/email/CodeFormField.class.php
new file mode 100644 (file)
index 0000000..3e1b384
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+namespace wcf\system\user\multifactor\email;
+use wcf\system\form\builder\field\TDefaultIdFormField;
+use wcf\system\form\builder\field\TextFormField;
+use wcf\system\user\multifactor\EmailMultifactorMethod;
+use wcf\system\user\multifactor\Helper;
+
+/**
+ * Handles the input of an email code.
+ *
+ * @author     Tim Duesterhus
+ * @copyright  2001-2020 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\System\User\Multifactor\Email
+ * @since      5.4
+ */
+class CodeFormField extends TextFormField {
+       use TDefaultIdFormField;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $templateName = '__emailCodeField';
+       
+       public function __construct() {
+               $this->minimumLength(EmailMultifactorMethod::LENGTH);
+               $this->maximumLength(EmailMultifactorMethod::LENGTH);
+               
+               $placeholder = '';
+               $gen = Helper::digitStream();
+               for ($i = 0; $i < $this->getMinimumLength(); $i++) {
+                       $placeholder .= $gen->current();
+                       $gen->next();
+               }
+               $this->placeholder($placeholder);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       protected static function getDefaultId(): string {
+               return 'code';
+       }
+}
index 2af016a27433f230dfce43871da923efffb3a455..ddc9528ca58435884828ff187a50ca03583f13d8 100644 (file)
@@ -2,6 +2,7 @@
 namespace wcf\system\user\multifactor\totp;
 use wcf\system\form\builder\field\TDefaultIdFormField;
 use wcf\system\form\builder\field\TextFormField;
+use wcf\system\user\multifactor\Helper;
 
 /**
  * Handles the input of a TOTP code.
@@ -28,7 +29,14 @@ class CodeFormField extends TextFormField {
        public function __construct() {
                $this->minimumLength(Totp::CODE_LENGTH);
                $this->maximumLength(Totp::CODE_LENGTH);
-               $this->placeholder("123456");
+               
+               $placeholder = '';
+               $gen = Helper::digitStream();
+               for ($i = 0; $i < $this->getMinimumLength(); $i++) {
+                       $placeholder .= $gen->current();
+                       $gen->next();
+               }
+               $this->placeholder($placeholder);
        }
        
        /**
index 23cfe7fec7eaa72cf139d4d1b33e92308c741a65..825b4a196cb513c3b0b6e15982274940d0909377 100644 (file)
 }
 
 // Just .multifactorTotpCode is not specific enough.
-input.multifactorTotpCode {
+input.multifactorTotpCode,
+input.multifactorEmailCode {
        font-family: monospace;
        font-weight: 600;
        font-size: 28px;
 }
+input.multifactorBackupCode {
+       font-family: monospace;
+       font-weight: 600;
+       font-size: 18px;
+}
 
 .multifactorTotpNewDevice {
        display: flex;
index d3ee7b608fdc7902d85ba1f28852ea52f90a3da5..481c29115ff151d815f0da11249a7638aa3e3cae 100644 (file)
@@ -4858,7 +4858,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.security.multifactor.totp.devices"><![CDATA[Aktive Smartphones]]></item>
                <item name="wcf.user.security.multifactor.totp.error.flood"><![CDATA[Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}versuche es{else}versuchen Sie es{/if} später erneut.]]></item>
                <item name="wcf.user.security.multifactor.backup.error.flood"><![CDATA[Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}versuche es{else}versuchen Sie es{/if} später erneut.]]></item>
-               <item name="wcf.user.security.multifactor.totp.deviceName.description"><![CDATA[Ein beliebiger Name, der dieses Gerät identifiziert.]]></item>
+               <item name="wcf.user.security.multifactor.totp.deviceName.description.setup"><![CDATA[Ein beliebiger Name, der dieses Gerät identifiziert.]]></item>
                <item name="wcf.user.security.multifactor.totp.code.description"><![CDATA[Der durch die Smartphone-App generierte 6-stellige Einmalcode.]]></item>
                <item name="wcf.user.security.multifactor.totp.newDevice.description"><![CDATA[<p>Authentifizieren Sie sich mit Hilfe einer App auf Ihrem Smartphone.</p>
 <ol class="nativeList">
@@ -4872,8 +4872,8 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.security.multifactor.totp.lastDevice"><![CDATA[Wenn Sie ihr Smartphone wechseln möchten, fügen Sie bitte zunächst Ihr neues Smartphone hinzu, bevor Sie Ihr letztes Gerät <strong>{$deviceName}</strong> entfernen. {if LANGUAGE_USE_INFORMAL_VARIANT}Verwende{else}Verwenden Sie{/if} bitte die <a href="{link controller='AccountSecurity'}{/link}">Übersicht in der Benutzerkonto-Sicherheit</a>, wenn {if LANGUAGE_USE_INFORMAL_VARIANT}du{else}Sie{/if} die Mehrfaktor-Authentifizierung deaktivieren {if LANGUAGE_USE_INFORMAL_VARIANT}möchtest{else}möchten{/if}.]]></item>
                <item name="wcf.user.security.multifactor.totp.lastDevice.title"><![CDATA[Letztes Gerät]]></item>
                <item name="wcf.user.security.multifactor.totp.success.delete"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Smartphone <strong>{$deviceName}</strong> wurde erfolgreich entfernt.]]></item>
-               <item name="wcf.user.security.multifactor.initialBackup"><![CDATA[<p>Die Mehrfaktor-Authentifizierung ist ab sofort für {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Benutzerkonto aktiv. {if LANGUAGE_USE_INFORMAL_VARIANT}Du wirst{else}Sie werden{/if} von nun an bei jedem Login den zusätzlichen Faktor benötigen.</p>\r
-<p>Zusätzlich wurden Backup-Codes generiert, mit denen der Zugriff wiederhergestellt werden kann, falls der zusätzliche Faktor unbrauchbar wird.</p>\r
+               <item name="wcf.user.security.multifactor.initialBackup"><![CDATA[<p>Die Mehrfaktor-Authentifizierung ist ab sofort für {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}Ihr{/if} Benutzerkonto aktiv. {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>Zusätzlich wurden Backup-Codes generiert, mit denen der Zugriff wiederhergestellt werden kann, falls der zusätzliche Faktor unbrauchbar wird.</p>
 <p>Bitte {if LANGUAGE_USE_INFORMAL_VARIANT}Notiere oder speichere dir{else}Notieren oder speichern Sie{/if} sich die unterhalb angezeigten Notfall-Codes. Ein möglicher Aufbewahrungsort könnte ein Blatt Papier in einem Aktenordner sein.</p>]]></item>
                <item name="wcf.user.security.multifactor.com.woltlab.wcf.multifactor.email"><![CDATA[Einmalcode über E-Mail]]></item>
                <item name="wcf.user.security.multifactor.com.woltlab.wcf.multifactor.email.manage"><![CDATA[Einmalcode über E-Mail]]></item>
@@ -4887,6 +4887,11 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.security.multifactor.email.subject"><![CDATA[{$code} ist {if LANGUAGE_USE_INFORMAL_VARIANT}dein{else}ihr{/if} Einmalcode for {@PAGE_TITLE|language}]]></item>
                <item name="wcf.user.security.multifactor.email.body.html"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Einmalcode lautet: <pre>{$code}</pre>]]></item>
                <item name="wcf.user.security.multifactor.email.body.plain"><![CDATA[{if LANGUAGE_USE_INFORMAL_VARIANT}Dein{else}Ihr{/if} Einmalcode lautet: {$code}]]></item>
+               <item name="wcf.user.security.multifactor.methods"><![CDATA[Verfahren]]></item>
+               <item name="wcf.user.security.multifactor.authentication"><![CDATA[Mehrfaktor-Authentifizierung]]></item>
+               <item name="wcf.user.security.multifactor.backup.code"><![CDATA[Notfall-Code]]></item>
+               <item name="wcf.user.security.multifactor.backup.code.description"><![CDATA[Ein Notfall-Code besteht aus 20 Ziffern und ist nur einmal gültig.]]></item>
+               <item name="wcf.user.security.multifactor.totp.deviceName.description.auth"><![CDATA[Das Gerät, das den genutzten Éinmalcode generiert hat.]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophäen]]></item>
index 3048f219f1c0e776aeeda0134afc9d0a98195a64..344f6e00209d0664c179bf68ae33510a1cf390c6 100644 (file)
@@ -4855,7 +4855,7 @@ Open the link below to access the user profile:
                <item name="wcf.user.security.multifactor.totp.devices"><![CDATA[Active Smartphones]]></item>
                <item name="wcf.user.security.multifactor.totp.error.flood"><![CDATA[Please try again later.]]></item>
                <item name="wcf.user.security.multifactor.backup.error.flood"><![CDATA[Please try again later.]]></item>
-               <item name="wcf.user.security.multifactor.totp.deviceName.description"><![CDATA[An arbitrary name identifying this device.]]></item>
+               <item name="wcf.user.security.multifactor.totp.deviceName.description.setup"><![CDATA[An arbitrary name identifying this device.]]></item>
                <item name="wcf.user.security.multifactor.totp.code.description"><![CDATA[The 6-digit one time code generated by the smartphone app.]]></item>
                <item name="wcf.user.security.multifactor.totp.newDevice.description"><![CDATA[<p>Authenticate using an app on your smartphone.</p>
 <ol class="nativeList">
@@ -4869,8 +4869,8 @@ Open the link below to access the user profile:
                <item name="wcf.user.security.multifactor.totp.lastDevice"><![CDATA[Please add your new device before removing your last device <strong>{$deviceName}</strong> if you want to switch phones. Use the <a href="{link controller='AccountSecurity'}{/link}">Overview in Account Security</a> if you want to disable multi-factor authentication.]]></item>
                <item name="wcf.user.security.multifactor.totp.lastDevice.title"><![CDATA[Last Device]]></item>
                <item name="wcf.user.security.multifactor.totp.success.delete"><![CDATA[Your smartphone <strong>{$deviceName}</strong> has successfully been removed.]]></item>
-               <item name="wcf.user.security.multifactor.initialBackup"><![CDATA[<p>The multi-factor authentication is enabled for your account starting now. Going forward you will need to have your second factor handy for every login.</p>\r
-<p>In addition we generated emergency codes for you. They will allow you to gain access to your account in case your second factor becomes unavailable.</p>\r
+               <item name="wcf.user.security.multifactor.initialBackup"><![CDATA[<p>The multi-factor authentication is enabled for your account starting now. Going forward you will need to have your second factor handy for every login.</p>
+<p>In addition we generated emergency codes for you. They will allow you to gain access to your account in case your second factor becomes unavailable.</p>
 <p>Please carefully note or save the emergency codes shown below. An example of a secure storage could be a piece of paper within a filing cabinet.</p>]]></item>
                <item name="wcf.user.security.multifactor.com.woltlab.wcf.multifactor.email"><![CDATA[Code via Email]]></item>
                <item name="wcf.user.security.multifactor.com.woltlab.wcf.multifactor.email.manage"><![CDATA[Code via Email]]></item>
@@ -4884,6 +4884,11 @@ Open the link below to access the user profile:
                <item name="wcf.user.security.multifactor.email.subject"><![CDATA[{$code} is your one time code for {@PAGE_TITLE|language}]]></item>
                <item name="wcf.user.security.multifactor.email.body.html"><![CDATA[Your one time code is: <pre>{$code}</pre>]]></item>
                <item name="wcf.user.security.multifactor.email.body.plain"><![CDATA[Your one time code is: {$code}]]></item>
+               <item name="wcf.user.security.multifactor.methods"><![CDATA[Method]]></item>
+               <item name="wcf.user.security.multifactor.authentication"><![CDATA[Multi-Factor Authentication]]></item>
+               <item name="wcf.user.security.multifactor.backup.code"><![CDATA[Emergency Code]]></item>
+               <item name="wcf.user.security.multifactor.backup.code.description"><![CDATA[An emergency code consists of 20 digits and may only be used once.]]></item>
+               <item name="wcf.user.security.multifactor.totp.deviceName.description.auth"><![CDATA[The device that generated the used one time code.]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophies]]></item>