Skip to content

Commit

Permalink
Send email to user to unlock their accounts after anti bruteforce lock.
Browse files Browse the repository at this point in the history
  • Loading branch information
corentin-soriano committed Nov 26, 2024
1 parent 37ceb6e commit 42da711
Show file tree
Hide file tree
Showing 6 changed files with 141 additions and 3 deletions.
2 changes: 2 additions & 0 deletions includes/language/english.php
Original file line number Diff line number Diff line change
Expand Up @@ -1185,6 +1185,8 @@
'bruteforce_wait' => 'Too many failed attempts, your account is blocked until: ',
'bruteforce_unlock_at' => 'Account unlocked at (anti bruteforce): ',
'bruteforce_reset_account' => 'Reset anti bruteforce of user',
'bruteforce_reset_mail_subject' => 'TEAMPASS - Your account is disabled',
'bruteforce_reset_mail_body' => 'Hello #name#,<br/><br/>Your teampass account has been locked due to a large number of authentication failures.<br/><br/>You can unblock it by clicking on this link <a href="#reset_url#" target="_blank">#reset_url#</a><br/><br/>Automatic unlock: #unlock_at#',
'settings_ldap_usergroup' => 'LDAP group to search',
'settings_ldap_usergroup_tip' => 'Enter the LDAP group in the directory where allowed user logins are stored. Example: cn=sysadmins,ou=groups,dc=example,dc=com',
'server_password_change_enable' => 'Enable changing password on distant server (using ssh connection)',
Expand Down
2 changes: 2 additions & 0 deletions includes/language/french.php
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,8 @@
'bruteforce_wait' => 'Trop de tentatives échouées, votre compte est bloqué jusqu&apos;à : ',
'bruteforce_unlock_at' => 'Déblocage du compte (anti bruteforce) : ',
'bruteforce_reset_account' => 'Réinitialiser l&apos;anti bruteforce de l&apos;utilisateur',
'bruteforce_reset_mail_subject' => 'TEAMPASS - Votre compte est désactivé',
'bruteforce_reset_mail_body' => 'Bonjour #name#,<br/><br/>Votre compte teampass a été verouillé en raison d&apos;un grand nombre d&apos;échecs d&apos;authentification.<br/><br/>Vous pouvez le débloquer en cliquant sur ce lien <a href="#reset_url#" target="_blank">#reset_url#</a><br/><br/>Déblocage automatique : #unlock_at#',
'settings_ldap_usergroup' => 'Groupe LDAP dans lequel faire la recherche',
'settings_ldap_usergroup_tip' => 'Groupe LDAP dans lequel les utilisateurs doivent être membre pour pouvoir se connecter. Exemple : cn=sysadmins,ou=groups,dc=example,dc=com',
'server_password_change_enable' => 'Activer le changement automatique du mot de passe du compte du serveur (en utilisant une connexion SSH)',
Expand Down
1 change: 1 addition & 0 deletions install/install.queries.php
Original file line number Diff line number Diff line change
Expand Up @@ -1376,6 +1376,7 @@ function encryptFollowingDefuse($message, $ascii_key)
`value` VARCHAR(500) NOT NULL,
`date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`unlock_at` TIMESTAMP NULL DEFAULT NULL,
`unlock_code` VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) CHARSET=utf8;"
);
Expand Down
14 changes: 14 additions & 0 deletions install/upgrade_run_3.1.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,10 +626,24 @@
`value` VARCHAR(500) NOT NULL,
`date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`unlock_at` TIMESTAMP NULL DEFAULT NULL,
`unlock_code` VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) CHARSET=utf8;"
);

// Add unlock_code column
try {
$alter_table_query = "
ALTER TABLE `" . $pre . "auth_failures`
ADD COLUMN `unlock_code` VARCHAR(50) NULL DEFAULT NULL;";
mysqli_begin_transaction($db_link);
mysqli_query($db_link, $alter_table_query);
mysqli_commit($db_link);
} catch (Exception $e) {
// Rollback transaction if index already exists.
mysqli_rollback($db_link);
}

//---<END 3.1.2


Expand Down
81 changes: 81 additions & 0 deletions self-unlock.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

declare(strict_types=1);

