Загрузка данных


<?php
require_once __DIR__ . '/includes/init.php';

if (is_logged_in()) {
    redirect(url('profile.php'));
}

const LOGIN_MAX_ATTEMPTS = 5;
const LOGIN_LOCK_MINUTES = 10;
const LOGIN_WINDOW_MINUTES = 10;

function login_get_ip(): string
{
    return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
}

function login_get_lock_info(PDO $pdo, string $email, string $ipAddress): ?array
{
    $stmt = $pdo->prepare('
        SELECT 
            locked_until,
            TIMESTAMPDIFF(SECOND, NOW(), locked_until) AS seconds_left
        FROM login_attempts
        WHERE email = :email
          AND ip_address = :ip_address
          AND locked_until IS NOT NULL
          AND locked_until > NOW()
        ORDER BY locked_until DESC
        LIMIT 1
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $row = $stmt->fetch();

    return $row ?: null;
}

function login_count_failed_attempts(PDO $pdo, string $email, string $ipAddress): int
{
    $stmt = $pdo->prepare('
        SELECT COUNT(*) AS total
        FROM login_attempts
        WHERE email = :email
          AND ip_address = :ip_address
          AND is_success = 0
          AND locked_until IS NULL
          AND attempted_at >= DATE_SUB(NOW(), INTERVAL ' . LOGIN_WINDOW_MINUTES . ' MINUTE)
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $row = $stmt->fetch();

    return (int)($row['total'] ?? 0);
}

function login_record_failed_attempt(PDO $pdo, string $email, string $ipAddress): string
{
    $failedAttempts = login_count_failed_attempts($pdo, $email, $ipAddress) + 1;

    if ($failedAttempts >= LOGIN_MAX_ATTEMPTS) {
        $stmt = $pdo->prepare('
            INSERT INTO login_attempts (
                email,
                ip_address,
                is_success,
                locked_until
            ) VALUES (
                :email,
                :ip_address,
                0,
                DATE_ADD(NOW(), INTERVAL ' . LOGIN_LOCK_MINUTES . ' MINUTE)
            )
        ');

        $stmt->execute([
            'email' => $email,
            'ip_address' => $ipAddress
        ]);

        return 'Слишком много неправильных попыток. Вход заблокирован на ' . LOGIN_LOCK_MINUTES . ' минут.';
    }

    $stmt = $pdo->prepare('
        INSERT INTO login_attempts (
            email,
            ip_address,
            is_success
        ) VALUES (
            :email,
            :ip_address,
            0
        )
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $leftAttempts = LOGIN_MAX_ATTEMPTS - $failedAttempts;

    return 'Неверный email или пароль. Осталось попыток: ' . $leftAttempts . '.';
}

function login_record_success(PDO $pdo, string $email, string $ipAddress): void
{
    $stmt = $pdo->prepare('
        DELETE FROM login_attempts
        WHERE email = :email
          AND ip_address = :ip_address
          AND is_success = 0
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);

    $stmt = $pdo->prepare('
        INSERT INTO login_attempts (
            email,
            ip_address,
            is_success
        ) VALUES (
            :email,
            :ip_address,
            1
        )
    ');

    $stmt->execute([
        'email' => $email,
        'ip_address' => $ipAddress
    ]);
}

if (is_post()) {
    csrf_validate();

    $email = strtolower(trim($_POST['email'] ?? ''));
    $password = $_POST['password'] ?? '';
    $ipAddress = login_get_ip();

    if ($email === '' || $password === '') {
        flash_set('danger', 'Введите email и пароль.');
        redirect(url('login.php'));
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        flash_set('danger', 'Введите корректный email.');
        redirect(url('login.php'));
    }

    $lock = login_get_lock_info($pdo, $email, $ipAddress);

    if ($lock) {
        $secondsLeft = max(1, (int)$lock['seconds_left']);
        $minutesLeft = (int)ceil($secondsLeft / 60);

        flash_set('danger', 'Вход временно заблокирован. Повторите попытку через ' . $minutesLeft . ' мин.');
        redirect(url('login.php'));
    }

    $stmt = $pdo->prepare('
        SELECT
            u.id,
            u.full_name,
            u.email,
            u.password_hash,
            u.status,
            r.code AS role_code
        FROM users u
        JOIN roles r ON r.id = u.role_id
        WHERE LOWER(u.email) = :email
        LIMIT 1
    ');

    $stmt->execute([
        'email' => $email
    ]);

    $user = $stmt->fetch();

    if (!$user || !password_verify($password, $user['password_hash'])) {
        $message = login_record_failed_attempt($pdo, $email, $ipAddress);
        flash_set('danger', $message);
        redirect(url('login.php'));
    }

    if ($user['status'] !== 'active') {
        flash_set('danger', 'Учётная запись недоступна. Обратитесь к администратору.');
        redirect(url('login.php'));
    }

    login_record_success($pdo, $email, $ipAddress);

    session_regenerate_id(true);

    $_SESSION['user_id'] = (int)$user['id'];
    $_SESSION['user'] = [
        'id' => (int)$user['id'],
        'full_name' => $user['full_name'],
        'email' => $user['email'],
        'role' => $user['role_code']
    ];
    $_SESSION['role'] = $user['role_code'];
    $_SESSION['user_role'] = $user['role_code'];
    $_SESSION['user_email'] = $user['email'];
    $_SESSION['user_name'] = $user['full_name'];

    $updateStmt = $pdo->prepare('
        UPDATE users
        SET last_login_at = NOW()
        WHERE id = :id
    ');

    $updateStmt->execute([
        'id' => $user['id']
    ]);

    flash_set('success', 'Вы успешно вошли в систему.');

    if ($user['role_code'] === 'admin') {
        redirect(url('admin/index.php'));
    }

    if ($user['role_code'] === 'manager') {
        redirect(url('manager/index.php'));
    }

    redirect(url('profile.php'));
}

require_once __DIR__ . '/includes/header.php';
?>

<div class="auth-page">
    <form method="post" class="auth-card">
        <?= csrf_field() ?>

        <h1>Вход</h1>

        <p class="text-muted">
            Войдите в аккаунт для оформления заявок и работы с личным кабинетом.
        </p>

        <div class="mb-3">
            <label class="form-label">Email</label>
            <input
                type="email"
                name="email"
                class="form-control"
                required
                autocomplete="email"
            >
        </div>

        <div class="mb-3">
            <label class="form-label">Пароль</label>
            <input
                type="password"
                name="password"
                class="form-control"
                required
                autocomplete="current-password"
            >
        </div>

        <button type="submit" class="btn btn-primary w-100">
            Войти
        </button>

        <div class="auth-links">
            <a href="<?= e(url('register.php')) ?>">Создать аккаунт</a>
        </div>
    </form>
</div>

<?php require_once __DIR__ . '/includes/footer.php'; ?>