Files
slim-blog/src/Auth/Application/AuthApplicationService.php
2026-03-16 16:58:54 +01:00

110 lines
3.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Auth\Application;
use App\Auth\AuthServiceInterface;
use App\Auth\Domain\LoginRateLimitPolicy;
use App\Auth\LoginAttemptRepositoryInterface;
use App\Shared\Exception\NotFoundException;
use App\Shared\Http\SessionManagerInterface;
use App\User\Exception\WeakPasswordException;
use App\User\User;
use App\User\UserRepositoryInterface;
class AuthApplicationService implements AuthServiceInterface
{
private readonly LoginRateLimitPolicy $rateLimitPolicy;
public function __construct(
private readonly UserRepositoryInterface $userRepository,
private readonly SessionManagerInterface $sessionManager,
private readonly LoginAttemptRepositoryInterface $loginAttemptRepository,
?LoginRateLimitPolicy $rateLimitPolicy = null,
) {
$this->rateLimitPolicy = $rateLimitPolicy ?? new LoginRateLimitPolicy();
}
public function checkRateLimit(string $ip): int
{
$this->loginAttemptRepository->deleteExpired();
$row = $this->loginAttemptRepository->findByIp($ip);
if ($row === null || $row['locked_until'] === null) {
return 0;
}
$lockedUntil = new \DateTime($row['locked_until']);
$now = new \DateTime();
if ($lockedUntil <= $now) {
return 0;
}
$secondsLeft = $lockedUntil->getTimestamp() - $now->getTimestamp();
return max(1, (int) ceil($secondsLeft / 60));
}
public function recordFailure(string $ip): void
{
$this->loginAttemptRepository->recordFailure($ip, $this->rateLimitPolicy->maxAttempts(), $this->rateLimitPolicy->lockMinutes());
}
public function resetRateLimit(string $ip): void
{
$this->loginAttemptRepository->resetForIp($ip);
}
public function authenticate(string $username, string $plainPassword): ?User
{
$user = $this->userRepository->findByUsername(mb_strtolower(trim($username)));
if ($user === null) {
return null;
}
if (!password_verify(trim($plainPassword), $user->getPasswordHash())) {
return null;
}
return $user;
}
public function changePassword(int $userId, string $currentPassword, string $newPassword): void
{
$user = $this->userRepository->findById($userId);
if ($user === null) {
throw new NotFoundException('Utilisateur', $userId);
}
if (!password_verify(trim($currentPassword), $user->getPasswordHash())) {
throw new \InvalidArgumentException('Mot de passe actuel incorrect');
}
if (mb_strlen(trim($newPassword)) < 8) {
throw new WeakPasswordException();
}
$newHash = password_hash(trim($newPassword), PASSWORD_BCRYPT, ['cost' => 12]);
$this->userRepository->updatePassword($userId, $newHash);
}
public function isLoggedIn(): bool
{
return $this->sessionManager->isAuthenticated();
}
public function login(User $user): void
{
$this->sessionManager->setUser($user->getId(), $user->getUsername(), $user->getRole());
}
public function logout(): void
{
$this->sessionManager->destroy();
}
}