From 2620dfd7f3e8b150f6dec85fcbd9e4bebf954f4c Mon Sep 17 00:00:00 2001 From: Pinga <121483313+getpinga@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:50:29 +0200 Subject: [PATCH] Added ability to have password policy and password expiration --- cp/app/Controllers/Auth/AuthController.php | 7 +++++++ cp/app/Controllers/Auth/PasswordController.php | 17 ++++++++++++++++- cp/app/Controllers/RegistrarsController.php | 12 +++++++++++- cp/app/Controllers/UsersController.php | 14 ++++++++++++++ cp/bootstrap/helper.php | 4 +++- 5 files changed, 51 insertions(+), 3 deletions(-) diff --git a/cp/app/Controllers/Auth/AuthController.php b/cp/app/Controllers/Auth/AuthController.php index 55e8405..9343829 100644 --- a/cp/app/Controllers/Auth/AuthController.php +++ b/cp/app/Controllers/Auth/AuthController.php @@ -93,6 +93,13 @@ class AuthController extends Controller unset($_SESSION['2fa_email'], $_SESSION['2fa_password'], $_SESSION['is2FAEnabled']); if ($login===true) { + // Check if password renewal is needed + $passwordLastChanged = $_SESSION['password_last_changed'][$_SESSION['auth_user_id']] ?? 0; + if (checkPasswordRenewal($passwordLastChanged)) { + Auth::logout(); + redirect()->route('forgot.password')->with('error','Your password is expired. Please change it'); + } + $db = $container->get('db'); $currentDateTime = new \DateTime(); $currentDate = $currentDateTime->format('Y-m-d H:i:s.v'); // Current timestamp diff --git a/cp/app/Controllers/Auth/PasswordController.php b/cp/app/Controllers/Auth/PasswordController.php index 2187ee4..75e8b1c 100644 --- a/cp/app/Controllers/Auth/PasswordController.php +++ b/cp/app/Controllers/Auth/PasswordController.php @@ -69,7 +69,13 @@ class PasswordController extends Controller * @throws \Pinga\Auth\AuthError */ public function updatePassword(Request $request, Response $response){ + global $container; $data = $request->getParsedBody(); + $db = $container->get('db'); + $userId = $db->selectValue( + 'SELECT user_id FROM users_resets WHERE selector = ? AND expires > ? ORDER BY expires DESC LIMIT 1', + [ $data['selector'], time() ] + ); $validation = $this->validator->validate($request, [ 'password' => v::notEmpty()->stringType()->length(8), 'password2' => v::notEmpty(), @@ -78,10 +84,13 @@ class PasswordController extends Controller if ($validation->failed()) { redirect()->route('update.password',[],['selector'=>urlencode($data['selector']),'token'=>urlencode($data['token'])]); } - elseif (!v::equals($data['password'])->validate($data['password2'])) { redirect()->route('update.password',[],['selector'=>urlencode($data['selector']),'token'=>urlencode($data['token'])])->with('error','The password do not match.'); } + if (!checkPasswordComplexity($data['password2'])) { + redirect()->route('update.password',[],['selector'=>urlencode($data['selector']),'token'=>urlencode($data['token'])])->with('error','Password too weak. Use a stronger password.'); + } + $_SESSION['password_last_changed'][$userId] = time(); Auth::resetPasswordUpdate($data['selector'], $data['token'], $data['password']); } @@ -91,6 +100,7 @@ class PasswordController extends Controller * @throws \Pinga\Auth\AuthError */ public function changePassword(Request $request, Response $response){ + global $container; $data = $request->getParsedBody(); $validation = $this->validator->validate($request, [ 'old_password' => v::notEmpty(), @@ -99,6 +109,11 @@ class PasswordController extends Controller if ($validation->failed()) { redirect()->route('profile'); } + if (!checkPasswordComplexity($data['new_password'])) { + redirect()->route('profile')->with('error','Password too weak. Use a stronger password.'); + } + $userId = $container->get('auth')->user()['id']; + $_SESSION['password_last_changed'][$userId] = time(); Auth::changeCurrentPassword($data['old_password'], $data['new_password']); } } diff --git a/cp/app/Controllers/RegistrarsController.php b/cp/app/Controllers/RegistrarsController.php index 6e1dc31..4e2c40c 100644 --- a/cp/app/Controllers/RegistrarsController.php +++ b/cp/app/Controllers/RegistrarsController.php @@ -115,6 +115,11 @@ class RegistrarsController extends Controller return $response->withHeader('Location', '/registrar/create')->withStatus(302); } + if (!checkPasswordComplexity($data['panelPassword'])) { + $this->container->get('flash')->addMessage('error', 'Password too weak. Use a stronger password'); + return $response->withHeader('Location', '/registrar/create')->withStatus(302); + } + $db->beginTransaction(); try { @@ -650,7 +655,12 @@ class RegistrarsController extends Controller $this->container->get('flash')->addMessage('error', $errorText); return $response->withHeader('Location', '/registrar/update/'.$registrar)->withStatus(302); } - + + if (!checkPasswordComplexity($data['panelPassword'])) { + $this->container->get('flash')->addMessage('error', 'Password too weak. Use a stronger password'); + return $response->withHeader('Location', '/registrar/update/'.$registrar)->withStatus(302); + } + if (!empty($_SESSION['registrars_user_email'])) { $regEmail = $_SESSION['registrars_user_email'][0]; } else { diff --git a/cp/app/Controllers/UsersController.php b/cp/app/Controllers/UsersController.php index 38c66cf..de2a5f9 100644 --- a/cp/app/Controllers/UsersController.php +++ b/cp/app/Controllers/UsersController.php @@ -83,6 +83,11 @@ class UsersController extends Controller return $response->withHeader('Location', '/user/create')->withStatus(302); } + if (!checkPasswordComplexity($password)) { + $this->container->get('flash')->addMessage('error', 'Password too weak. Use a stronger password'); + return $response->withHeader('Location', '/user/create')->withStatus(302); + } + $registrars = $db->select("SELECT id, clid, name FROM registrar"); if ($_SESSION["auth_roles"] != 0) { $registrar = true; @@ -146,6 +151,7 @@ class UsersController extends Controller 'registered' => \time() ] ); + $userId = $db->getlastInsertId(); $db->commit(); } catch (Exception $e) { @@ -154,6 +160,7 @@ class UsersController extends Controller return $response->withHeader('Location', '/user/create')->withStatus(302); } + $_SESSION['password_last_changed'][$userId] = time(); $this->container->get('flash')->addMessage('success', 'User ' . $email . ' has been created successfully'); return $response->withHeader('Location', '/users')->withStatus(302); } @@ -305,6 +312,11 @@ class UsersController extends Controller return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302); } + if (!checkPasswordComplexity($password)) { + $this->container->get('flash')->addMessage('error', 'Password too weak. Use a stronger password'); + return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302); + } + // Check if username already exists (excluding the current user) if ($username && $username !== $old_username) { $existingUsername = $db->selectValue('SELECT COUNT(*) FROM users WHERE username = ? AND username != ?', [$username, $old_username]); @@ -395,7 +407,9 @@ class UsersController extends Controller return $response->withHeader('Location', '/user/update/'.$old_username)->withStatus(302); } + $userId = $db->selectValue('SELECT id from users WHERE username = ?', [ $username ]); unset($_SESSION['user_to_update']); + $_SESSION['password_last_changed'][$userId] = time(); $this->container->get('flash')->addMessage('success', 'User ' . $username . ' has been updated successfully on ' . $update); return $response->withHeader('Location', '/user/update/'.$username)->withStatus(302); } diff --git a/cp/bootstrap/helper.php b/cp/bootstrap/helper.php index aa22c01..1e42e85 100644 --- a/cp/bootstrap/helper.php +++ b/cp/bootstrap/helper.php @@ -693,8 +693,10 @@ function checkPasswordComplexity($password) { $score = $zxcvbn->passwordStrength($password)['score']; if ($score < $requiredScore) { // Score ranges from 0 (weak) to 4 (strong) - throw new Exception('Password too weak. Use a stronger password.'); + return false; } + + return true; } function checkPasswordRenewal($lastPasswordUpdateTimestamp) {