Andrey Epifancev db7df33517 chore(openspec): архив change slice-0-skeleton
Срез 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
2026-10-04 19:44:37 +04:00

67 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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