82 lines
2.5 KiB
PHP
82 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Auth;
|
|
|
|
use App\Database;
|
|
use PDO;
|
|
|
|
final class AdminAuth
|
|
{
|
|
private PDO $db;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->db = Database::connection();
|
|
}
|
|
|
|
public function attempt(string $username, string $password): bool
|
|
{
|
|
$statement = $this->db->prepare(
|
|
'SELECT id, username, display_name, password_hash, is_active, failed_attempts, locked_until
|
|
FROM admins WHERE username = :username LIMIT 1'
|
|
);
|
|
$statement->execute(['username' => $username]);
|
|
$admin = $statement->fetch();
|
|
|
|
if (!$admin) {
|
|
password_verify($password, password_hash('invalid-login', PASSWORD_DEFAULT));
|
|
return false;
|
|
}
|
|
|
|
if (!(bool) $admin['is_active']) {
|
|
return false;
|
|
}
|
|
|
|
if ($admin['locked_until'] && strtotime($admin['locked_until']) > time()) {
|
|
return false;
|
|
}
|
|
|
|
if (!password_verify($password, $admin['password_hash'])) {
|
|
$failed = (int) $admin['failed_attempts'] + 1;
|
|
$lockedUntil = $failed >= 5 ? date('Y-m-d H:i:s', time() + 900) : null;
|
|
$update = $this->db->prepare('UPDATE admins SET failed_attempts = :failed, locked_until = :locked WHERE id = :id');
|
|
$update->execute(['failed' => $failed >= 5 ? 0 : $failed, 'locked' => $lockedUntil, 'id' => $admin['id']]);
|
|
return false;
|
|
}
|
|
|
|
$update = $this->db->prepare('UPDATE admins SET failed_attempts = 0, locked_until = NULL, last_login_at = NOW() WHERE id = :id');
|
|
$update->execute(['id' => $admin['id']]);
|
|
session_regenerate_id(true);
|
|
$_SESSION['admin'] = [
|
|
'id' => (int) $admin['id'],
|
|
'username' => $admin['username'],
|
|
'display_name' => $admin['display_name'],
|
|
'authenticated_at' => time(),
|
|
];
|
|
return true;
|
|
}
|
|
|
|
public static function check(): bool
|
|
{
|
|
return isset($_SESSION['admin']['id'], $_SESSION['admin']['authenticated_at'])
|
|
&& time() - (int) $_SESSION['admin']['authenticated_at'] < 7200;
|
|
}
|
|
|
|
public static function requireLogin(): void
|
|
{
|
|
if (!self::check()) {
|
|
unset($_SESSION['admin']);
|
|
redirect('/admin/login');
|
|
}
|
|
$_SESSION['admin']['authenticated_at'] = time();
|
|
}
|
|
|
|
public static function logout(): void
|
|
{
|
|
unset($_SESSION['admin']);
|
|
session_regenerate_id(true);
|
|
}
|
|
}
|