# authn-authz Specification ## Purpose Вход в ядро по JWT внешнего SSO (РАИСА): пользователь находится по email в `app_user`, его эффективные права — объединение прав его ролей из БД; любой защищённый маршрут объявляет требуемое право одной зависимостью. Для разработки — эмитент токенов за флагом. ## Requirements ### Requirement: Личность из JWT, пользователь из БД Защищённый маршрут SHALL требовать заголовок `Authorization: Bearer `. Ядро SHALL проверить подпись и срок токена настроенным проверяющим (dev HS256 при `AUTH_DEV_ISSUER=true`, иначе JWKS/публичный ключ по `AUTH_*`), извлечь email из claim `AUTH_EMAIL_CLAIM` (по умолчанию `email`) и найти `app_user` с этим `email` и `is_active = true`. Нет или невалидный токен → 401; пользователь не найден или неактивен → 403. Тело ошибки — `{"detail": "<причина по-русски>"}`. Отказы в аутентификации (401 при предъявленном токене, 403 «не найден / отключён / неоднозначен») и вход SHALL фиксироваться в журнале по требованию «События входа» спеки `audit-trail` (`LOGIN_FAILED` / `LOGIN`). #### Scenario: Валидный токен активного пользователя - **WHEN** запрос несёт корректный токен с `email = ka_rf@armka.local`, и такой активный пользователь есть - **THEN** маршрут выполняется от имени этого `app_user` #### Scenario: Пользователя нет в системе - **WHEN** токен валиден, но `app_user` с таким email отсутствует - **THEN** ответ 403, `detail` сообщает, что пользователь не зарегистрирован в АРМ #### Scenario: Истёкший или подделанный токен - **WHEN** срок токена истёк или подпись не проходит - **THEN** ответ 401, тело `{"detail": …}` ### Requirement: Эффективные права и зависимость `require` `effective_permissions(user)` SHALL быть объединением `permission.code` по всем ролям пользователя (`user_role → role_permission → permission`); код роли в проверках MUST NOT использоваться. Зависимость `require("")` SHALL возвращать 403, если кода нет в эффективных правах, и MUST NOT обращаться к БД повторно в рамках одного запроса. #### Scenario: Право есть - **WHEN** пользователь с ролью `KA_GO` вызывает маршрут с `require("KFO_VERIFY")` - **THEN** маршрут выполняется #### Scenario: Права нет - **WHEN** пользователь с ролью `EXPERT` вызывает маршрут с `require("DOCUMENT_EDIT")` - **THEN** ответ 403, `detail` называет недостающее право #### Scenario: Две роли - **WHEN** пользователю назначены роли `KA_RF` и `CURATOR_GO` - **THEN** его эффективные права — объединение прав обеих ролей без дубликатов ### Requirement: `GET /api/v1/me` Маршрут SHALL возвращать текущего пользователя: `id`, `email`, `full_name`, `regional_office {id, name, is_head_office}`, `business_segment`, `roles` (список `code`), `permissions` (отсортированный список `code`). Это единственный маршрут Среза 0, доступный любому аутентифицированному пользователю без `require`. #### Scenario: Ответ для dev-пользователя - **WHEN** `ka_rf@armka.local` (роль `KA_RF`) запрашивает `/api/v1/me` - **THEN** ответ 200 содержит `roles = ["KA_RF"]` и `permissions`, равные правам `KA_RF` из матрицы §10 ### Requirement: Dev-эмитент токенов только в режиме разработки При `AUTH_DEV_ISSUER=true` **и** `DEBUG=true` ядро SHALL предоставлять `POST /api/v1/dev/token` с телом `{"email"}`, возвращающий HS256 JWT (срок 8 часов) для существующего активного пользователя. При любом другом сочетании флагов маршрут MUST NOT существовать (404), а dev-проверяющий MUST NOT принимать токены. #### Scenario: Получение dev-токена - **WHEN** `AUTH_DEV_ISSUER=true`, `DEBUG=true` и запрошен токен для `admin@armka.local` - **THEN** ответ 200 `{access_token, token_type: "bearer"}`, токен принимается `/api/v1/me` #### Scenario: Прод-конфигурация - **WHEN** `DEBUG=false` - **THEN** `POST /api/v1/dev/token` возвращает 404, токен, подписанный dev-секретом, отвергается с 401 ### Requirement: Прод-конфигурация HTTP-поверхности Документация API (`/docs`, `/redoc`, `/openapi.json`) SHALL существовать только при `DEBUG=true`; при `DEBUG=false` эти пути MUST отвечать 404 (не схемой и не страницей SPA) (OQ-66). CORS при `DEBUG=true` SHALL разрешать любой источник `*` без `allow_credentials`; при `DEBUG=false` — только источники из `CORS_ORIGINS` (JSON-список или строка через запятую), пустой список SHALL запрещать кросс-доменные запросы, `allow_credentials` — только для явного списка без `*` (OQ-65). Под `/api/v1` SPA MUST NOT отвечать: неверный метод на существующий маршрут SHALL давать 405 с заголовком `Allow` и `detail` по-русски, несуществующий путь (в том числе dev-маршрут при выключенных флагах) — 404 (OQ-61); `GET /` SHALL отдавать `index.html` SPA без редиректа при любом `ROOT_PATH` (OQ-62). #### Scenario: Документация в проде - **WHEN** `DEBUG=false` и запрошен `/openapi.json` - **THEN** ответ 404 #### Scenario: Кросс-доменный запрос в проде без настроенных источников - **WHEN** `DEBUG=false`, `CORS_ORIGINS` пуст, браузер шлёт preflight с `Origin: https://other.example` - **THEN** в ответе нет `Access-Control-Allow-Origin` #### Scenario: Неверный метод - **WHEN** выполнен `POST /api/v1/me` - **THEN** ответ 405, заголовок `Allow: GET` #### Scenario: Несуществующий путь API - **WHEN** выполнен `GET /api/v1/no-such-route` - **THEN** ответ 404, не `index.html` #### Scenario: Корень приложения - **WHEN** при `ROOT_PATH=/` выполнен `GET /` - **THEN** ответ 200 с `index.html`, без редиректа на `//`