Перейти к основному содержимому

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 areaadmin (ADR-0053)
Feature groupadmin-users
Статусdraft
Документdocs/06-services/identity/api/admin/block-user.md

Актор и доступ

ПроверкаЗначение
Actor typeExternalSystem (система администрирования; m2m client-credentials)
Auth policy / Permissionspolicy AdminSystemOnly: mTLS/OAuth2 client-credentials клиента pin-admin-system; каталожное соответствие — permission identity.users.block + identity.users.view-all (permission.md; для системы администрирования каталог справочный, ADR-0053)
Scope (RBAC)платформа (кросс-tenant: блокируется пользователь любой компании и инвестор)
Tenant isolationbypass tenant-фильтров под system-scope (операция платформенная); CompanyId пользователя не влияет на доступ
Auditобязателен: deep audit admin-API (Audit.NET, SDK §12) — actor (X-Admin-User-Id), userId, reason, comment, результат

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/admin/v1/users/{userId}/block
Success status200
Idempotency headerIdempotency-Key (Guid; обязателен — система администрирования ретраит по таймауту)
CorrelationOpenTelemetry trace (traceparent); X-Admin-User-Id (Guid админа в системе администрирования) — обязательный заголовок для аудита

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdrouteGuidДа!= Guid.Empty; существование — шаг 2 алгоритма
ReasonbodyUserBlockReason (enum-string)Да∈ {AdminDecision, CareIntervention} (значения TalentBlocked/CompanyRequest этому актору недоступны — валидатор отклоняет)попадает в событие
Commentbodystring?Нет≤ 1024 символовтолько аудит; в событие/ответ не попадает
AdminUserIdheader X-Admin-User-IdGuidДа!= Guid.Emptyaudit-stamping (UpdatedByUserId — служебный actor системы администрирования; сам Guid админа — в audit-записи)
IdempotencyKeyheader Idempotency-KeyGuidДа!= Guid.Empty

Модель ответа AdminUserStatusResult

ПолеТипОбязательностьИсточникMaskingОписание
UserIdGuidДаUserEntity.IdПользователь.
Statusstring (enum UserStatus)ДаUserEntity.StatusНовый статус (Blocked).
RevokedSessionsCountintДарезультат батч-отзываСколько Active-сессий отозвано.
VersionlongДаUserEntity.VersionДоменная версия после мутации.

Алгоритм

  1. Контекст и доступ. Операцию выполняет система администрирования (m2m-клиент pin-admin-system, policy AdminSystemOnly); инициатор-человек — из заголовка X-Admin-User-Id. Повтор с тем же Idempotency-Key (admin:user-block:{IdempotencyKey}) возвращает ранее сохранённый результат (200), шаги не повторяются.
  2. Проверка входа и предусловий. Reason ∈ {AdminDecision, CareIntervention}, Comment ≤ 1024, заголовки заполнены — иначе ValidationError (400). Пользователь UserId существует — иначе IDENTITY_USER_NOT_FOUND (404). Статус допускает блокировку: Active или Invited. Если статус уже Blocked — идемпотентный успех без изменений (RevokedSessionsCount = 0); если ArchivedIDENTITY_USER_INVALID_STATUS_TRANSITION (409). Операция платформенная, кросс-tenant: CompanyId пользователя на доступ не влияет.
  3. Бизнес-правила и переходы. Учётка переводится Active|Invited → Blocked (stateDiagram user.md); фиксируется вид изменения «блокировка» и причина Reason; актором изменения записывается служебный actor admin-API, а Guid администратора и Comment — в audit-запись.
  4. Что меняется. Статус учётки становится Blocked. Все её активные сессии отзываются (INV-S7): RefreshSession со статусом Active переходят в Revoked c RevokedReason = AdminBlock — их число возвращается как RevokedSessionsCount. Правка не должна затирать конкурентное изменение (гонка с синком Talent или второй операцией) — при конфликте версий операция повторяется системой администрирования с тем же Idempotency-Key.
  5. События. Публикуется UserBlockedKafkaEvent (топик pin.identity.users.v1, key = UserId, ContractVersion = 1, снапшот {UserId, CompanyId, BlockedAt, Reason, Version}контракт). На каждый отзыв сессии фиксируется доменный SessionRevokedEvent (лента расследований заботы). Блокировка учётки, отзыв сессий, отметка идемпотентности, audit и публикация событий выполняются в одном согласованном изменении.
  6. Результат. AdminUserStatusResult: UserId, новый Status = Blocked, RevokedSessionsCount, Version.

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsUserBlockedKafkaEventpin.identity.users.v1, key = UserId
External callsnone
Cache / projections / searchкэш интроспекции gateway инвалидируется событием (consumer gateway)
Notificationsnone напрямую (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, Then IDENTITY_USER_INVALID_STATUS_TRANSITION (409), состояние не меняется.
  • Given UserId не существует, When block, Then IDENTITY_USER_NOT_FOUND (404).
  • Given тот же Idempotency-Key, что и у завершённого вызова, When повтор по таймауту, Then возвращается сохранённый результат (replay), сессии повторно не трогаются.
  • Given Reason вне {AdminDecision, CareIntervention} (например TalentBlocked), When block, Then ValidationError (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
ValidationError400Невалидный Reason/Comment/заголовкиОшибка валидации запросаno retry
FORBIDDEN403Клиент не pin-admin-system / нет policyДоступ запрещёнno retry
IDENTITY_USER_NOT_FOUND404Пользователь не существуетПользователь не найденno retry
IDENTITY_USER_INVALID_STATUS_TRANSITION409Status = ArchivedПользователь в архиве; блокировка неприменимаno retry
CONCURRENCY_CONFLICT409Гонка версий 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 «массовый отзыв сессий»).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы