max(get_setting('password_max_length', 64)); $requireUpper = get_setting('password_require_uppercase', false); $requireLower = get_setting('password_require_lowercase', false); if ($requireUpper && $requireLower) { $rules->mixedCase(); } elseif ($requireUpper) { $rules->rules(['regex:/[A-Z]/']); } elseif ($requireLower) { $rules->rules(['regex:/[a-z]/']); } if (get_setting('password_require_numeric', false)) { $rules->numbers(); } if (get_setting('password_require_special', false)) { $rules->symbols(); } return $rules; } /** * Check if the password has expired for a user. */ public static function isPasswordExpired(User $user): bool { $expiryDays = get_setting('password_expiry_days', 0); if ($expiryDays <= 0) { return false; } $lastChanged = $user->password_changed_at ?? $user->created_at; return $lastChanged->addDays($expiryDays)->isPast(); } /** * Verify that the new password is not in the user's password history. */ public static function checkHistory(User $user, string $newPassword): void { $historyCount = get_setting('password_history_count', 0); if ($historyCount <= 0) { return; } $histories = $user->passwordHistories() ->latest() ->take($historyCount) ->get(); foreach ($histories as $history) { if (Hash::check($newPassword, $history->password)) { throw ValidationException::withMessages([ 'password' => __('You cannot reuse any of your last :count passwords.', ['count' => $historyCount]), ]); } } } /** * Record the current password into history and update changed_at timestamp. */ public static function recordPasswordChange(User $user, string $newPasswordHash): void { $historyCount = get_setting('password_history_count', 0); // 1. Record to history (only if enabled) if ($historyCount > 0) { $user->passwordHistories()->create([ 'password' => $newPasswordHash, ]); } // 2. Update timestamp $user->update([ 'password_changed_at' => now(), ]); // 3. Optional: Prune old history — keep exactly $historyCount entries $historyCount = get_setting('password_history_count', 0); if ($historyCount > 0) { $user->passwordHistories() ->orderBy('created_at', 'desc') ->skip($historyCount) ->take(100) ->delete(); } } }