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; } }