Аутентификация и роли
Одна схема во всех трёх фронтах: телефон → одноразовый код → пара токенов.
Контракт
Заголовок раздела «Контракт»| Метод и путь | Что делает |
|---|---|
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.
Источник истины — серверная проверка. Интерфейс лишь прячет недоступное, и полагаться на это как на защиту нельзя.
Что рядом
Заголовок раздела «Что рядом»- Аккаунт и вход — то же словами продукта.
- Запись: контракт и переходы — где эти роли применяются.