diff --git a/data/web/css/build/008-mailcow.css b/data/web/css/build/008-mailcow.css index f557084f..b118ca01 100644 --- a/data/web/css/build/008-mailcow.css +++ b/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 { diff --git a/data/web/inc/functions.inc.php b/data/web/inc/functions.inc.php index 08963888..4f70b4c2 100644 --- a/data/web/inc/functions.inc.php +++ b/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]); diff --git a/data/web/templates/base.twig b/data/web/templates/base.twig index 37c27e21..770decde 100644 --- a/data/web/templates/base.twig +++ b/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"); diff --git a/data/web/templates/modals/footer.twig b/data/web/templates/modals/footer.twig index 67cc3482..52e89e00 100644 --- a/data/web/templates/modals/footer.twig +++ b/data/web/templates/modals/footer.twig @@ -206,20 +206,9 @@