Skip to content

Commit

Permalink
Merge pull request #4466 from corentin-soriano/password_policy
Browse files Browse the repository at this point in the history
Add strong user password policy.
  • Loading branch information
nilsteampassnet authored Nov 15, 2024
2 parents fae22cd + 0652004 commit d6937bb
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 3 deletions.
2 changes: 1 addition & 1 deletion includes/core/load.js.php
Original file line number Diff line number Diff line change
Expand Up @@ -1003,9 +1003,9 @@ function() {
$('#dialog-user-change-password-do, #dialog-user-change-password-close').attr('disabled', 'disabled');

data = {
'user_id': store.get('teampassUser').user_id,
'old_password': $('#profile-current-password').val(),
'new_password': $('#profile-password').val(),
'new_password_confirm': $('#profile-password-confirm').val(),
}
if (debugJavascript === true) console.log(data);

Expand Down
1 change: 1 addition & 0 deletions includes/language/english.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@
'settings_ldap_additional_user_dn_tip' => 'This value is used in addition to the base DN when searching and loading users. If no value is supplied, the subtree search will start from the base DN. Examples: ou=Users ; cn=users',
'settings_ldap_additional_user_dn' => 'Additional User DN',
'ldap_user_has_changed_his_password' => 'Your authentication password has been changed in your AD since you last get logged in in Teampass. We need to adapt your encryption key. Please provide your previous password and the current one.',
'user_password_policy_tip' => 'The new password must:<br/> - Be different from the previous one<br/> - Contain at least 10 characters<br/> - Contain at least one uppercase letter and one lowercase letter<br/> - Contain at least one number or special character<br/> - Not contain your name, first name, username, or email.',
'provide_your_previous_password' => 'Your previous password',
'admin_change_user_password_info' => 'This operation will reset the selected user current password.',
'sending_email_message' => 'Now sending email to user, please wait',
Expand Down
1 change: 1 addition & 0 deletions includes/language/french.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
'settings_ldap_additional_user_dn_tip' => 'Cette valeur est utilisée en plus de la base DN lors de la recherche et du chargement des utilisateurs. Si aucune valeur n&apos;est fournie, la recherche des sous-arbres commencera à partir de la base. Exemples: ou=Users ; cn=users',
'settings_ldap_additional_user_dn' => 'Identifiant DN utilisateur supplémentaire',
'ldap_user_has_changed_his_password' => 'Votre mot de passe d&apos;authentification a été changé dans votre annuaire LDAP depuis votre dernière connexion à Teampass. Nous devons adapter votre clé de chiffrement. Veuillez fournir votre précédent mot de passe et l&apos;actuel.',
'user_password_policy_tip' => 'Le nouveau mot de passe doit :<br/> - Etre différent du précédent<br/> - Contenir au moins 10 caractères<br/> - Contenir au moins une lettre en majuscule et une en minuscule<br/> - Contenir au moins un chiffre ou caractère spécial<br/> - Ne pas contenir votre nom/prénom/identifiant/mail.',
'provide_your_previous_password' => 'Votre précédent mot de passe',
'admin_change_user_password_info' => 'Cette opération réinitialisera le mot de passe actuel de l&apos;utilisateur sélectionné.',
'sending_email_message' => 'Envoi de l&apos;email à l&apos;utilisateur, veuillez patienter',
Expand Down
4 changes: 3 additions & 1 deletion index.php
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,9 @@
<div class="card-body">
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="mb-5 alert alert-info hidden" id="dialog-user-change-password-info">
<div class="mb-5 alert alert-info" id="dialog-user-change-password-info">
<i class="icon fa-solid fa-info mr-2"></i>
<?php echo $lang->get('user_password_policy_tip'); ?>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
Expand Down
68 changes: 68 additions & 0 deletions sources/main.functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4412,3 +4412,71 @@ function checkIdsExist(array $ids, string $tableName, string $fieldName) : array

return $missingIds; // Renvoie les IDs qui n'existent pas dans la table
}

/**
* Check that a password is strong. The password needs to have at least :
* - length >= 10.
* - Uppercase and lowercase chars.
* - Number or special char.
* - Not contain username, name or mail part.
* - Different from previous password.
*
* @param string $password - Password to ckeck.
* @return bool - true if the password is strong, false otherwise.
*/
function isPasswordStrong($password) {
$session = SessionManager::getSession();

// Password can't contain login, name or lastname
$forbiddenWords = [
$session->get('user-login'),
$session->get('user-name'),
$session->get('user-lastname'),
];

// Cut out the email
if ($email = $session->get('user-email')) {
$emailParts = explode('@', $email);

if (count($emailParts) === 2) {
// Mail username (removed @domain.tld)
$forbiddenWords[] = $emailParts[0];

// Organisation name (removed username@ and .tld)
$domain = explode('.', $emailParts[1]);
if (count($domain) > 1)
$forbiddenWords[] = $domain[0];
}
}

// Search forbidden words in password
foreach ($forbiddenWords as $word) {
if (empty($word))
continue;

// Stop if forbidden word found in password
if (stripos($password, $word) !== false)
return false;
}

// Get password complexity
$length = strlen($password);
$hasUppercase = preg_match('/[A-Z]/', $password);
$hasLowercase = preg_match('/[a-z]/', $password);
$hasNumber = preg_match('/[0-9]/', $password);
$hasSpecialChar = preg_match('/[\W_]/', $password);

// Get current user hash
$userHash = DB::queryFirstRow(
"SELECT pw FROM " . prefixtable('users') . " WHERE id = %d;",
$session->get('user-id')
)['pw'];

$passwordManager = new PasswordManager();

return $length >= 8
&& $hasUppercase
&& $hasLowercase
&& ($hasNumber || $hasSpecialChar)
&& !$passwordManager->verifyPassword($userHash, $password);
}
25 changes: 24 additions & 1 deletion sources/main.queries.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,29 @@ function passwordHandler(string $post_type, /*php8 array|null|string*/ $dataRece
* Change user's authentication password
*/
case 'change_user_auth_password'://action_password

// Check new password and confirm match server side
if ($dataReceived['new_password'] !== $dataReceived['new_password_confirm']) {
return prepareExchangedData(
array(
'error' => true,
'message' => $lang->get('error_bad_credentials'),
),
'encode'
);
}

// Check if new password is strong
if (!isPasswordStrong($dataReceived['new_password'])) {
return prepareExchangedData(
array(
'error' => true,
'message' => $lang->get('complexity_level_not_reached'),
),
'encode'
);
}

return changeUserAuthenticationPassword(
(int) $session->get('user-id'),
(string) filter_var($dataReceived['old_password'], FILTER_SANITIZE_FULL_SPECIAL_CHARS),
Expand Down Expand Up @@ -3109,7 +3132,7 @@ function changeUserAuthenticationPassword(
{
$session = SessionManager::getSession();
$lang = new Language($session->get('user-language') ?? 'english');

if (isUserIdValid($post_user_id) === true) {
// Get user info
$userData = DB::queryFirstRow(
Expand Down

0 comments on commit d6937bb

Please sign in to comment.