iac/apps/iam/ENDPOINTS.md

208 lines
14 KiB
Markdown
Raw Permalink 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.

# Эндпоинты сервиса iams-v2
Документ описывает все HTTP-эндпоинты, которые **предоставляет** сервис `iams` (IAM: пользователи, ресурсы/проекты, права доступа). В отличие от фронтенд-модулей, здесь описан контракт самого сервиса.
## Как устроено взаимодействие
Сервер — Fiber (`internal/controller/http/server.go`). Маршруты регистрируются двумя наборами:
- **Внутренний контур** (`RegisterAPIRoutes`) — без аутентификации на уровне приложения, доступ ограничивается сетевым слоем (не публикуется через ingress). Базовые группы: `/api/v0`, `/api/admin/v0`, `/api/v1`, `/api/v2`.
- **Внешний контур** (`RegisterExternalAPIRoutes`) — регистрируется только при `AUTH_ENABLED=true`. Перед маршрутами выполняется разбор JWT (`AuthMiddleware`) и пометка контекста (`ExternalAPIMiddleware`, для админ-группы дополнительно `ExternalUserAdminAPIMiddleware`). Базовые группы: `/external/api/v0`, `/external/api/admin/v0`, `/external/api/v1`, `/external/api/v2`. Внешний контур публикует **подмножество** внутренних маршрутов, часть — только на чтение.
Глобальные middleware: `requestid`, `recover`, OTel (при `OTEL_ENABLED` и заданном `OTEL_SERVICE_NAME`), логирование.
### Аутентификация (внешний контур)
Токен передаётся заголовком `Authorization: Bearer <jwt>` (`internal/controller/http/jwt/parser.go`). **Подпись токена не проверяется** — доверие делегируется вышестоящему gateway/virtual service. Схема разбора выбирается по заголовку `Identity`:
- заголовок `Identity` отсутствует/пуст → legacy-формат (Django SimpleJWT);
- заголовок `Identity` задан → Zitadel (клейм `urn:zitadel:iam:user:metadata`, значения полей — base64).
### Формат ответов и ошибок
Успешные ответы — JSON (списки: `{ count, results }`; часть эндпоинтов возвращает объект напрямую). Ошибки — JSON `{ "error": "...", "id": "..." }` (`httpx.Response`). Доменный `Kind` маппится на HTTP-статус (`httpx/error_resolve.go`): `InvalidArgument/OutOfRange`→`400`, `Unauthenticated`→`401`, `PermissionDenied`→`403`, `NotFound`→`404`, `Aborted/AlreadyExists`→`409`, `PreconditionFailed`→`412`, `ResourceExhausted`→`429`, `Unavailable`→`503`, `Internal/Unknown/DataLoss`→`500`. Идентификатор запроса — заголовок из `requestid`.
### Пагинация и фильтры
Списки — через query `limit`/`offset` (по умолчанию `limit=1000`). Дополнительные фильтры задаются query-параметрами (напр. для `/resource`: `parent_id`, `type`, `tenant_id`, `name`, `code`, `public_id`, `target_id`, `service_accounts`; для `/users`: `username`, `search`, `is_active`, `is_staff`, `is_superuser`, `service_account_id`, `id`, `company_id`, `departments`, `positions`, `groups`).
## Служебные эндпоинты
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/health` | Health-check: проверка БД и (при включении) Kafka. `200` — ok, `503` — недоступность зависимости |
## Внутренний контур
### Users (`/api/v0/users`, `/api/admin/v0`, `/api/v1`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/users` | Список пользователей (фильтры, пагинация) |
| POST | `/api/v0/users` | Создать пользователя (если включён use case) |
| PATCH | `/api/v0/users` | Массовое обновление пользователей (legacy bulk_update) |
| GET | `/api/v0/users/:id` | Пользователь по id |
| PUT | `/api/v0/users/:id` | Полное обновление пользователя |
| PATCH | `/api/v0/users/:id` | Частичное обновление пользователя |
| DELETE | `/api/v0/users/:id` | Удалить пользователя |
| POST | `/api/admin/v0/users/activation` | Активация/деактивация пользователей компании |
| POST | `/api/v1/users-with-resources` | Пользователи с их ресурсами (тело: `tenant_id`, `id[]`) |
| GET | `/api/v1/users-grouped-by-resource/` | Пользователи, сгруппированные по ресурсу (если включён use case) |
### Permission check (`/api/v0`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/api/v0/permissions-check` | Проверка набора прав пользователя (`user_id`, `checks[]`) |
### Groups (`/api/v0/groups`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/groups` | Список групп |
| POST | `/api/v0/groups` | Создать группу |
| POST | `/api/v0/groups/search` | Поиск групп (пагинация, сортировка, фильтры) |
| GET | `/api/v0/groups/:id` | Группа по id |
| PUT | `/api/v0/groups/:id` | Обновить группу |
| PATCH | `/api/v0/groups/:id` | Частично обновить группу |
| DELETE | `/api/v0/groups/:id` | Удалить группу |
### Positions (`/api/v0/positions`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/positions` | Список должностей |
| POST | `/api/v0/positions` | Создать должность |
| GET | `/api/v0/positions/:id` | Должность по id |
| PUT | `/api/v0/positions/:id` | Обновить должность |
| PATCH | `/api/v0/positions/:id` | Частично обновить должность |
| DELETE | `/api/v0/positions/:id` | Удалить должность |
### Departments (`/api/v0/departments`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/departments` | Список подразделений |
| POST | `/api/v0/departments` | Создать подразделение |
| GET | `/api/v0/departments/:id` | Подразделение по id |
| PUT | `/api/v0/departments/:id` | Обновить подразделение |
| PATCH | `/api/v0/departments/:id` | Частично обновить подразделение |
| DELETE | `/api/v0/departments/:id` | Удалить подразделение |
### Django permissions (`/api/v0/permissions`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/permissions` | Список permission'ов |
| POST | `/api/v0/permissions` | Создать permission |
| POST | `/api/v0/permissions/search` | Поиск permission'ов |
| GET | `/api/v0/permissions/:id` | Permission по id |
| DELETE | `/api/v0/permissions/:id` | Удалить permission |
### Resource types v0 (`/api/v0/resource-types`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/resource-types` | Список типов ресурсов |
| POST | `/api/v0/resource-types` | Создать тип |
| GET | `/api/v0/resource-types/:id` | Тип по id |
| PUT | `/api/v0/resource-types/:id` | Обновить тип |
| PATCH | `/api/v0/resource-types/:id` | Частично обновить тип |
| DELETE | `/api/v0/resource-types/:id` | Удалить тип |
### Resources v0 (`/api/v0/resources`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/resources` | Список ресурсов (legacy v0) |
| POST | `/api/v0/resources` | Создать ресурс |
| GET | `/api/v0/resources/:id` | Ресурс по id |
| PUT | `/api/v0/resources/:id` | Обновить ресурс |
| PATCH | `/api/v0/resources/:id` | Частично обновить ресурс |
| DELETE | `/api/v0/resources/:id` | Удалить ресурс |
### Projects v0 (`/api/v0/projects`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/projects` | Список проектов |
| POST | `/api/v0/projects` | Создать проект |
| GET | `/api/v0/projects/:publicID` | Проект по public id |
| PUT | `/api/v0/projects/:publicID` | Обновить проект |
| PATCH | `/api/v0/projects/:publicID` | Частично обновить проект |
| DELETE | `/api/v0/projects/:publicID` | Удалить проект |
### Widgets v0 (`/api/v0/widgets`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/widgets` | Список виджетов |
| POST | `/api/v0/widgets` | Создать виджет |
| GET | `/api/v0/widgets/:publicID` | Виджет по public id |
| PUT | `/api/v0/widgets/:publicID` | Обновить виджет |
| PATCH | `/api/v0/widgets/:publicID` | Частично обновить виджет |
| DELETE | `/api/v0/widgets/:publicID` | Удалить виджет |
### Resources v1 (`/api/v1/resource`, `/api/v1/targets`, `/api/v1/service-accounts`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v1/resource` | Список ресурсов (v1) |
| POST | `/api/v1/resource` | Создать ресурс |
| GET | `/api/v1/resource/:publicID` | Ресурс по public id |
| PUT | `/api/v1/resource/:publicID` | Обновить ресурс |
| PATCH | `/api/v1/resource/:publicID` | Частично обновить ресурс |
| DELETE | `/api/v1/resource/:publicID` | Удалить ресурс |
| GET | `/api/v1/targets/:target_id/resource` | Ресурс по target id (только внутренний) |
| GET | `/api/v1/resources-grouped-by-sa` | Ресурсы, сгруппированные по сервисному аккаунту (если включено) |
| GET | `/api/v1/service-accounts` | Список сервисных аккаунтов |
### Widgets v2 (`/api/v2/resource`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v2/resource` | Список виджетов (v2) |
| POST | `/api/v2/resource` | Создать виджет |
| GET | `/api/v2/resource/:publicID` | Виджет по public id |
| PUT | `/api/v2/resource/:publicID` | Обновить виджет |
| PATCH | `/api/v2/resource/:publicID` | Частично обновить виджет |
| DELETE | `/api/v2/resource/:publicID` | Удалить виджет |
### Permissions v1 (`/api/v1`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v1/resource_permission` | Список прав на ресурсы |
| POST | `/api/v1/resource_permission` | Создать права (пакет `items[]`) |
| GET | `/api/v1/resource_permission/:id` | Право по id |
| DELETE | `/api/v1/resource_permission/:id` | Удалить право |
| PATCH | `/api/v1/permissions-bulk` | Массовое изменение прав (`tenant_id`, `permissions`) |
| GET | `/api/v1/company_resource_permission` | Список прав компании на ресурсы |
| POST | `/api/v1/company_resource_permission` | Создать права компании (пакет `items[]`) |
| GET | `/api/v1/company_resource_permission/:id` | Право компании по id |
| DELETE | `/api/v1/company_resource_permission/:id` | Удалить право компании |
## Внешний контур (`/external/api/*`, только при `AUTH_ENABLED=true`)
Публикуется подмножество внутренних маршрутов; часть — только на чтение. Пути идентичны внутренним, но с префиксом `/external/api`.
| Группа | Маршруты | Отличия от внутреннего контура |
| --- | --- | --- |
| `/external/api/v0/users` | `GET /`, `GET /:id`, `PATCH /` | Только чтение + массовый PATCH (bulk) |
| `/external/api/admin/v0/users` | `POST /activation`, полный CRUD `/users` | Полный доступ (админ-группа) |
| `/external/api/admin/v0/positions` | `/positions` (CRUD) | Как внутренний |
| `/external/api/admin/v0/departments` | `/departments` (CRUD) | Как внутренний |
| `/external/api/admin/v0/groups` | `/groups` (CRUD + search) | Как внутренний |
| `/external/api/admin/v0/permissions` | `/permissions` (django) | Как внутренний |
| `/external/api/v0/resource-types` | `/resource-types` (CRUD) | Как внутренний |
| `/external/api/v0/resources` | `/resources` (CRUD, v0) | Как внутренний |
| `/external/api/v0/projects` | `GET /`, `GET /:publicID` | Только чтение |
| `/external/api/v0/widgets` | `/widgets` (CRUD) | Как внутренний |
| `/external/api/v1/resource` | `GET /`, `GET /:publicID` | Только чтение |
| `/external/api/v2/resource` | `/resource` (виджеты v2, CRUD) | Как внутренний |
| `/external/api/v1/resource_permission` | CRUD-подмножество | Как внутренний |
| `/external/api/v1/permissions-bulk` | `PATCH /` | Как внутренний |
| `/external/api/v1/company_resource_permission` | CRUD-подмножество | Как внутренний |
> Часть маршрутов регистрируется условно — только если соответствующий хендлер/use case подключён при инициализации сервера (проверки `Has*` в хендлерах). При отсутствии сервиса группа не регистрируется.