Add flood control for multi-factor authentication
authorTim Düsterhus <duesterhus@woltlab.com>
Tue, 10 Nov 2020 09:46:35 +0000 (10:46 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Mon, 16 Nov 2020 16:29:05 +0000 (17:29 +0100)
com.woltlab.wcf/objectType.xml
wcfsetup/install/files/lib/system/user/multifactor/BackupMultifactorMethod.class.php
wcfsetup/install/files/lib/system/user/multifactor/TotpMultifactorMethod.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 554f04e1df33bf500ae278f4263fa59a6186b139..c080b520370b5806fa33c1ab85be3d607820c68c 100644 (file)
                        <priority>1</priority>
                        <classname>wcf\system\user\multifactor\BackupMultifactorMethod</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.multifactor.backup</name>
+                       <definitionname>com.woltlab.wcf.floodControl</definitionname>
+               </type>
                <type>
                        <name>com.woltlab.wcf.multifactor.totp</name>
                        <definitionname>com.woltlab.wcf.multifactor</definitionname>
                        <priority>10</priority>
                        <classname>wcf\system\user\multifactor\TotpMultifactorMethod</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.multifactor.totp</name>
+                       <definitionname>com.woltlab.wcf.floodControl</definitionname>
+               </type>
                <!-- /multi factor -->
                <!-- deprecated -->
                <type>
index 91116c5ead2ee8084e29293a4b2716d418fd0ecb..4a70764c8b122802963d58e37b9f587e55dbc834 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\system\user\multifactor;
+use wcf\system\flood\FloodControl;
 use wcf\system\form\builder\container\FormContainer;
 use wcf\system\form\builder\field\ButtonFormField;
 use wcf\system\form\builder\field\TextFormField;
@@ -30,6 +31,8 @@ class BackupMultifactorMethod implements IMultifactorMethod {
        private const CHUNKS = 4;
        private const CHUNK_LENGTH = 5;
        
+       private const USER_ATTEMPTS_PER_HOUR = 5;
+       
        public function __construct() {
                $this->algorithm = new Bcrypt();
        }
@@ -212,7 +215,18 @@ class BackupMultifactorMethod implements IMultifactorMethod {
                                ->label('wcf.user.security.multifactor.backup.code')
                                ->autoFocus()
                                ->required()
-                               ->addValidator(new FormFieldValidator('code', function (TextFormField $field) use ($codes) {
+                               ->addValidator(new FormFieldValidator('code', function (TextFormField $field) use ($codes, $setupId) {
+                                       FloodControl::getInstance()->registerUserContent('com.woltlab.wcf.multifactor.backup', $setupId);
+                                       $attempts = FloodControl::getInstance()->countUserContent('com.woltlab.wcf.multifactor.backup', $setupId, new \DateInterval('PT1H'));
+                                       if ($attempts['count'] > self::USER_ATTEMPTS_PER_HOUR) {
+                                               $field->addValidationError(new FormFieldValidationError(
+                                                       'flood',
+                                                       'wcf.user.security.multifactor.backup.error.flood',
+                                                       $attempts
+                                               ));
+                                               return;
+                                       }
+                                       
                                        $userCode = \preg_replace('/\s+/', '', $field->getValue());
                                        
                                        if ($this->findValidCode($userCode, $codes) === null) {
index dffca00603e4435cc105c1323bcad9094f8724eb..c21156602ee0b04d404e3c30ff804af29370ca66 100644 (file)
@@ -1,6 +1,7 @@
 <?php
 namespace wcf\system\user\multifactor;
 use ParagonIE\ConstantTime\Hex;
+use wcf\system\flood\FloodControl;
 use wcf\system\form\builder\button\FormButton;
 use wcf\system\form\builder\container\FormContainer;
 use wcf\system\form\builder\field\ButtonFormField;
@@ -28,6 +29,8 @@ use wcf\system\WCF;
  * @since      5.4
  */
 class TotpMultifactorMethod implements IMultifactorMethod {
+       private const USER_ATTEMPTS_PER_TEN_MINUTES = 5;
+       
        /**
         * Returns the number of devices the user set up.
         */
@@ -201,7 +204,18 @@ class TotpMultifactorMethod implements IMultifactorMethod {
                                ->label('wcf.user.security.multifactor.totp.code')
                                ->autoFocus()
                                ->required()
-                               ->addValidator(new FormFieldValidator('code', function (CodeFormField $field) use ($devices) {
+                               ->addValidator(new FormFieldValidator('code', function (CodeFormField $field) use ($devices, $setupId) {
+                                       FloodControl::getInstance()->registerUserContent('com.woltlab.wcf.multifactor.backup', $setupId);
+                                       $attempts = FloodControl::getInstance()->countUserContent('com.woltlab.wcf.multifactor.backup', $setupId, new \DateInterval('PT10M'));
+                                       if ($attempts['count'] > self::USER_ATTEMPTS_PER_TEN_MINUTES) {
+                                               $field->addValidationError(new FormFieldValidationError(
+                                                       'flood',
+                                                       'wcf.user.security.multifactor.totp.error.flood',
+                                                       $attempts
+                                               ));
+                                               return;
+                                       }
+                                       
                                        /** @var IFormField $deviceField */
                                        $deviceField = $field->getDocument()->getNodeById('device');
                                        
index b16802f0be9d5bc88b007108659740d43de6105a..e7235e1c9f1676e8c9279a95c36659e433dac523 100644 (file)
@@ -4857,6 +4857,8 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
                <item name="wcf.user.security.multifactor.backup.generateCodes"><![CDATA[Codes generieren]]></item>
                <item name="wcf.user.security.multifactor.totp.devices"><![CDATA[Eingerichtete Geräte]]></item>
                <item name="wcf.user.security.multifactor.totp.deviceName.default"><![CDATA[{TIME_NOW|plainTime}]]></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>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophäen]]></item>
index 4eee12d387b2fe8de9d148bfdd6f37dce6b2b478..42135f5dd7c1c0a84f12319f0157a5f473093814 100644 (file)
@@ -4854,6 +4854,8 @@ Open the link below to access the user profile:
                <item name="wcf.user.security.multifactor.backup.generateCodes"><![CDATA[Generate Codes]]></item>
                <item name="wcf.user.security.multifactor.totp.devices"><![CDATA[Set Up Devices]]></item>
                <item name="wcf.user.security.multifactor.totp.deviceName.default"><![CDATA[{TIME_NOW|plainTime}]]></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>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophies]]></item>