Merge pull request #4685 from FreddleSpl0it/tfa-patch

Update TFA flow
This commit is contained in:
Patrick Schult 2022-08-25 14:38:37 +02:00 committed by GitHub
commit 02512e0f4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 69 additions and 63 deletions

View File

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

View File

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

View File

@ -182,30 +182,19 @@ function recursiveBase64StrToArrayBuffer(obj) {
keyboard: false 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 // validate Time based OTP tfa
$("#pending_tfa_tab_totp").click(function(){ $("#pending_tfa_tab_totp").click(function(){
$(".yubi-authenticator-selection").removeClass("active");
$(".webauthn-authenticator-selection").removeClass("active"); $(".webauthn-authenticator-selection").removeClass("active");
$("#collapseYubiTFA").collapse('hide');
$("#collapseWebAuthnTFA").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").click(function(){
$(".totp-authenticator-selection").removeClass("active"); $(".totp-authenticator-selection").removeClass("active");
@ -216,13 +205,37 @@ function recursiveBase64StrToArrayBuffer(obj) {
$("#collapseTotpTFA").collapse('show'); $("#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 // validate WebAuthn tfa
$("#pending_tfa_tab_webauthn").click(function(){ $("#pending_tfa_tab_webauthn").click(function(){
$(".totp-authenticator-selection").removeClass("active"); $(".totp-authenticator-selection").removeClass("active");
$(".yubi-authenticator-selection").removeClass("active");
$("#collapseTotpTFA").collapse('hide'); $("#collapseTotpTFA").collapse('hide');
$("#collapseYubiTFA").collapse('hide');
}); });
$(".webauthn-authenticator-selection").click(function(){ $(".webauthn-authenticator-selection").click(function(){
$(".webauthn-authenticator-selection").removeClass("active"); $(".webauthn-authenticator-selection").removeClass("active");

View File

@ -206,20 +206,9 @@
<form role="form" method="post"> <form role="form" method="post">
<legend> <legend>
<i class="bi bi-shield-fill-check"></i> <i class="bi bi-shield-fill-check"></i>
Authenticators Authenticate
</legend> </legend>
<div class="list-group"> <div class="collapse in pending-tfa-collapse" id="collapseYubiTFA">
{% 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="form-group"> <div class="form-group">
<div class="input-group"> <div class="input-group">
<span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span> <span class="input-group-addon" id="yubi-addon"><img alt="Yubicon Icon" src="/img/yubi.ico"></span>