API: Аутентификация
Базовый URL: /api/v1/auth
Термины: JWT — JSON Web Token; LDAP — протокол доступа к каталогу; AD — Active Directory. Полный список — в словаре терминов.
POST /auth/login
Заголовок раздела «POST /auth/login»Вход в портал.
Тело запроса:
{ "login": "admin", "password": "admin_change_me", "authType": "local"}| Поле | Тип | Описание |
|---|---|---|
| login | string | Логин |
| password | string | Пароль |
| authType | local | domain | Тип учётной записи (логин/пароль в форме) |
Ответ 200:
{ "success": true, "data": { "user": { "id": "uuid", "login": "admin", "display_name": "Администратор портала", "is_portal_admin": true }, "token": "eyJ...", "expiresIn": 900 }}JWT также устанавливается в httpOnly cookie portal_token. expiresIn — оставшееся время жизни в секундах; expiresAt — unix timestamp истечения. maxExpiresIn — полный срок из JWT_EXPIRES_IN.
Ошибки:
401— неверный логин или пароль400— ошибка валидации429— превышен лимит попыток (10/мин)
Пример curl:
curl -X POST http://localhost/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"login":"admin","password":"admin_change_me","authType":"local"}' \ -c cookies.txtPOST /auth/logout
Заголовок раздела «POST /auth/logout»Выход. Очищает cookie и отзывает JWT (jti в denylist до истечения TTL).
POST /auth/refresh
Заголовок раздела «POST /auth/refresh»Продление сессии (sliding session). Требует действующий JWT в cookie. Предыдущий jti отзывается. Смена/сброс пароля увеличивает token_version — все старые JWT пользователя перестают действовать.
Ответ 200:
{ "success": true, "data": { "token": "eyJ...", "expiresIn": 900 }}Cookie обновляется автоматически. Фронтенд вызывает endpoint проактивно (~50% оставшегося TTL), при возврате на вкладку, при фокусе окна и после каждой загрузки страницы.
Ошибки:
401— сессия истекла или пользователь заблокирован
GET /auth/me
Заголовок раздела «GET /auth/me»Текущий пользователь. Требует авторизации.
Ответ 200:
{ "success": true, "data": { "user": { "id": "...", "login": "admin", "display_name": "...", "email": "admin@portal.local", "auth_source": "local", "is_portal_admin": true, "is_active": true, "last_login_at": "...", "created_at": "...", "groups": [ { "id": "uuid", "name": "Читатели портала" } ] } }}groups — группы доступа, в которых состоит пользователь (для назначения прав через группы).
Доменная авторизация (LDAP / AD)
Заголовок раздела «Доменная авторизация (LDAP / AD)»При authType: "domain" портал проверяет логин и пароль через LDAP bind. Пароли доменных пользователей не хранятся в Postgres.
При первом успешном входе создаётся запись в users с auth_source=domain (JIT-provisioning). При повторном входе обновляются display_name и email.
Настройка подключения: Админка → Службы → Синхронизация AD → Настройки (/services/ad-sync). По умолчанию LDAP не настроен.
Опциональный fallback для Docker/CI — переменные окружения:
| Переменная | Описание |
|---|---|
LDAP_URL | URL сервера, напр. ldap://ldap:389 |
LDAP_BASE_DN | Базовый DN, напр. dc=portal,dc=local |
LDAP_BIND_DN | Service-account для поиска |
LDAP_BIND_PASSWORD | Пароль service-account |
LDAP_USER_FILTER | Фильтр входа, плейсхолдер {{username}} |
LDAP_SEARCH_FILTER | Фильтр поиска, плейсхолдер {{query}} |
LDAP_LOGIN_ATTR | Атрибут логина: sAMAccountName (AD) или uid (OpenLDAP) |
Локальный LDAP для разработки:
docker compose --profile ldap up -d --buildПосле запуска контейнера ldap откройте Админка → Службы → Синхронизация AD → Настройки и укажите параметры из docker/ldap/.env.example (URL, Base DN, Bind DN, пароль, фильтры для OpenLDAP). Переменные LDAP_* в корневом .env — опциональный fallback; для ручной настройки через UI их можно не добавлять.
Тестовый пользователь: ivanov / domain123.
Ошибки доменного входа:
400— LDAP не настроен401— неверный логин или пароль
См. также: directory.md
GET /auth/providers
Заголовок раздела «GET /auth/providers»Публичный endpoint с доступными способами входа для экрана авторизации.
Ответ 200:
{ "success": true, "data": { "local": true, "domain": true, "keycloak": false }}keycloak=true только если служба keycloak-sso включена и заполнены настройки OIDC.
Keycloak как третий провайдер
Заголовок раздела «Keycloak как третий провайдер»Для входа через Keycloak используется OIDC Authorization Code + PKCE:
- Браузер открывает
GET /auth/keycloak/login - Portal редиректит пользователя в Keycloak
- Keycloak возвращает пользователя на
GET /auth/keycloak/callback - Portal создаёт/обновляет пользователя с
auth_source=keycloak, ставит cookieportal_tokenи делает redirect на/
В этом сценарии POST /auth/login не используется.
Возможные auth_error в query
Заголовок раздела «Возможные auth_error в query»После неуспешного OIDC-callback пользователь возвращается на / с параметром auth_error:
keycloak_disabled— вход через Keycloak выключен или не настроенkeycloak_missing_code— Keycloak не вернулcodekeycloak_state_invalid— state устарел/не найденkeycloak_login_failed— ошибка обмена code/token или валидации id_token