diff --git a/index.php b/index.php index a701bfaa..d59146e3 100644 --- a/index.php +++ b/index.php @@ -53,9 +53,15 @@ if ($action == '2fa_entercode') { Response::redirectTo('index.php'); exit(); } + $smessage = isset($_GET['showmessage']) ? (int)$_GET['showmessage'] : 0; + $message = ""; + if ($smessage > 0) { + $message = lng('error.2fa_wrongcode'); + } // show template to enter code UI::view('login/enter2fa.html.twig', [ - 'pagetitle' => lng('login.2fa') + 'pagetitle' => lng('login.2fa'), + 'message' => $message ]); } elseif ($action == '2fa_verify') { // verify code from 2fa code-enter form @@ -68,25 +74,25 @@ if ($action == '2fa_entercode') { // verify entered code $tfa = new FroxlorTwoFactorAuth('Froxlor ' . Settings::Get('system.hostname')); $result = ($_SESSION['secret_2fa'] == 'email' ? true : $tfa->verifyCode($_SESSION['secret_2fa'], $code, 3)); + // get user-data + $table = $_SESSION['uidtable_2fa']; + $field = $_SESSION['uidfield_2fa']; + $uid = $_SESSION['uid_2fa']; + $isadmin = $_SESSION['unfo_2fa']; // either the code is valid when using authenticator-app, or we will select userdata by id and entered code // which is temporarily stored for the customer when using email-2fa if ($result) { - // get user-data - $table = $_SESSION['uidtable_2fa']; - $field = $_SESSION['uidfield_2fa']; - $uid = $_SESSION['uid_2fa']; - $isadmin = $_SESSION['unfo_2fa']; $sel_param = [ 'uid' => $uid ]; if ($_SESSION['secret_2fa'] == 'email') { // verify code by selecting user by id and the temp. stored code, // so only if it's the correct code, we get the user-data - $sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid AND `data_2fa` = :code"); + $sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid AND `data_2fa` = :code"); $sel_param['code'] = $code; } else { // Authenticator-verification has already happened at this point, so just get the user-data - $sel_stmt = Database::prepare("SELECT * FROM $table WHERE `" . $field . "` = :uid"); + $sel_stmt = Database::prepare("SELECT * FROM " . $table . " WHERE `" . $field . "` = :uid"); } $userinfo = Database::pexecute_first($sel_stmt, $sel_param); // whoops, no (valid) user? Start again @@ -108,15 +114,50 @@ if ($action == '2fa_entercode') { // when using email-2fa, remove the one-time-code if ($userinfo['type_2fa'] == '1') { - $del_stmt = Database::prepare("UPDATE $table SET `data_2fa` = '' WHERE `" . $field . "` = :uid"); + $del_stmt = Database::prepare("UPDATE " . $table . " SET `data_2fa` = '' WHERE `" . $field . "` = :uid"); $userinfo = Database::pexecute_first($del_stmt, [ 'uid' => $uid ]); } exit(); } + // wrong 2fa code - treat like "wrong password" + $stmt = Database::prepare(" + UPDATE " . $table . " + SET `lastlogin_fail`= :lastlogin_fail, `loginfail_count`=`loginfail_count`+1 + WHERE `" . $field . "`= :uid + "); + Database::pexecute($stmt, [ + "lastlogin_fail" => time(), + "uid" => $uid + ]); + + // get data for processing further + $stmt = Database::prepare(" + SELECT `loginname`, `loginfail_count`, `lastlogin_fail` FROM " . $table . " + WHERE `" . $field . "`= :uid + "); + $fail_user = Database::pexecute_first($stmt, [ + "uid" => $uid + ]); + + if ($fail_user['loginfail_count'] >= Settings::Get('login.maxloginattempts') && $fail_user['lastlogin_fail'] > (time() - Settings::Get('login.deactivatetime'))) { + // Log failed login + $rstlog = FroxlorLogger::getInstanceOf([ + 'loginname' => $_SERVER['REMOTE_ADDR'] + ]); + $rstlog->logAction(FroxlorLogger::LOGIN_ACTION, LOG_WARNING, "User '" . $fail_user['loginname'] . "' entered wrong 2fa code too often."); + unset($fail_user); + Response::redirectTo('index.php', [ + 'showmessage' => '3' + ]); + exit(); + } + unset($fail_user); + // back to form Response::redirectTo('index.php', [ - 'showmessage' => '2' + 'action' => '2fa_entercode', + 'showmessage' => '1' ]); exit(); } elseif ($action == 'login') { diff --git a/lng/de.lng.php b/lng/de.lng.php index 83aa897f..c331ccd5 100644 --- a/lng/de.lng.php +++ b/lng/de.lng.php @@ -926,6 +926,7 @@ return [ 'domaincannotbeedited' => 'Keine Berechtigung, um die Domain %s zu bearbeiten', 'invalidcronjobintervalvalue' => 'Cronjob Intervall muss einer der folgenden Werte sein: %s', 'phpgdextensionnotavailable' => 'Die PHP GD Extension ist nicht verfügbar. Bild-Daten können nicht validiert werden.', + '2fa_wrongcode' => 'Der angegebene Code ist nicht korrekt', ], 'extras' => [ 'description' => 'Hier können Sie zusätzliche Extras einrichten, wie zum Beispiel einen Verzeichnisschutz.
Die Änderungen sind erst nach einer kurzen Zeit wirksam.', diff --git a/lng/en.lng.php b/lng/en.lng.php index e01d50e6..e4645ca3 100644 --- a/lng/en.lng.php +++ b/lng/en.lng.php @@ -995,6 +995,7 @@ return [ 'domaincannotbeedited' => 'You are not permitted to edit the domain %s', 'invalidcronjobintervalvalue' => 'Cronjob interval must be one of: %s', 'phpgdextensionnotavailable' => 'The PHP GD extension is not available. Unable to validate image-data', + '2fa_wrongcode' => 'The code entered is not valid', ], 'extras' => [ 'description' => 'Here you can add some extras, for example directory protection.
The system will need some time to apply the new settings after every change.', diff --git a/templates/Froxlor/login/enter2fa.html.twig b/templates/Froxlor/login/enter2fa.html.twig index 75dee1cc..a185c986 100644 --- a/templates/Froxlor/login/enter2fa.html.twig +++ b/templates/Froxlor/login/enter2fa.html.twig @@ -10,6 +10,13 @@
{{ pagetitle }}
+ {% if message is not empty %} + + {% endif %} +