110 lines
3.2 KiB
PHP
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();
|
|
}
|
|
}
|