feat: add app and database modules
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Auth;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class PasswordPolicyService
|
||||
{
|
||||
/**
|
||||
* Get the password validation rules based on system settings.
|
||||
*/
|
||||
public static function getRules(): Password
|
||||
{
|
||||
$rules = Password::min(get_setting('password_min_length', 8))
|
||||
->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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user