Перейти к содержанию

Аутентификация и сеансы

XC_VM поддерживает три контекста аутентификации - администратора, реселлера и игрока - каждый с изолированными сеансовыми ключами, отдельными потоками входа в систему и независимой логикой проверки. В этом документе описан полный жизненный цикл аутентификации от входа в систему до проверки сеанса и обеспечения безопасности.


Обзор процесса входа в систему

Все три контекста следуют схожему шаблону высокого уровня, с зависящими от контекста различиями в проверке и хранении сеанса.

Взаимодействие администратора и реселлера

POST request with credentials
  -> BruteforceGuard checks (flood / brute-force)
  -> Optional reCAPTCHA verification
  -> Credential lookup via UserRepository::getAuthUserByCredentials()
  -> Access code / group validation
  -> Permission check (is_admin or is_reseller)
  -> User status check (enabled/disabled)
  -> Password re-hash + session write + login log

Поток игроков

POST request with credentials
  -> UserRepository::getUserInfo() lookup
  -> Line type checks (reject E2, MAG, Stalker)
  -> Expiration date check
  -> admin_enabled / enabled status checks
  -> IP allowlist, country restriction, user agent, ISP checks
  -> Session write + redirect
  -> BruteforceGuard::checkFlood() on any failure

Аутентификатор

Файл: src/Core/Auth/Authenticator.php

Authenticator::login(array $data, bool $bypassRecaptcha = false): array

Способ входа в систему администратора. Шаги по порядку:

  1. Проверка повторной проверки (если параметр recaptcha_enable включен и не пропущен).
  2. Поиск учетных данных с помощью UserRepository::getAuthUserByCredentials().
  3. Проверка группы кодов доступа - имя пользователя member_group_id должно входить в разрешенные группы текущего кода доступа, в противном случае кодов доступа не должно существовать.
  4. Permission check -- is_admin must be true for the user's group.
  5. Проверка состояния - $rUserInfo['status'] == 1 (включено).
  6. В случае успеха: повторно хэширует пароль, обновляет last_login и ip в базе данных, записывает сеансовые ключи, регистрирует логин.

Значения сеанса, записанные в зависимости от успеха:

$_SESSION['hash']   = $rUserInfo['id'];       // User ID
$_SESSION['ip']     = $rIP;                   // Client IP at login
$_SESSION['code']   = AuthRepository::getCurrentCode(); // Current access code
$_SESSION['verify'] = md5($rUserInfo['username'] . '||' . $rCrypt); // Verification hash

Authenticator::resellerLogin(array $data): array

Метод входа в систему торгового посредника. Структура идентична login() с этими отличиями:

  • Повторная проверка всегда проверяется, когда она включена (параметр обхода отсутствует).
  • Для проверки разрешений требуется is_reseller вместо is_admin.
  • Возвращает STATUS_NOT_RESELLER, если у пользователя нет разрешения торгового посредника.
  • Журналы входа в систему записываются с типом RESELLER вместо ADMIN.

Значения сеанса, записанные в зависимости от успеха:

$_SESSION['reseller'] = $rUserInfo['id'];       // User ID
$_SESSION['rip']      = $rIP;                   // Client IP at login
$_SESSION['rcode']    = AuthRepository::getCurrentCode(); // Current access code
$_SESSION['rverify']  = md5($rUserInfo['username'] . '||' . $rCrypt); // Verification hash

Константы статуса входа в систему

Определено в src/bootstrap.php через XC_Bootstrap::defineStatusConstants():

Постоянный Ценность Значение
STATUS_FAILURE 0 Общий сбой (неверные учетные данные или универсальная ошибка)
STATUS_SUCCESS 1 Вход в систему был успешным
STATUS_DISABLED 5 Учетная запись отключена
STATUS_NOT_ADMIN 6 У пользователя нет прав администратора
STATUS_INVALID_CAPTCHA 12 Не удалось выполнить проверку reCAPTCHA
STATUS_INVALID_CODE 13 Несоответствие кода доступа/ группы
STATUS_NOT_RESELLER 35 У пользователя нет разрешения торгового посредника

Хэширование паролей

Authenticator::hashPassword(string $password, ?string $salt = null, int $rounds = 20000): string

Использует crypt() с SHA-512 ($6$). Формат salt равен $6$rounds=20000$<salt>$, где <salt> - это 16 шестнадцатеричных символов, полученных из openssl_random_pseudo_bytes(16). Пароли повторно хэшируются при каждом успешном входе в систему, что приводит к замене значения salt.

