Срез 0 реализован и влит (PR #3), change перенесён в openspec/changes/archive, спеки — в openspec/specs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
67 lines
5.2 KiB
Markdown
67 lines
5.2 KiB
Markdown
# authn-authz Specification
|
||
|
||
## Purpose
|
||
Вход в ядро по JWT внешнего SSO (РАИСА): пользователь находится по email в `app_user`, его эффективные права — объединение
|
||
прав его ролей из БД; любой защищённый маршрут объявляет требуемое право одной зависимостью. Для разработки — эмитент
|
||
токенов за флагом.
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Личность из JWT, пользователь из БД
|
||
Защищённый маршрут SHALL требовать заголовок `Authorization: Bearer <jwt>`. Ядро SHALL проверить подпись и срок токена
|
||
настроенным проверяющим (dev HS256 при `AUTH_DEV_ISSUER=true`, иначе JWKS/публичный ключ по `AUTH_*`), извлечь email из
|
||
claim `AUTH_EMAIL_CLAIM` (по умолчанию `email`) и найти `app_user` с этим `email` и `is_active = true`. Нет или невалидный
|
||
токен → 401; пользователь не найден или неактивен → 403. Тело ошибки — `{"detail": "<причина по-русски>"}`.
|
||
|
||
#### 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("<PERMISSION_CODE>")` 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
|