You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
2.8 KiB
102 lines
2.8 KiB
<?php |
|
|
|
namespace App\Services; |
|
|
|
use Illuminate\Support\Facades\Cache; |
|
use Illuminate\Support\Str; |
|
use InvalidArgumentException; |
|
use RuntimeException; |
|
use Throwable; |
|
|
|
class LdapPasswordResetService |
|
{ |
|
private const TOKEN_TTL_MINUTES = 60; |
|
|
|
public function __construct( |
|
private readonly LdapAuthService $authService, |
|
private readonly LdapUserService $userService, |
|
private readonly MailDeliveryService $mailDelivery |
|
) { |
|
} |
|
|
|
public function sendResetLink(string $username): bool |
|
{ |
|
$username = trim($username); |
|
|
|
if (! preg_match('/^[a-zA-Z0-9._-]+$/', $username)) { |
|
return false; |
|
} |
|
|
|
$user = $this->authService->lookupUser($username); |
|
|
|
if ($user === null || $user['email'] === '') { |
|
return false; |
|
} |
|
|
|
$token = Str::random(64); |
|
|
|
Cache::put($this->cacheKey($token), $username, now()->addMinutes(self::TOKEN_TTL_MINUTES)); |
|
|
|
$url = route('password.reset', ['token' => $token]); |
|
|
|
$this->mailDelivery->sendTemplated($user['email'], 'password_reset', [ |
|
'username' => $username, |
|
'display_name' => $user['display_name'], |
|
'reset_url' => $url, |
|
'expires_minutes' => self::TOKEN_TTL_MINUTES, |
|
]); |
|
|
|
return true; |
|
} |
|
|
|
public function validateToken(string $token): ?string |
|
{ |
|
if (! preg_match('/^[a-zA-Z0-9]{64}$/', $token)) { |
|
return null; |
|
} |
|
|
|
return Cache::get($this->cacheKey($token)); |
|
} |
|
|
|
public function resetPassword(string $token, string $password): void |
|
{ |
|
$username = $this->validateToken($token); |
|
|
|
if ($username === null) { |
|
throw new InvalidArgumentException('Reset link is invalid or has expired.'); |
|
} |
|
|
|
try { |
|
$this->userService->resetPassword($username, $password); |
|
Cache::forget($this->cacheKey($token)); |
|
$this->notifyPasswordChanged($username, session('ldap_username')); |
|
} catch (RuntimeException $e) { |
|
throw new InvalidArgumentException($e->getMessage()); |
|
} |
|
} |
|
|
|
public function notifyPasswordChanged(string $username, ?string $changedBy = null): void |
|
{ |
|
try { |
|
$user = $this->authService->lookupUser($username); |
|
|
|
if ($user === null || $user['email'] === '') { |
|
return; |
|
} |
|
|
|
$this->mailDelivery->notifyPasswordChanged( |
|
$user['email'], |
|
$username, |
|
$user['display_name'], |
|
$changedBy |
|
); |
|
} catch (Throwable) { |
|
// Notification must not block password change. |
|
} |
|
} |
|
|
|
private function cacheKey(string $token): string |
|
{ |
|
return 'ldap_password_reset:' . $token; |
|
} |
|
}
|
|
|