Authenticator::checkPassword(string $password, string $storedHash): bool

Проверяет пароль в виде открытого текста на соответствие сохраненному хэшу, используя crypt($password, $storedHash), с возможностью сравнения по времени с помощью hash_equals(). Сохраненный хэш содержит алгоритм, раунды и соль, поэтому crypt() воспроизводит правильный хэш для сравнения.


Аутентификация игрока

Файл: src/Public/Controllers/Player/PlayerLoginController.php

Процедура входа игрока в систему принципиально отличается от процедуры администратора/реселлера. Она аутентифицирует "линии" конечного пользователя (подписки на IPTV), а не операторов панели.

Процесс входа в систему

PlayerLoginController::processLogin() выполняет эти проверки в порядке:

  1. Поиск учетных данных -- UserRepository::getUserInfo() (отличается от getAuthUserByCredentials, используемого администратором/реселлером).
  2. Отклонение типа линии -- Линии E2, MAG и Stalker отклоняются с определенными кодами ошибок.
  3. Проверка истечения срока годности -- exp_date должно быть равно null или в будущем.
  4. Проверка с поддержкой администратора -- admin_enabled == 0 возвращает CLIENT_BANNED.
  5. Проверка, включенная пользователем -- enabled == 0 возвращает CLIENT_DISABLED.
  6. Список разрешенных IP-адресов -- Если для пользователя задано значение allowed_ips, IP-адрес клиента должен совпадать (решается с помощью gethostbyname).
  7. Ограничение по стране - Два режима:
  8. Для каждого пользователя: если задано значение forced_country, а не ALL, страна GeoIP должна совпадать.
  9. Глобальный: если нет переопределения для каждого пользователя, устанавливается глобальный параметр allow_countries (если только он не содержит ALL).
  10. Проверка агента пользователя -- Если для пользователя задано значение allowed_ua, то пользовательский агент HTTP должен соответствовать.
  11. флаг Проверка интернет-провайдера -- isp_violate отклоняет соединение.
  12. Проверка сервера интернет-провайдера -- Если значение isp_is_server равно true и пользователь не является рестримером, соединение будет отклонено.

Каждый сбой запускает BruteforceGuard::checkFlood() перед возвратом кода ошибки.

Коды ошибок проигрывателя

Постоянный Ценность Значение
CLIENT_INVALID 0 Неверное имя пользователя или пароль
CLIENT_IS_E2 1 Линии Enigma запрещены
CLIENT_IS_MAG 2 МАГНИТНЫЕ линии не допускаются
CLIENT_IS_STALKER 3 Линии преследования запрещены
CLIENT_EXPIRED 4 Срок действия строки истек
CLIENT_BANNED 5 Строка заблокирована (admin_enabled = 0)
CLIENT_DISABLED 6 Линия отключена (включено = 0)
CLIENT_DISALLOWED 7 Не удалось выполнить проверку IP/страны/UA/провайдера

Ключи к сеансу игрока

$_SESSION['phash']   = $rUserInfo['id'];
$_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password']);

В контексте player хранятся только два сеансовых ключа. В отличие от ключей администратора и реселлера, здесь нет ключей activity, ip или code. Это означает, что в сеансе player нет таймаута бездействия и не обнаруживается изменение IP-адреса на уровне сеанса.


Проверка сеанса при загрузке страницы

После первоначального входа в систему при каждой загрузке страницы, прошедшей проверку подлинности, выполняется повторная проверка сеанса. Это происходит в файлах начальной загрузки, а не в SessionManager.

Проверка сеанса администратора

Файл: src/Public/Views/admin/functions.php

Если установлено значение $_SESSION['hash'], при каждой загрузке страницы выполняются следующие проверки:

  1. Поиск пользователя -- UserRepository::getRegisteredUserById($_SESSION['hash']). Если пользователь больше не существует, сеанс завершается.
  2. Проверка прав доступа -- AuthRepository::getPermissions() должен возвращать допустимый набор с is_admin == true.
  3. Проверка IP-адреса - Сравнивает текущий IP-адрес с $_SESSION['ip']:
  4. Если параметр ip_subnet_match включен: сравниваются только первые три октета (например, 192.168.1.* соответствует 192.168.1.*).
  5. Если параметр ip_subnet_match отключен: требуется точное совпадение IP-адресов.
  6. Если IP-адрес не совпадает и включена настройка ip_logout, сеанс завершается.
  7. Если IP-адрес не совпадает и ip_logout отключен, $_SESSION['ip'] автоматически обновляется до нового IP-адреса.
  8. Проверить проверку хэша Verify -- $_SESSION['verify'] должно быть равно md5($rUserInfo['username'] . '||' . $rUserInfo['password']). Это гарантирует, что сеанс будет аннулирован в случае изменения пароля.

