106 lines
3.2 KiB
PHP
106 lines
3.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Service;
|
||
|
||
use App\Repository\UserRepository;
|
||
|
||
final class AuthService
|
||
{
|
||
public function __construct(private UserRepository $users)
|
||
{
|
||
}
|
||
|
||
public function canAttempt(string $email, string $ip): bool
|
||
{
|
||
// backoff: 5 dernières tentatives/5 min
|
||
$sql = "select count(*)
|
||
from login_attempts
|
||
where ip = :ip
|
||
and attempted_at > now() - interval '5 minutes'
|
||
and success = false";
|
||
$st = \App\Infrastructure\Database::pdo()->prepare($sql);
|
||
$st->execute([':ip' => $ip]);
|
||
$fails = (int)$st->fetchColumn();
|
||
return $fails < 10; // à ajuster
|
||
}
|
||
|
||
|
||
|
||
public function login(string $email, string $password, string $ip): bool
|
||
{
|
||
$user = $this->users->findByEmail($email);
|
||
$ok = $user && $user->isActive && password_verify($password, $user->passwordHash);
|
||
|
||
$pdo = \App\Infrastructure\Database::pdo();
|
||
$st = $pdo->prepare('insert into login_attempts(email, ip, success) values(:e, :ip, :s)');
|
||
$st->bindValue(':e', $email, \PDO::PARAM_STR);
|
||
$st->bindValue(':ip', $ip, \PDO::PARAM_STR);
|
||
$st->bindValue(':s', $ok, \PDO::PARAM_BOOL);
|
||
$st->execute();
|
||
|
||
if ($ok) {
|
||
\App\Infrastructure\Session::regenerate();
|
||
$_SESSION['uid'] = $user->id;
|
||
$_SESSION['email'] = $user->email;
|
||
}
|
||
return $ok;
|
||
}
|
||
|
||
|
||
public function changePassword(string $userId, string $currentPassword, string $newPassword): bool
|
||
{
|
||
// Récupération de l’utilisateur (rapide : requête directe ; tu peux créer findById() si tu préfères)
|
||
$pdo = \App\Infrastructure\Database::pdo();
|
||
$st = $pdo->prepare('select id, email, password_hash, is_active from users where id = :id');
|
||
$st->execute([':id' => $userId]);
|
||
$row = $st->fetch(\PDO::FETCH_ASSOC);
|
||
if (!$row || !(bool)$row['is_active']) {
|
||
return false;
|
||
}
|
||
|
||
// Vérifier l’ancien mot de passe
|
||
if (!password_verify($currentPassword, (string)$row['password_hash'])) {
|
||
return false;
|
||
}
|
||
|
||
// Politique minimale : longueur uniquement (espaces autorisés)
|
||
if (mb_strlen($newPassword) < 7) {
|
||
return false;
|
||
}
|
||
// (optionnel) interdire seulement le caractère NUL
|
||
if (strpos($newPassword, "\0") !== false) {
|
||
return false;
|
||
}
|
||
|
||
// Mettre à jour le hash
|
||
$newHash = password_hash($newPassword, PASSWORD_ARGON2ID);
|
||
(new \App\Repository\UserRepository(\App\Infrastructure\Database::get()))->updatePassword($row['id'], $newHash);
|
||
|
||
// (Optionnel) rotation session
|
||
\App\Infrastructure\Session::regenerate();
|
||
return true;
|
||
}
|
||
|
||
public function register(string $email, string $password): string
|
||
{
|
||
$hash = password_hash($password, PASSWORD_ARGON2ID);
|
||
return $this->users->create($email, $hash);
|
||
}
|
||
|
||
public static function requireAuth(): void
|
||
{
|
||
if (!isset($_SESSION['uid'])) {
|
||
header('Location: /login');
|
||
exit;
|
||
}
|
||
}
|
||
|
||
public static function logout(): void
|
||
{
|
||
$_SESSION = [];
|
||
session_destroy();
|
||
}
|
||
}
|