Expire pending user changes after 15 minutes
authorTim Düsterhus <duesterhus@woltlab.com>
Wed, 25 Nov 2020 17:24:37 +0000 (18:24 +0100)
committerTim Düsterhus <duesterhus@woltlab.com>
Wed, 25 Nov 2020 17:38:00 +0000 (18:38 +0100)
15 minutes should be plenty of time for the user to complete the MF
authentication flow. A user is not going to sit patiently for 15 minutes
without simply closing the browser window which de facto forces them to start
anew, because nothing leads back to the MF authentication flow except
performing yet another login.

Email codes are the only (default) method that could be slow without the user
being able to do anything about it (e.g. due to greylisting). These codes
already expire after 10 minutes, giving an implicit upper bound (when ignoring
the fact that a new code will be sent every 2 minutes during refreshing).

wcfsetup/install/files/lib/form/MultifactorAuthenticationForm.class.php
wcfsetup/install/files/lib/system/session/SessionHandler.class.php
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index ceea1c90004d61147e6c3a3280466807371a5615..0954afec8a7b64fe4514e4c9876c76280739be61 100644 (file)
@@ -6,6 +6,7 @@ use wcf\form\AbstractFormBuilderForm;
 use wcf\system\application\ApplicationHandler;
 use wcf\system\cache\runtime\UserProfileRuntimeCache;
 use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\NamedUserException;
 use wcf\system\exception\PermissionDeniedException;
 use wcf\system\form\builder\TemplateFormNode;
 use wcf\system\request\LinkHandler;
@@ -77,7 +78,9 @@ class MultifactorAuthenticationForm extends AbstractFormBuilderForm {
                
                $this->user = WCF::getSession()->getPendingUserChange();
                if (!$this->user) {
-                       throw new PermissionDeniedException();
+                       throw new NamedUserException(WCF::getLanguage()->getDynamicVariable(
+                               'wcf.user.security.multifactor.authentication.noPendingUserChange'
+                       ));
                }
                
                $this->setups = Setup::getAllForUser($this->user);
index 0f0839a323092ff2955930006ea9c78690ac7def..739a7db76609044d09a209aa0e1a55ad1ea2b06d 100644 (file)
@@ -133,11 +133,12 @@ final class SessionHandler extends SingletonFactory {
         */
        private $xsrfToken;
        
-       private const ACP_SESSION_LIFETIME = 7200;
-       private const GUEST_SESSION_LIFETIME = 7200;
-       private const USER_SESSION_LIFETIME = 86400 * 14;
+       private const ACP_SESSION_LIFETIME = 2 * 3600;
+       private const GUEST_SESSION_LIFETIME = 2 * 3600;
+       private const USER_SESSION_LIFETIME = 14 * 86400;
        
        private const CHANGE_USER_AFTER_MULTIFACTOR_KEY = '__changeUserAfterMultifactor__';
+       private const PENDING_USER_LIFETIME = 15 * 60;
        
        /**
         * Provides access to session data.
@@ -705,7 +706,10 @@ final class SessionHandler extends SingletonFactory {
         */
        public function changeUserAfterMultifactor(User $user): bool {
                if ($user->multifactorActive) {
-                       $this->register(self::CHANGE_USER_AFTER_MULTIFACTOR_KEY, $user->userID);
+                       $this->register(self::CHANGE_USER_AFTER_MULTIFACTOR_KEY, [
+                               'userId' => $user->userID,
+                               'expires' => TIME_NOW + self::PENDING_USER_LIFETIME,
+                       ]);
                        $this->setLanguageID($user->languageID);
                        
                        return true;
@@ -746,8 +750,15 @@ final class SessionHandler extends SingletonFactory {
         * Returns the pending user change initiated by changeUserAfterMultifactor().
         */
        public function getPendingUserChange(): ?User {
-               $userId = $this->getVar(self::CHANGE_USER_AFTER_MULTIFACTOR_KEY);
-               if (!$userId) {
+               $data = $this->getVar(self::CHANGE_USER_AFTER_MULTIFACTOR_KEY);
+               if (!$data) {
+                       return null;
+               }
+               
+               $userId = $data['userId'];
+               $expires = $data['expires'];
+               
+               if ($expires < TIME_NOW) {
                        return null;
                }
                
index 175e07076a4cc20d6f48dfe36bc73d273d4820a3..b312e5801d1dc3fcc6e836964fde064201f0c3e3 100644 (file)
@@ -4945,6 +4945,7 @@ Die E-Mail-Adresse des neuen Benutzers lautet: {@$user->email}
 {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}.]]></item>
+               <item name="wcf.user.security.multifactor.authentication.noPendingUserChange"><![CDATA[Die Mehrfaktor-Authentifizierung wurde nicht rechtzeitig abgeschlossen. Aus Sicherheitsgründen wurde der unvollständige Login-Vorgang abgebrochen. Bitte <a href="{link controller='Login'}{/link}">{if LANGUAGE_USE_INFORMAL_VARIANT}logge dich{else}loggen Sie sich{/if}</a> erneut ein.]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophäen]]></item>
index 95e900e6f4039abe07df251a4a5ded33b62edb35..684f18b3f2a0ed7782b0414a01d3e06f977f31fc 100644 (file)
@@ -4942,6 +4942,7 @@ You can manage multi-factor authentication within the Account Security page [URL
 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}].]]></item>
+               <item name="wcf.user.security.multifactor.authentication.noPendingUserChange"><![CDATA[The multi-factor authentication was not completed in time. The incomplete login process was aborted for security reasons. Please <a href="{link controller='Login'}{/link}">Login</a> once again.]]></item>
        </category>
        <category name="wcf.user.trophy">
                <item name="wcf.user.trophy.trophyPoints"><![CDATA[Trophies]]></item>