diff --git a/ProcessMaker/Http/Controllers/Api/ChangePasswordController.php b/ProcessMaker/Http/Controllers/Api/ChangePasswordController.php index e14ea0f0d7..4083cabcaf 100644 --- a/ProcessMaker/Http/Controllers/Api/ChangePasswordController.php +++ b/ProcessMaker/Http/Controllers/Api/ChangePasswordController.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Http\Controllers\Api; +use Carbon\Carbon; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -35,6 +36,10 @@ public function update(Request $request) $user->setAttribute('password', Hash::make($request->json('password'))); $user->setAttribute('force_change_password', 0); + $user->setAttribute('password_changed_at', Carbon::now()->toDateTimeString()); + + // Remove login error message related to password expired if exists + session()->forget('login-error'); try { $user = $user->save(); diff --git a/ProcessMaker/Http/Controllers/Api/UserController.php b/ProcessMaker/Http/Controllers/Api/UserController.php index 59aedb7a08..caf2c72aca 100644 --- a/ProcessMaker/Http/Controllers/Api/UserController.php +++ b/ProcessMaker/Http/Controllers/Api/UserController.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Http\Controllers\Api; +use Carbon\Carbon; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -167,6 +168,10 @@ public function store(Request $request) if (isset($fields['password'])) { $fields['password'] = Hash::make($fields['password']); + $fields['password_changed_at'] = Carbon::now()->toDateTimeString(); + + // Remove login error message related to password expired if exists + session()->forget('login-error'); } $user->fill($fields); @@ -301,6 +306,10 @@ public function update(User $user, Request $request) $fields = $request->json()->all(); if (isset($fields['password'])) { $fields['password'] = Hash::make($fields['password']); + $fields['password_changed_at'] = Carbon::now()->toDateTimeString(); + + // Remove login error message related to password expired if exists + session()->forget('login-error'); } $original = $user->getOriginal(); $user->fill($fields); diff --git a/ProcessMaker/Http/Middleware/VerifyChangePasswordNeeded.php b/ProcessMaker/Http/Middleware/VerifyChangePasswordNeeded.php index 018d853a70..08754c3849 100644 --- a/ProcessMaker/Http/Middleware/VerifyChangePasswordNeeded.php +++ b/ProcessMaker/Http/Middleware/VerifyChangePasswordNeeded.php @@ -2,6 +2,7 @@ namespace ProcessMaker\Http\Middleware; +use Carbon\Carbon; use Closure; use Illuminate\Support\Facades\Auth; @@ -20,6 +21,14 @@ public function handle($request, Closure $next) return redirect()->route('password.change'); } + if ($this->checkPasswordExpiration()) { + // Set the error message + session()->put('login-error', _('Your password has expired.')); + + // Redirect to change password screen + return redirect()->route('password.change'); + } + return $next($request); } @@ -27,4 +36,14 @@ public function checkForForceChangePassword() { return Auth::user() && Auth::user()->force_change_password; } + + public function checkPasswordExpiration() + { + $validationRequired = config('password-policies.expiration_days') && + Auth::user() && Auth::user()->password_changed_at; + + return $validationRequired && + (Carbon::now()->diffInDays(Auth::user()->password_changed_at) >= + config('password-policies.expiration_days')); + } } diff --git a/ProcessMaker/Models/User.php b/ProcessMaker/Models/User.php index 541e91ba8c..fdbb48213b 100644 --- a/ProcessMaker/Models/User.php +++ b/ProcessMaker/Models/User.php @@ -122,6 +122,7 @@ class User extends Authenticatable implements HasMedia 'manager_id', 'schedule', 'force_change_password', + 'password_changed_at', ]; protected $appends = [ diff --git a/config/password-policies.php b/config/password-policies.php index 1bb8d9cefa..a0bbb07490 100644 --- a/config/password-policies.php +++ b/config/password-policies.php @@ -6,6 +6,6 @@ 'numbers' => env('PASSWORD_POLICY_NUMBERS', true), 'uppercase' => env('PASSWORD_POLICY_UPPERCASE', true), 'special' => env('PASSWORD_POLICY_SPECIAL', true), - //'expiration_days' => env('PASSWORD_POLICY_EXPIRATION_DAYS', 0), // 0 never expires + 'expiration_days' => env('PASSWORD_POLICY_EXPIRATION_DAYS', null), 'login_attempts' => env('PASSWORD_POLICY_LOGIN_ATTEMPTS', 5), ]; diff --git a/database/migrations/2023_11_28_200725_add_password_changed_at_column_to_users_table.php b/database/migrations/2023_11_28_200725_add_password_changed_at_column_to_users_table.php new file mode 100644 index 0000000000..c40f6fa2c9 --- /dev/null +++ b/database/migrations/2023_11_28_200725_add_password_changed_at_column_to_users_table.php @@ -0,0 +1,28 @@ +timestamp('password_changed_at')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('password_changed_at'); + }); + } +}; diff --git a/resources/lang/de.json b/resources/lang/de.json index b8a0e92c0a..106ee7b5c9 100644 --- a/resources/lang/de.json +++ b/resources/lang/de.json @@ -1801,5 +1801,6 @@ "This environment already contains an older version of the {{ item }} named '{{ name }}.'": "Diese Umgebung enthält bereits eine ältere Version des {{ item }} namens '{{ name }}'.", "This environment already contains the same version of the {{ item }} named '{{ name }}.'": "Diese Umgebung enthält bereits die gleiche Version des {{ item }} namens '{{ name }}'.", "Visit our Gallery for more Templates": "Besuchen Sie unsere Galerie für mehr Vorlagen", - "Start a new process from a blank canvas, a text description, or a preset template.": "Starten Sie einen neuen Prozess von einer leeren Leinwand, einer Textbeschreibung oder einer voreingestellten Vorlage." + "Start a new process from a blank canvas, a text description, or a preset template.": "Starten Sie einen neuen Prozess von einer leeren Leinwand, einer Textbeschreibung oder einer voreingestellten Vorlage.", + "Your password has expired.": "Your password has expired." } \ No newline at end of file diff --git a/resources/lang/en.json b/resources/lang/en.json index b7e37e07ca..4c361672e6 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -1870,6 +1870,7 @@ "Your account has been timed out for security.": "Your account has been timed out for security.", "Your password has been reset!": "Your password has been reset!", "Your password has been updated.": "Your password has been updated.", + "Your password has expired.": "Your password has expired.", "Your PMQL contains invalid syntax.": "Your PMQL contains invalid syntax.", "Your PMQL search could not be completed.": "Your PMQL search could not be completed.", "Your profile was saved.": "Your profile was saved.", diff --git a/resources/lang/es.json b/resources/lang/es.json index 7ce552475a..ccc3d6ceb5 100644 --- a/resources/lang/es.json +++ b/resources/lang/es.json @@ -1802,5 +1802,6 @@ "This environment already contains an older version of the {{ item }} named '{{ name }}.'": "Este entorno ya contiene una versión más antigua del {{ item }} llamado '{{ name }}'.", "This environment already contains the same version of the {{ item }} named '{{ name }}.'": "Este entorno ya contiene la misma versión del {{ item }} llamado '{{ name }}'.", "Visit our Gallery for more Templates": "Visita nuestra Galería para más Plantillas", - "Start a new process from a blank canvas, a text description, or a preset template.": "Inicie un nuevo proceso desde un lienzo en blanco, una descripción de texto o una plantilla preestablecida." + "Start a new process from a blank canvas, a text description, or a preset template.": "Inicie un nuevo proceso desde un lienzo en blanco, una descripción de texto o una plantilla preestablecida.", + "Your password has expired.": "Tu contraseña ha expirado." } \ No newline at end of file diff --git a/resources/lang/fr.json b/resources/lang/fr.json index e00faf5a98..4786805050 100644 --- a/resources/lang/fr.json +++ b/resources/lang/fr.json @@ -1801,5 +1801,6 @@ "This environment already contains an older version of the {{ item }} named '{{ name }}.'": "Cet environnement contient déjà une version plus ancienne de {{ item }} nommé '{{ name }}'.", "This environment already contains the same version of the {{ item }} named '{{ name }}.'": "Cet environnement contient déjà la même version de l'{{ item }} nommé '{{ name }}'.", "Visit our Gallery for more Templates": "Visitez notre Galerie pour plus de Modèles", - "Start a new process from a blank canvas, a text description, or a preset template.": "Démarrez un nouveau processus à partir d'une toile vierge, d'une description textuelle ou d'un modèle prédéfini." + "Start a new process from a blank canvas, a text description, or a preset template.": "Démarrez un nouveau processus à partir d'une toile vierge, d'une description textuelle ou d'un modèle prédéfini.", + "Your password has expired.": "Your password has expired." } \ No newline at end of file