Если какая-либо проверка завершается неудачей, сеанс очищается с помощью SessionManager::clearContext('admin'), и пользователь перенаправляется на индексную страницу.

Проверка сеанса работы с реселлером

Файл: src/Infrastructure/Bootstrap/reseller_functions.php

Логика идентична проверке администратора, но используются сеансовые ключи реселлера:

  • Проверяет $_SESSION['reseller'] на наличие идентификатора пользователя.
  • Использует $_SESSION['rip'] для сравнения IP-адресов.
  • Использует $_SESSION['rverify'] для проверки хэша.
  • Проверяет разрешение is_reseller вместо is_admin.

Соответствие IP-подсети и поведение при выходе из системы по IP-адресу такое же, как у администратора.

Тайм-аут сеанса администрирования

Файл: src/Public/Views/admin/session.php

Для сеансов администрирования выполняется отдельная проверка тайм-аута сеанса. Если заданы значения $_SESSION['hash'] и $_SESSION['last_activity'], а с момента last_activity прошло более 60 минут, ключи сеанса (hash, ip, code, verify, last_activity) не заданы. При каждом действительном запросе обновляется $_SESSION['last_activity'], и сеанс закрывается для записи.

Проверка сеанса игрока

Контекст проигрывателя не выполняет проверку IP-адреса, соответствие подсети или тайм-аут активности на уровне сеанса. Сохраняются только phash и pverify, и проверка выполняется на прикладном уровне для повторной проверки этих значений по базе данных.


Менеджер сеанса

Файл: src/Core/Auth/SessionManager.php

Унифицированный сеансовый API, который абстрагирует различные имена сеансовых ключей в разных контекстах. Предназначен для замены устаревших файлов admin/session.php и reseller/session.php.

Контекстная ключевая карта

Логический ключ Ключ администратора $_SESSION Ключ реселлера $_SESSION Ключ игрока $_SESSION
auth hash reseller phash
activity last_activity rlast_activity --
ip ip rip --
code code rcode --
verify verify rverify pverify

Контекст игрока намеренно опускает сопоставления activity, ip и code.

Методы

start(string $context, int $timeout = 60): void

Запускает сеанс PHP (если он еще не запущен), устанавливает активный контекст и запускает checkTimeout() для завершения устаревших сеансов. Контекст должен быть 'admin', 'reseller' или 'player'.

requireAuth(?string $loginUrl = null): void

Проверяет наличие аутентифицированного сеанса. Если запрос направлен напрямую на session.php, возвращает ответ в формате JSON {"result": true/false} (используется для опроса сеанса AJAX). В противном случае перенаправляет не прошедших проверку пользователей на страницу входа в систему. В случае успеха вызывает touch() для обновления временной метки действия.

isAuthenticated(): bool

Проверка на отсутствие блокировки. Возвращает значение true, если сеанс был запущен и установлен ключ auth.

getUser(): mixed

Возвращает значение, сохраненное в ключе сеанса auth (идентификатор пользователя для администратора/реселлера или идентификатор строки для игрока), или null, если аутентификация не пройдена.

getValue(string $name): mixed

Возвращает значение сеанса по его логическому имени (auth, activity, ip, code, verify). Логическое имя сопоставляется с фактическим ключом $_SESSION на основе текущего контекста.

setValue(string $name, mixed $value): void

Устанавливает значение сеанса по логическому имени.

login(mixed $hash, ?string $ip = null): void

Создает аутентифицированный сеанс, задавая значения auth и activity. При необходимости сохраняет IP-адрес клиента.

destroy(): void

Очищает все ключи сеанса для текущего контекста. Если никакой другой контекст не активен (проверяет противоположный контекст администратора/реселлера), уничтожает весь сеанс PHP.

clearContext(string $context): void

Очищает все ключи сеанса для определенного контекста, не разрушая сеанс. Простая замена устаревшего destroySession($type).

touch(): void

Обновляет $_SESSION[$activityKey] до текущей временной метки и вызывает session_write_close(), чтобы снять блокировку сеанса.

getContext(): ?string

Возвращает текущую контекстную строку ('admin', 'reseller', 'player') или null, если она не задана.

