POST /api/admin/v1/users/{userId}/block — Блокировка пользователя (admin)
Назначение
Административная блокировка учётки UserEntity (Active|Invited → Blocked): пользователь теряет
возможность входа любым способом, все его активные refresh-сессии отзываются (INV-S7), downstream
уведомляется событием. Операция admin-области по ADR-0053: администраторы живут в отдельной системе
администрирования и обращаются к identity через m2m-API. Обратная операция — POST /api/admin/v1/users/{userId}/unblock — симметрична (переход Blocked → Active, публикуется
user-upserted со Status = Active; отдельный документ не заводится,
дельта описана здесь).
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | admin (ADR-0053) |
| Feature group | admin-users |
| Статус | draft |
| Документ | docs/06-services/identity/api/admin/block-user.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | ExternalSystem (система администрирования; m2m client-credentials) |
| Auth policy / Permissions | policy AdminSystemOnly: mTLS/OAuth2 client-credentials клиента pin-admin-system; каталожное соответствие — permission identity.users.block + identity.users.view-all (permission.md; для системы администрирования каталог справочный, ADR-0053) |
| Scope (RBAC) | платформа (кросс-tenant: блокируется пользователь любой компании и инвестор) |
| Tenant isolation | bypass tenant-фильтров под system-scope (операция платформенная); CompanyId пользователя не влияет на доступ |
| Audit | обязателен: deep audit admin-API (Audit.NET, SDK §12) — actor (X-Admin-User-Id), userId, reason, comment, результат |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/admin/v1/users/{userId}/block |
| Success status | 200 |
| Idempotency header | Idempotency-Key (Guid; обязателен — система администрирования ретраит по таймауту) |
| Correlation | OpenTelemetry trace (traceparent); X-Admin-User-Id (Guid админа в системе администрирования) — обязательный заголовок для аудита |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | route | Guid | Да | != Guid.Empty; существование — шаг 2 алгоритма | — |
Reason | body | UserBlockReason (enum-string) | Да | ∈ {AdminDecision, CareIntervention} (значения TalentBlocked/CompanyRequest этому актору недоступны — валидатор отклоняет) | попадает в событие |
Comment | body | string? | Нет | ≤ 1024 символов | только аудит; в событие/ответ не попадает |
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | audit-stamping (UpdatedByUserId — служебный actor системы администрирования; сам Guid админа — в audit-записи) |
IdempotencyKey | header Idempotency-Key | Guid | Да | != Guid.Empty | — |
Модель ответа AdminUserStatusResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
UserId | Guid | Да | UserEntity.Id | — | Пользователь. |
Status | string (enum UserStatus) | Да | UserEntity.Status | — | Новый статус (Blocked). |
RevokedSessionsCount | int | Да | результат батч-отзыва | — | Сколько Active-сессий отозвано. |
Version | long | Да | UserEntity.Version | — | Доменная версия после мутации. |
Алгоритм
- Контекст и доступ. Операцию выполняет система администрирования (m2m-клиент
pin-admin-system, policyAdminSystemOnly); инициатор-человек — из заголовкаX-Admin-User-Id. Повтор с тем жеIdempotency-Key(admin:user-block:{IdempotencyKey}) возвращает ранее сохранённый результат (200), шаги не повторяются. - Проверка входа и предусловий.
Reason ∈ {AdminDecision, CareIntervention},Comment ≤ 1024, заголовки заполнены — иначеValidationError(400). ПользовательUserIdсуществует — иначеIDENTITY_USER_NOT_FOUND(404). Статус допускает блокировку:ActiveилиInvited. Если статус ужеBlocked— идемпотентный успех без изменений (RevokedSessionsCount = 0); еслиArchived—IDENTITY_USER_INVALID_STATUS_TRANSITION(409). Операция платформенная, кросс-tenant:CompanyIdпользователя на доступ не влияет. - Бизнес-правила и переходы. Учётка переводится
Active|Invited → Blocked(stateDiagram user.md); фиксируется вид изменения «блокировка» и причинаReason; актором изменения записывается служебный actor admin-API, а Guid администратора иComment— в audit-запись. - Что меняется. Статус учётки становится
Blocked. Все её активные сессии отзываются (INV-S7):RefreshSessionсо статусомActiveпереходят вRevokedcRevokedReason = AdminBlock— их число возвращается какRevokedSessionsCount. Правка не должна затирать конкурентное изменение (гонка с синком Talent или второй операцией) — при конфликте версий операция повторяется системой администрирования с тем жеIdempotency-Key. - События. Публикуется
UserBlockedKafkaEvent(топикpin.identity.users.v1, key =UserId,ContractVersion = 1, снапшот{UserId, CompanyId, BlockedAt, Reason, Version}— контракт). На каждый отзыв сессии фиксируется доменныйSessionRevokedEvent(лента расследований заботы). Блокировка учётки, отзыв сессий, отметка идемпотентности, audit и публикация событий выполняются в одном согласованном изменении. - Результат.
AdminUserStatusResult:UserId, новыйStatus = Blocked,RevokedSessionsCount,Version.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | UserBlockedKafkaEvent → pin.identity.users.v1, key = UserId |
| External calls | none |
| Cache / projections / search | кэш интроспекции gateway инвалидируется событием (consumer gateway) |
| Notifications | none напрямую (notifications реагирует на событие — остановка рассылок) |
Статус Blocked виден в самом identity сразу после коммита; downstream
(crm/messenger/notifications/user-activity) узнаёт из UserBlockedKafkaEvent асинхронно (лаг — секунды),
и до применения события может краткий промежуток показывать пользователя активным. Вход любым способом
(СМС/пароль/пин/2FA) запрещён немедленно — проверка Status. Уже выданные access-JWT дорабатывают свой
TTL (≤ 15 мин), но на привилегированных операциях gateway отклоняет раньше — по кэшу отозванных sid
(вытеснение ≤ 60 сек, интроспекция identity через internal-API). notifications по событию останавливает
рассылки; кэш эффективных прав пользователя инвалидируется потребителем-gateway по событию.
Acceptance Criteria
- Given учётка
Activeс 3 активными refresh-сессиями, When система администрирования вызывает block сReason = AdminDecisionи валиднымиX-Admin-User-Id/Idempotency-Key, Then статус становитсяBlocked, все 3 сессии переходятActive → Revoked(RevokedReason = AdminBlock), ответ200сRevokedSessionsCount = 3, публикуетсяUserBlockedKafkaEvent(key =UserId), вызов попадает в deep audit. - Given учётка уже
Blocked, When повтор block (в т.ч. с новымIdempotency-Key), Then идемпотентный200без изменений,RevokedSessionsCount = 0, повторное событие не публикуется. - Given учётка
Archived, When block, ThenIDENTITY_USER_INVALID_STATUS_TRANSITION(409), состояние не меняется. - Given
UserIdне существует, When block, ThenIDENTITY_USER_NOT_FOUND(404). - Given тот же
Idempotency-Key, что и у завершённого вызова, When повтор по таймауту, Then возвращается сохранённый результат (replay), сессии повторно не трогаются. - Given
Reasonвне {AdminDecision,CareIntervention} (напримерTalentBlocked), When block, ThenValidationError(400) — недопустимые для этого актора значения отклоняются. - Given параллельно консюмер синка Talent меняет ту же учётку, When block конфликтует по версии,
Then
CONCURRENCY_CONFLICT(409); система администрирования повторяет с тем жеIdempotency-Key. - Given учётка заблокирована, у пользователя есть живой access-JWT, When он обращается к
привилегированной операции, Then gateway отклоняет по кэшу отозванных
sid/Statusв окне ≤ 60 сек.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Невалидный Reason/Comment/заголовки | Ошибка валидации запроса | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system / нет policy | Доступ запрещён | no retry |
IDENTITY_USER_NOT_FOUND | 404 | Пользователь не существует | Пользователь не найден | no retry |
IDENTITY_USER_INVALID_STATUS_TRANSITION | 409 | Status = Archived | Пользователь в архиве; блокировка неприменима | no retry |
CONCURRENCY_CONFLICT | 409 | Гонка версий c синком/другой операцией | Повторите операцию | retry с тем же Idempotency-Key |
Совместимость и наблюдаемость
- Новые optional поля запроса/ответа — без breaking change; удаление/переименование — breaking (migration + rollout по правилам api).
- Logs:
admin-user-blocked(userId, reason, adminUserId; без PII-полей учётки); metrics:identity_admin_operations_total{operation="block-user"}, duration; traces: приём запроса → проверка доступа → доступ к хранилищу; correlation через сквозной trace (traceparent). Каждый вызов — в deep audit (dashboard identity-overview; runbook «массовый отзыв сессий»).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): DELETE /api/admin/v1/users/{userId} — Удаление пользователя: архив vs полное удаление (admin), Identity Admin API — реестр методов и общие правила области, POST /api/admin/v1/users/{userId}/security/reset — Ручной сброс факторов входа (admin), POST /api/admin/v1/users/{userId}/sessions/revoke — Отзыв сессий пользователя (admin)
- События (1): Event Contract: pin.identity.user-blocked (UserBlockedKafkaEvent)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: user.md, user-session.md (INV-S7).
- События: user-blocked, user-upserted (unblock).
- Соседние admin-операции: delete-user, reset-user-security, revoke-user-sessions. ADR-0053.