Auth Service — API Contract
Этот документ является единственным источником правды для API Auth Service.
Бэкенд реализует контракт. Фронтенд потребляет контракт. Отклонения запрещены.
Содержание
Public API
- POST /auth/login
- POST /auth/pin-login (POS)
- POST /auth/refresh
- POST /auth/logout
- POST /auth/forgot-password
- POST /auth/reset-password
- GET /auth/me (BR 1.4.3)
Internal API (Service Token)
POST /auth/login
Авторизация сотрудника по email и паролю.
| Параметр | Значение |
|---|---|
| Auth | Public |
| Content-Type | application/json |
Request Body
{
"email": "string, required — email сотрудника",
"password": "string, required — пароль"
}Response 200
{
"data": {
"access_token": "string — JWT access token",
"refresh_token": "string — JWT refresh token",
"expires_in": "number — TTL access token в секундах",
"user": {
"id": "uuid",
"email": "string",
"first_name": "string",
"last_name": "string",
"franchise": {
"id": "uuid",
"type": "corporate | individual"
},
"scope": {
"type": "all_franchise | legal_entity_ids | store_ids",
"legal_entity_ids": ["uuid"],
"store_ids": ["uuid"]
},
"role_ids": ["uuid"],
"permissions": ["string"]
}
}
}(Расширено в BR 1.4.3: поля role_ids и permissions — permissions-слой для фронтенда.)
(Обновлено в BR 1.4.4: удалены поля role, store_ids, legal_entity_id. Добавлены franchise: { id, type } и scope: { type, legal_entity_ids?, store_ids? }. Scope вычисляется по правилам Ролевая модель: владельцы получают all_franchise или legal_entity_ids; обычные сотрудники — store_ids из агрегата permissions-ролей. Поля legal_entity_ids / store_ids присутствуют только при соответствующем type.)
Errors
| Code | HTTP | Когда |
|---|---|---|
INVALID_CREDENTIALS | 401 | Неверный email или пароль |
ACCOUNT_DISABLED | 403 | Сотрудник деактивирован |
ACCOUNT_LOCKED | 429 | Превышен лимит попыток (5), аккаунт заблокирован на 15 мин |
VALIDATION_ERROR | 400 | Невалидный формат email |
NO_BACKOFFICE_ACCESS | 403 | В permissions нет ни одного backoffice *.read — только POS-операции. Сотрудник может работать только на POS (BR 1.5) |
POST /auth/pin-login
Авторизация кассира на POS-терминале по PIN-коду. PIN уникален в рамках ТТ.
| Параметр | Значение |
|---|---|
| Auth | Public |
| Content-Type | application/json |
Request Body
{
"pin": "string, required — 4-значный PIN",
"store_id": "uuid, required — ID торговой точки",
"employee_id": "uuid, optional — ID сотрудника. Требуется при коллизии PIN (несколько сотрудников с одним PIN в одной ТТ) для явного выбора"
}Response 200
Та же структура что и у /auth/login (access_token + refresh_token + user).
BR 5.1 v2:
kitchen_station_idдля KDSПри вызове из KDS-клиента (с
X-Device-Id) pos-bff дополнительно проксирует полеkitchen_station_id(uuid | null) в response — это цех, к которому привязано устройство вkds_devices. Клиент использует, чтобы пропустить экран выбора станций после PIN. Auth Service сам это поле не возвращает; добавляется pos-bff’ом по результатам внутреннего вызоваGET /internal/kds-devices/validateв User Service.
Errors
| Code | HTTP | Когда |
|---|---|---|
INVALID_PIN | 401 | Неверный PIN, нет сотрудника с таким PIN в ТТ, или PIN не назначен сотруднику |
PIN_COLLISION | 409 | В одной ТТ несколько сотрудников с совпадающим PIN. Клиент должен повторить запрос, передав employee_id |
ACCOUNT_DISABLED | 403 | Сотрудник деактивирован |
ACCOUNT_LOCKED | 429 | Превышен лимит попыток |
POS_ACCESS_DENIED | 403 | В permissions сотрудника нет pos.access (BR 1.4.4) |
DEVICE_NOT_REGISTERED | 403 | (добавляется pos-bff’ом до вызова этого endpoint’а) KDS-клиент прислал X-Device-Id, но в kds_devices нет соответствия (device_id, store_id) или запись revoked. Клиент чистит localStorage и заново проходит wizard регистрации |
VALIDATION_ERROR | 400 | PIN не 4 цифры / store_id невалиден |
Логика
POS_ACCESS_DENIEDПроверяется что в permissions сотрудника (по набору назначенных ему permissions-ролей) присутствует ключ
pos.access. Без него вход на POS невозможен даже при валидном PIN.
Логика
DEVICE_NOT_REGISTERED(BR 5.1 — KDS device validation)Проверка выполняется в pos-bff до вызова Auth Service: если в headers пришёл
X-Device-Id, pos-bff дёргает User ServiceGET /internal/kds-devices/validate?device_id=X&store_id=Y. Приvalid=falseсразу 403, Auth Service не вызывается. БезX-Device-Id(обычный POS-кассир) проверка пропускается — поведение не меняется.
POST /auth/refresh
Обновление access token по refresh token.
| Параметр | Значение |
|---|---|
| Auth | Refresh token в body |
| Content-Type | application/json |
Request Body
{
"refresh_token": "string, required"
}Response 200
{
"data": {
"access_token": "string — новый JWT access token",
"refresh_token": "string — новый refresh token (rotation)",
"expires_in": "number"
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
INVALID_REFRESH_TOKEN | 401 | Токен невалиден или истёк |
TOKEN_REVOKED | 401 | Токен был отозван (logout) |
POST /auth/logout
Выход — инвалидация текущего refresh token.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT |
| Content-Type | application/json |
Request Body
{
"refresh_token": "string, required"
}Response 204
Нет тела ответа.
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Невалидный JWT |
POST /auth/forgot-password
Запрос сброса пароля. Отправляет email со ссылкой через Resend.
| Параметр | Значение |
|---|---|
| Auth | Public |
| Content-Type | application/json |
Request Body
{
"email": "string, required"
}Response 200
{
"data": {
"message": "Если аккаунт существует, на email отправлена ссылка для сброса пароля"
}
}Всегда 200 — не раскрываем существует ли email в системе.
Errors
| Code | HTTP | Когда |
|---|---|---|
VALIDATION_ERROR | 400 | Невалидный формат email |
POST /auth/reset-password
Установка нового пароля по токену из email-ссылки.
| Параметр | Значение |
|---|---|
| Auth | Public |
| Content-Type | application/json |
Request Body
{
"token": "string, required — токен из ссылки",
"password": "string, required — новый пароль",
"password_confirmation": "string, required — подтверждение"
}Response 200
{
"data": {
"message": "Пароль успешно изменён"
}
}Errors
| Code | HTTP | Когда |
|---|---|---|
VALIDATION_ERROR | 400 | Пароли не совпадают или слишком короткий |
INVALID_RESET_TOKEN | 422 | Токен невалиден или истёк (TTL = 1 час) |
TOKEN_ALREADY_USED | 422 | Токен уже использован |
POST /internal/auth/validate
Internal endpoint — только для service-to-service вызовов
Валидация JWT-токена. Используется API Gateway и BFF для проверки запросов.
| Параметр | Значение |
|---|---|
| Auth | Service token (X-Service-Token header) |
| Content-Type | application/json |
Request Body
{
"token": "string, required — JWT access token"
}Response 200
{
"data": {
"valid": true,
"user_id": "uuid",
"franchise_id": "uuid",
"role_ids": ["uuid"],
"permissions": ["string — agg granted=true из всех ролей"],
"scope": {
"type": "all_franchise | legal_entity_ids | store_ids",
"legal_entity_ids": ["uuid"],
"store_ids": ["uuid"]
}
}
}(Расширено в BR 1.4.3: поля role_ids и permissions. Значения берутся из Redis-кэша user_permissions:{user_id} с TTL 60 сек; при промахе — fetch через GET /internal/users/{user_id}/permissions в User Service.)
(Обновлено в BR 1.4.4: удалены role, store_ids, legal_entity_id. Добавлено поле scope — берётся из Redis-кэша user_scope:{user_id} (TTL 60 сек), при промахе — fetch через GET /internal/users/{user_id}/scope в User Service. Все downstream-сервисы используют scope и permissions для авторизации; enum role больше не передаётся.)
Response 200 (невалидный токен)
{
"data": {
"valid": false,
"reason": "expired | invalid_signature | revoked"
}
}GET /auth/me
(Введено в BR 1.4.3)
Полный профиль авторизованного сотрудника с актуальным списком ролей и permissions. Фронт вызывает при старте сессии и/или после логина для получения permissions-каталога.
| Параметр | Значение |
|---|---|
| Auth | Bearer JWT |
Response 200
{
"data": {
"user": {
"id": "uuid",
"email": "string",
"first_name": "string",
"last_name": "string",
"franchise": {
"id": "uuid",
"type": "corporate | individual"
},
"scope": {
"type": "all_franchise | legal_entity_ids | store_ids",
"legal_entity_ids": ["uuid"],
"store_ids": ["uuid"]
}
},
"roles": [
{
"id": "uuid",
"name": "string",
"is_system": "boolean",
"store_ids": ["uuid — где эта роль действует у данного сотрудника"]
}
],
"permissions": ["string — агрегат granted=true permission-ключей"]
}
}(Обновлено в BR 1.4.4: удалены role, store_ids, legal_entity_id. Добавлены franchise и scope — аналогично /auth/login. franchise.type управляет видимостью раздела «Юр. лица» во фронте.)
Errors
| Code | HTTP | Когда |
|---|---|---|
UNAUTHORIZED | 401 | Нет токена или невалидный |
JWT Payload
Формат JWT access token, единый для всех сервисов:
{
"sub": "uuid — user_id",
"franchise_id": "uuid",
"role_ids": ["uuid"],
"iat": 1234567890,
"exp": 1234567890
}(Обновлено в BR 1.4.3: добавлено поле role_ids — список permissions-ролей. Permissions в JWT НЕ кладутся — только role_ids. Полный список прав — через /auth/me или /internal/auth/validate.)
(Упрощено в BR 1.4.4: удалены поля role (enum), store_ids, legal_entity_id. Scope считается на бэке Auth Service по правилам Ролевая модель с Redis-кэшем user_scope:{user_id} и отдаётся через /auth/me и /internal/auth/validate.)
Общий формат ошибок
{
"error": {
"code": "string — машиночитаемый код (UPPER_SNAKE_CASE)",
"message": "string — человекочитаемое описание",
"details": [
{
"field": "string — путь к полю",
"message": "string — описание ошибки"
}
]
}
}