Auth.php 4.11 KB
<?php

final class Auth
{
    private const MAX_ATTEMPTS = 5;
    private const WINDOW_MINUTES = 15;

    public static function start(): void
    {
        if (session_status() === PHP_SESSION_NONE) {
            session_name(Config::get('app.session_name', 'ad_session'));
            session_set_cookie_params([
                'lifetime' => 0,
                'path'     => '/',
                'secure'   => (!empty($_SERVER['HTTPS'])),
                'httponly' => true,
                'samesite' => 'Lax',
            ]);
            session_start();
        }
    }

    public static function attempt(string $login, string $password): array
    {
        $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';

        if (self::isRateLimited($login, $ip)) {
            return ['ok' => false, 'error' => 'Muitas tentativas. Aguarde alguns minutos e tente novamente.'];
        }

        $ldap = new Ldap();
        $bindOk = $ldap->tryUserBind($login, $password);

        self::logAttempt($login, $ip, $bindOk);

        if (!$bindOk) {
            return ['ok' => false, 'error' => 'Usuário ou senha incorretos.'];
        }

        $admin = AdminRepository::findActiveByLogin($login);
        if (!$admin) {
            AuditLog::record($login, 'login_negado', null, 'usuário autenticado no AD mas sem permissão no sistema');
            return ['ok' => false, 'error' => 'Seu usuário não tem permissão para acessar este sistema.'];
        }

        // Atualiza o nome em cache a partir do AD, sem depender da conta de serviço falhar.
        try {
            $ldap->bindService();
            $entry = $ldap->findUserByLogin($login);
            $nome = $entry['displayname'][0] ?? null;
            if ($nome) {
                AdminRepository::touchLoginCache($login, $nome);
                $admin['nome'] = $nome;
            }
        } catch (Throwable $e) {
            // Não bloqueia o login por causa da conta de serviço.
        }

        self::start();
        session_regenerate_id(true);
        $_SESSION['login'] = $login;
        $_SESSION['nome']  = $admin['nome'] ?? $login;
        $_SESSION['papel'] = $admin['papel'];

        AuditLog::record($login, 'login', null, 'login efetuado');

        return ['ok' => true];
    }

    public static function logout(): void
    {
        self::start();
        $login = $_SESSION['login'] ?? 'desconhecido';
        AuditLog::record($login, 'logout');
        $_SESSION = [];
        session_destroy();
    }

    public static function requireLogin(): void
    {
        self::start();
        if (empty($_SESSION['login'])) {
            header('Location: index.php?erro=sessao_expirada');
            exit;
        }
    }

    public static function requireAdmin(): void
    {
        self::requireLogin();
        if (($_SESSION['papel'] ?? null) !== 'admin') {
            http_response_code(403);
            echo 'Acesso restrito a administradores.';
            exit;
        }
    }

    public static function currentLogin(): ?string
    {
        return $_SESSION['login'] ?? null;
    }

    public static function currentName(): ?string
    {
        return $_SESSION['nome'] ?? null;
    }

    public static function isAdmin(): bool
    {
        return ($_SESSION['papel'] ?? null) === 'admin';
    }

    private static function isRateLimited(string $login, string $ip): bool
    {
        $stmt = Database::pdo()->prepare(
            'SELECT COUNT(*) FROM login_attempts
             WHERE login = :login AND ip = :ip AND sucesso = 0
               AND criado_em >= (NOW() - INTERVAL :minutes MINUTE)'
        );
        $stmt->bindValue('login', $login);
        $stmt->bindValue('ip', $ip);
        $stmt->bindValue('minutes', self::WINDOW_MINUTES, PDO::PARAM_INT);
        $stmt->execute();
        return (int) $stmt->fetchColumn() >= self::MAX_ATTEMPTS;
    }

    private static function logAttempt(string $login, string $ip, bool $success): void
    {
        $stmt = Database::pdo()->prepare(
            'INSERT INTO login_attempts (login, ip, sucesso) VALUES (:login, :ip, :sucesso)'
        );
        $stmt->execute(['login' => $login, 'ip' => $ip, 'sucesso' => $success ? 1 : 0]);
    }
}