Поведение по истечении времени ожидания

SessionManager::DEFAULT_TIMEOUT равно 60 минутам. Метод checkTimeout() (вызываемый автоматически start()) сравнивает время, прошедшее с момента last_activity. Если время ожидания превышено, все ключи сеанса, зависящие от контекста, сбрасываются, что приводит к выходу пользователя из системы.

Поскольку контекст игрока не имеет ключа activity в карте ключей, проверка тайм-аута не применяется к сессиям игрока.


Охранник с применением грубой силы

Файл: src/Core/Auth/BruteforceGuard.php

Централизованное ограничение скорости и защита от перебора. Все методы используют состояние на основе файла, хранящееся в FLOOD_TMP_PATH (/home/xc_vm/tmp/flood/). Разрешенные IP-адреса (IP-адреса сервера) и IP-адреса, указанные в параметре flood_ips_exclude, всегда исключаются.

checkFlood(?string $ip = null, bool $useCachedMode = false): void

Скорость - ограничивает количество запросов по IP-адресу в пределах настраиваемого временного интервала.

  • Настройки: flood_limit (максимальное количество запросов), flood_seconds (размер окна).
  • Файл состояния: FLOOD_TMP_PATH . $ip - сохраняет объект JSON с числом requests и временной меткой last_request.
  • Поведение: Отслеживает количество запросов в пределах временного окна. Если количество превышает flood_limit, IP-адрес блокируется (заносится в таблицу blocked_ips или передается через Redis в кэшированном/потоковом режиме). Файл состояния удаляется после блокировки.
  • Используется: Вход игрока (вызывается при каждой неудачной попытке входа в систему), конечные точки потоковой передачи.

checkBruteforce(?string $ip = null, ?string $mac = null, ?string $username = null, bool $useCachedMode = false): void

Обнаруживает атаки методом перебора на основе количества уникальных MAC-адресов или имен пользователей, обнаруженных с одного IP-адреса.

  • Настройки: bruteforce_mac_attempts, bruteforce_username_attempts ( максимальное количество уникальных значений), bruteforce_frequency (временной интервал в секундах).
  • Файл состояния: FLOOD_TMP_PATH . $ip . '_mac' или FLOOD_TMP_PATH . $ip . '_user' - сохраняет попытки в виде пар {term: timestamp}.
  • Поведение: Попытки с истекшим сроком действия (за пределами частотного диапазона) отсекаются с помощью truncateAttempts(). Если количество уникальных запросов превышает допустимое, IP-адрес блокируется.
  • Используется: Конечные точки потоковой аутентификации.

checkAuthFlood(array $user, ?string $ip = null): void

Ограничивает скорость запросов на аутентификацию для конкретной комбинации пользователь +IP. Предназначен для ограничения повторных попыток авторизации без полной блокировки.

  • Настройки: auth_flood_limit (максимальное количество попыток), auth_flood_seconds (окно), auth_flood_sleep (задержка в секундах при блокировке).
  • Файл состояния: FLOOD_TMP_PATH . $userId . '_' . $ip - сохраняет попытки в виде индексированных временных меток плюс необязательную временную метку block_until.
  • Поведение: Когда количество попыток превышает допустимое значение, устанавливается временная метка block_until. Последующие запросы в течение периода блокировки задерживаются на auth_flood_sleep секунды (через sleep()). IP-адрес не блокируется навсегда. Пользователи Restreamer (is_restreamer) освобождаются от этого требования.
  • Используется: Потоковая аутентификация.

truncateAttempts(array $attempts, int $frequency, bool $list = false): array

Отфильтровывает просроченные попытки из массива отслеживания. Если значение $list равно true, массив обрабатывается как индексированный (для checkAuthFlood); в противном случае как ассоциативный с ключом по термину (для checkBruteforce).

Блокирующий механизм

Когда IP-адрес заблокирован:

  • Обычный режим: Выполняет вставку в таблицу базы данных blocked_ips с указанием причины (FLOOD ATTACK или BRUTEFORCE MAC/USER ATTACK) и обновляет кэш BlocklistService.
  • Режим кэширования/потоковой передачи ($useCachedMode = true): Устанавливает сигнал Redis (bruteforce_attack/$ip или flood_attack/$ip) через RedisManager::setSignal() для блокировки потокового контекста без записи в базу данных.
  • В обоих режимах выполняется касание файла-маркера FLOOD_TMP_PATH . 'block_' . $ip для быстрой проверки на уровне файловой системы.

Безопасность сеанса

