Browse Source

Merge pull request #4685 from FreddleSpl0it/tfa-patch

Update TFA flow
Patrick Schult 3 years ago
parent
commit
02512e0f4f

+ 1 - 0
data/web/css/build/008-mailcow.css

@@ -269,6 +269,7 @@ code {
   padding: 10px;
   background: #fbfbfb;
   border: 1px solid #ededed;
+  min-height: 110px;
 }
 
 .tag-box {

+ 32 - 29
data/web/inc/functions.inc.php

@@ -937,22 +937,33 @@ function check_login($user, $pass, $app_passwd_data = false) {
   }
   foreach ($rows as $row) {
     // verify password
-    if (verify_hash($row['password'], $pass) !== false) {
-      // check for tfa authenticators
-      $authenticators = get_tfa($user);
-      if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
-        $_SESSION['pending_mailcow_cc_username'] = $user;
-        $_SESSION['pending_mailcow_cc_role'] = "user";
-        $_SESSION['pending_tfa_methods'] = $authenticators['additional'];
-        unset($_SESSION['ldelay']);
-        $_SESSION['return'][] =  array(
-          'type' => 'success',
-          'log' => array(__FUNCTION__, $user, '*'),
-          'msg' => array('logged_in_as', $user)
-        );
-        return "pending";
-      } else {
-        if ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
+    if ($app_passwd_data['eas'] !== true && $app_passwd_data['dav'] !== true){
+      if (verify_hash($row['password'], $pass) !== false) {
+        // check for tfa authenticators
+        $authenticators = get_tfa($user);
+        if (isset($authenticators['additional']) && is_array($authenticators['additional']) && count($authenticators['additional']) > 0) {
+          $_SESSION['pending_mailcow_cc_username'] = $user;
+          $_SESSION['pending_mailcow_cc_role'] = "user";
+          $_SESSION['pending_tfa_methods'] = $authenticators['additional'];
+          unset($_SESSION['ldelay']);
+          $_SESSION['return'][] =  array(
+            'type' => 'success',
+            'log' => array(__FUNCTION__, $user, '*'),
+            'msg' => array('logged_in_as', $user)
+          );
+          return "pending";
+        } else {
+          // Reactivate TFA if it was set to "deactivate TFA for next login"
+          $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
+          $stmt->execute(array(':user' => $user));
+
+          unset($_SESSION['ldelay']);
+          return "user";
+        }
+      }
+    } elseif ($app_passwd_data['eas'] === true || $app_passwd_data['dav'] === true) {
+      if (array_key_exists("app_passwd_id", $row)){
+        if (verify_hash($row['password'], $pass) !== false) {
           $service = ($app_passwd_data['eas'] === true) ? 'EAS' : 'DAV';
           $stmt = $pdo->prepare("REPLACE INTO sasl_log (`service`, `app_password`, `username`, `real_rip`) VALUES (:service, :app_id, :username, :remote_addr)");
           $stmt->execute(array(
@@ -961,13 +972,10 @@ function check_login($user, $pass, $app_passwd_data = false) {
             ':username' => $user,
             ':remote_addr' => ($_SERVER['HTTP_X_REAL_IP'] ?? $_SERVER['REMOTE_ADDR'])
           ));
-        }
 
-        unset($_SESSION['ldelay']);
-        // Reactivate TFA if it was set to "deactivate TFA for next login"
-        $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
-        $stmt->execute(array(':user' => $user));
-        return "user";
+          unset($_SESSION['ldelay']);
+          return "user";
+        }
       }
     }
   }
@@ -1626,12 +1634,8 @@ function verify_tfa_login($username, $_data) {
   global $WebAuthn;
 
   if ($_data['tfa_method'] != 'u2f'){
-    $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
-        WHERE `username` = :username AND `id` = :id AND `active` = '1'");
-    $stmt->execute(array(':username' => $username, ':id' => $_data['id']));
-    $row = $stmt->fetch(PDO::FETCH_ASSOC);
 
-    switch ($row["authmech"]) {
+    switch ($_data["tfa_method"]) {
         case "yubi_otp":
             if (!ctype_alnum($_data['token']) || strlen($_data['token']) != 44) {
                 $_SESSION['return'][] =  array(
@@ -1645,10 +1649,9 @@ function verify_tfa_login($username, $_data) {
             $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
                 WHERE `username` = :username
                 AND `authmech` = 'yubi_otp'
-                AND `id` = :id
                 AND `active` = '1'
                 AND `secret` LIKE :modhex");
-            $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id, ':id' => $_data['id']));
+            $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
             $row = $stmt->fetch(PDO::FETCH_ASSOC);
             $yubico_auth = explode(':', $row['secret']);
             $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);

+ 34 - 21
data/web/templates/base.twig

@@ -182,30 +182,19 @@ function recursiveBase64StrToArrayBuffer(obj) {
       keyboard: false
     });
 
-    // validate Yubi OTP tfa
-    $("#pending_tfa_tab_yubi_otp").click(function(){
-      $(".totp-authenticator-selection").removeClass("active");
-      $(".webauthn-authenticator-selection").removeClass("active");
-
-      $("#collapseTotpTFA").collapse('hide');
-      $("#collapseWebAuthnTFA").collapse('hide');
-    });
-    $(".yubi-authenticator-selection").click(function(){
-      $(".yubi-authenticator-selection").removeClass("active");
-      $(this).addClass("active");
-
-      var id = $(this).children('input').first().val();
-      $("#yubi_selected_id").val(id);
 
-      $("#collapseYubiTFA").collapse('show');
-    });
     // validate Time based OTP tfa
     $("#pending_tfa_tab_totp").click(function(){
-      $(".yubi-authenticator-selection").removeClass("active");
       $(".webauthn-authenticator-selection").removeClass("active");
-
-      $("#collapseYubiTFA").collapse('hide');
       $("#collapseWebAuthnTFA").collapse('hide');
+
+      // select default if only one authenticator exists
+      if ($('.totp-authenticator-selection').length == 1){
+        $('.totp-authenticator-selection').addClass("active");
+        var id = $('.totp-authenticator-selection').children('input').first().val();
+        $("#totp_selected_id").val(id);
+        $("#collapseTotpTFA").collapse('show');
+      }
     });
     $(".totp-authenticator-selection").click(function(){
       $(".totp-authenticator-selection").removeClass("active");
@@ -216,13 +205,37 @@ function recursiveBase64StrToArrayBuffer(obj) {
 
       $("#collapseTotpTFA").collapse('show');
     });
+    if ($('.totp-authenticator-selection').length == 1 &&
+        $('#pending_tfa_tab_yubi_otp').length == 0 &&
+        $('.webauthn-authenticator-selection').length == 0){
+      
+      // select default if only one authenticator exists
+      $('.totp-authenticator-selection').addClass("active");
+
+      var id = $('.totp-authenticator-selection').children('input').first().val();
+      $("#totp_selected_id").val(id);
+
+      $("#collapseTotpTFA").collapse('show');
+      setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 1000);
+    }
+    $('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
+      // autofocus
+      setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
+    });    
+    // validate Yubi OTP tfa
+    if ($('.webauthn-authenticator-selection').length == 0){
+      // autofocus
+      setTimeout(function() { $("#collapseYubiTFA").find('input[name="token"]').focus(); }, 1000);
+    }
+    $('#pending_tfa_tab_yubi_otp').on('shown.bs.tab', function() {
+      // autofocus
+      $("#collapseYubiTFA").find('input[name="token"]').focus();
+    });
     // validate WebAuthn tfa
     $("#pending_tfa_tab_webauthn").click(function(){
       $(".totp-authenticator-selection").removeClass("active");
-      $(".yubi-authenticator-selection").removeClass("active");
 
       $("#collapseTotpTFA").collapse('hide');
-      $("#collapseYubiTFA").collapse('hide');
     });
     $(".webauthn-authenticator-selection").click(function(){
       $(".webauthn-authenticator-selection").removeClass("active");

+ 2 - 13
data/web/templates/modals/footer.twig

@@ -206,20 +206,9 @@
                     <form role="form" method="post">
                       <legend>
                           <i class="bi bi-shield-fill-check"></i>
-                          Authenticators
+                          Authenticate
                       </legend>
-                      <div class="list-group">
-                        {% for authenticator in pending_tfa_methods %}
-                          {% if authenticator["authmech"] == "yubi_otp" %}
-                            <a href="#" class="list-group-item yubi-authenticator-selection">
-                              <i class="bi bi-key-fill" style="margin-right: 5px"></i>
-                              <span>{{ authenticator["key_id"] }}</span>
-                              <input type="hidden" value="{{ authenticator["id"] }}" />
-                            </a>
-                          {% endif %}
-                        {% endfor %}
-                      </div>
-                      <div class="collapse pending-tfa-collapse" id="collapseYubiTFA">
+                      <div class="collapse in pending-tfa-collapse" id="collapseYubiTFA">
                         <div class="form-group">
                           <div class="input-group">
                             <span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>