Перейти к содержимому

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

Одна схема во всех трёх фронтах: телефон → одноразовый код → пара токенов.

Метод и путь Что делает
POST /auth/otp/request выдаёт код, отвечает именем канала доставки
POST /auth/otp/verify проверяет код, отдаёт пару токенов
POST /auth/refresh обменивает refresh на новую пару, ротируя старый
GET /auth/me текущий пользователь
PATCH /me профиль игрока
GET/POST /admin/admins, PUT /admin/admins/{userId}/role управление админ-ролями

Сервер генерирует шестизначный код и хранит только SHA-256-хеш, TTL — 5 минут.

Лимит: 3 запроса на телефон за 10 минут. Проверка и запись идут под pg_advisory_xact_lock по номеру, поэтому параллельные запросы лимит не обходят. На проверку — не больше 5 попыток на код; успешная проверка гасит код (consumedAt).

Доставка — цепочка каналов в порядке OTP_CHANNELS, по умолчанию telegram,max,sms. Канал без настроек в окружении возвращает false, и OtpSender идёт к следующему. Вне production в хвост цепочки добавляется FakeOtpChannel — поэтому dev и тесты работают без секретов. Печать кода в stdout включается только флагом OTP_LOG_CODES и в проде не работает никогда.

Телефон из PLATFORM_ADMIN_PHONES автоматически получает adminRole = super_admin — в том числе если аккаунт уже существовал. Забаненный аккаунт получает 403 USER_BANNED.

Access — JWT на 15 минут, внутри sub и флаг adm. Refresh — на 30 дней, это случайные 32 байта, в базе лежит хеш.

POST /auth/refresh ротирует токен: старый помечается revokedAt guarded-апдейтом. Повторное использование уже потраченного refresh даёт 401 INVALID_REFRESH_TOKEN — это не баг, а обнаружение переиспользования.

Клиенты хранят токены в SecureStore (нативно) или localStorage (веб и админка). В HTTP-клиенте каждого фронта единый api(): подставляет Authorization, на 401 один раз делает refresh и повторяет запрос. Конкурентные вызовы разделяют один промис refresh — иначе десяток параллельных запросов сжёг бы токен гонкой. Неудачный refresh чистит хранилище.

При создании аккаунта все GuestPlayer с тем же телефоном привязываются к нему, их Result переезжают на нового пользователя, и профиль пересчитывается по хронологии через ProfileRebuildService. История walk-in-игр не теряется.

Платформенный adminRole — матрица в apps/server/src/auth/permissions.ts:

Право super_admin moderator support marketing
organizers.manage
seasons.manage
rating_config.manage
moderation
push.send
metrics.view
admins.manage
cities.manage
content.manage
tournaments.manage

Роль внутри клубаowner | td | dealer в OrganizerMember. Проверка OrganizersService.requireRole стоит на всех операциях клуба; день игры и live ведут owner и td.

Источник истины — серверная проверка. Интерфейс лишь прячет недоступное, и полагаться на это как на защиту нельзя.