В контексте начальной загрузки администратора сеансовые файлы cookie устанавливаются в соответствии со строгой политикой SameSite:

$params['samesite'] = 'Strict';
session_set_cookie_params($params);
session_start();

Проверка хэша

Проверяющий хэш ($_SESSION['verify'] / $_SESSION['rverify'] / $_SESSION['pverify']) вычисляется следующим образом:

md5($username . '||' . $hashedPassword)

Это значение сравнивается с текущими значениями базы данных при каждой загрузке страницы. Если администратор изменяет пароль пользователя (что изменяет сохраненный хэш), все существующие сеансы для этого пользователя автоматически становятся недействительными, поскольку проверяемый хэш больше не будет совпадать.

Для логинов администратора и торгового посредника пароль повторно хэшируется во время входа в систему, поэтому используется $rCrypt (новый хэш). Для логинов игроков используется существующий сохраненный хэш $rUserInfo['password'].

Обработка изменений IP-адреса

Два параметра управляют поведением при изменении IP-адреса:

Установка Эффект
ip_logout Если включено, завершает сеанс при изменении IP-адреса клиента (точного или из подсети, в зависимости от ip_subnet_match).
ip_subnet_match Когда этот параметр включен, сравниваются только первые три октета IP-адреса, вместо того чтобы требовать точного совпадения. Позволяет пользователям с динамическими IP-адресами в пределах одной подсети сохранять сеанс связи.

Когда ip_logout отключено и IP-адрес изменяется, сохраненный IP-адрес сеанса автоматически обновляется до нового IP-адреса.


Регистрация входа в систему

Когда параметр save_login_logs включен, все попытки входа в систему (успешные и неудачные) записываются в таблицу login_logs:

INSERT INTO `login_logs`(`type`, `access_code`, `user_id`, `status`, `login_ip`, `date`)
VALUES($type, $codeId, $userId, $status, $ip, $timestamp);
Колонка Описание
type ADMIN или RESELLER
access_code Идентификатор текущего кода доступа
user_id Идентификатор пользователя (0 для неверных учетных данных)
status SUCCESS, INVALID_LOGIN, INVALID_CODE, NOT_ADMIN, DISABLED
login_ip IP-адрес клиента
date Временная метка Unix

Логины игроков не записываются в login_logs.


Авторизация (после входа в систему)

После аутентификации два дополнительных уровня авторизации определяют, к чему пользователь может получить доступ:

Authorization

Файл: src/Core/Auth/Authorization.php

Авторизация на уровне объекта. Проверяет, есть ли у текущего пользователя разрешение на доступ к определенному ресурсу (пользователь, поток и т.д.) на основе иерархии владельцев реселлеров и групповых разрешений.

  • Authorization::hasResellerPermissions($type) -- устанавливает флажок единственного разрешения на $rPermissions.
  • Authorization::check($type, $id) -- проверяет доступ к определенному ресурсу по типу и идентификатору.

PageAuthorization

Файл: src/Core/Auth/PageAuthorization.php

Управление доступом на уровне страницы. Определяет, разрешают ли групповые права текущего пользователя доступ к определенной странице панели администратора или торгового посредника.

  • PageAuthorization::checkResellerPermissions($page) -- сопоставляет названия страниц с флагами требуемых разрешений и возвращает, разрешен ли доступ.

Связанные файлы

Файл Цель
src/Core/Auth/Authenticator.php Логика входа администратора и реселлера в систему, хэширование паролей
src/Core/Auth/SessionManager.php Унифицированный сеансовый API с сопоставлением контекстных ключей
src/Core/Auth/BruteforceGuard.php Ограничение скорости и защита от перебора
src/Core/Auth/Authorization.php Проверки авторизации на уровне объекта
src/Core/Auth/PageAuthorization.php Управление доступом на уровне страницы
src/Public/Controllers/Player/PlayerLoginController.php Процесс входа игрока в систему с проверкой безопасности
src/Public/Views/admin/functions.php Проверка сеанса администратора при каждой загрузке страницы
src/Public/Views/admin/session.php Тайм-аут сеанса администратора и проверка сеанса AJAX
src/Infrastructure/Bootstrap/reseller_functions.php Проверка сеанса реселлера при каждой загрузке страницы
src/Domain/User/UserRepository.php Поиск учетных данных (getAuthUserByCredentials)
src/bootstrap.php Определения констант состояния, контексты начальной загрузки
src/Core/Config/Paths.php FLOOD_TMP_PATH определение