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

Источник: https://docs.novapoker.ru/dev/auth/
Дорожка: разработка
Сверено с кодом: Mon Aug 24
Код: repos/poker-api/apps/server/src/auth, repos/poker-api/apps/server/src/auth/permissions.ts

---

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

## Контракт

| Метод и путь | Что делает |
|---|---|
| `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`.

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

## Что рядом

- [Аккаунт и вход](https://docs.novapoker.ru/product/account.md) — то же словами продукта.
- [Запись: контракт и переходы](https://docs.novapoker.ru/dev/registration.md) — где эти роли применяются.