/**
* Teampass - a collaborative passwords manager.
* ---
* This file is part of the TeamPass project.
*
* TeamPass is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License.
*
* TeamPass is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Certain components of this file may be under different licenses. For
* details, see the `licenses` directory or individual file headers.
* ---
* @file 2fa.js.php
* @author Nils Laumaillé ([email protected])
* @copyright 2009-2024 Teampass.net
* @license GPL-3.0
* @see https://www.teampass.net
*/


use Symfony\Component\HttpFoundation\Request as SymfonyRequest;

// Load functions
require_once __DIR__. '/includes/config/include.php';
require_once __DIR__.'/sources/main.functions.php';

// init
loadClasses();

// Get username and OTP from GET parameters
$request = SymfonyRequest::createFromGlobals();
$username = $request->query->get('login', '');
$otp = $request->query->get('otp', '');

// Redirect user to teampass if username or otp is not provided
if (empty($username) || empty($otp)) {
header('Location: ./index.php');
exit;
}

// Check for existing lock
$result = DB::queryFirstField(
'SELECT 1
FROM ' . prefixTable('auth_failures') . '
WHERE unlock_at = (
SELECT MAX(unlock_at)
FROM ' . prefixTable('auth_failures') . '
WHERE unlock_at > %s
AND source = %s AND value = %s)
AND unlock_code = %s',
date('Y-m-d H:i:s', time()),
'login',
$username,
$otp
);

// Delete all logs for this user if provided OTP is correct
if ($result) {
DB::delete(
prefixTable('auth_failures'),
'source = %s AND value = %s',
'login',
$username
);
}

// Redirect user to teampass
header('Location: ./index.php');
exit;
44 changes: 41 additions & 3 deletions sources/identify.php
Original file line number Diff line number Diff line change
Expand Up @@ -2617,7 +2617,7 @@ function identifyDoAzureChecks(
* @param string $source - The source of the failed attempt (login or remote_ip).
* @param string $value - The value for this source (username or IP address).
* @param int $limit - The failure attempt limit after which the account/IP
* will be locked.
* will be locked.
*/
function handleFailedAttempts($source, $value, $limit) {
// Count failed attempts from this source
Expand All @@ -2633,19 +2633,57 @@ function handleFailedAttempts($source, $value, $limit) {
$count++;

// Calculate unlock time if number of attempts exceeds limit
$unlock_at = $count >= $limit
$unlock_at = $count >= $limit
? date('Y-m-d H:i:s', time() + (($count - $limit + 1) * 600))
: NULL;

// Unlock account one time code
$unlock_code = ($count >= $limit && $source === 'login')
? generateQuickPassword(30, false)
: NULL;

// Insert the new failure into the database
DB::insert(
prefixTable('auth_failures'),
[
'source' => $source,
'value' => $value,
'unlock_at' => $unlock_at,
'unlock_code' => $unlock_code,
]
);

if ($unlock_at !== null && $source === 'login') {
$configManager = new ConfigManager();
$SETTINGS = $configManager->getAllSettings();
$lang = new Language($SETTINGS['default_language']);

// Get user email
$userInfos = DB::QueryFirstRow(
'SELECT email, name
FROM '.prefixTable('users').'
WHERE login = %s',
$value
);

// No valid email address for user
if (!$userInfos || !filter_var($userInfos['email'], FILTER_VALIDATE_EMAIL))
return;

$unlock_url = $SETTINGS['cpassman_url'].'/self-unlock.php?login='.$value.'&otp='.$unlock_code;

sendMailToUser(
$userInfos['email'],
$lang->get('bruteforce_reset_mail_body'),
$lang->get('bruteforce_reset_mail_subject'),
[
'#name#' => $userInfos['name'],
'#reset_url#' => $unlock_url,
'#unlock_at#' => $unlock_at,
],
true
);
}
}

/**
Expand All @@ -2659,7 +2697,7 @@ function handleFailedAttempts($source, $value, $limit) {
*/
function addFailedAuthentication($username, $ip) {
$user_limit = 10;
$ip_limit = 20;
$ip_limit = 30;

// Remove old logs (more than 24 hours)
DB::delete(
Expand Down

0 comments on commit 42da711

Please sign in to comment.