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

POST /api/admin/v1/users/{userId}/sessions/revoke — Отзыв сессий пользователя (admin)

Назначение

Административное управление сессиями (ADR-0053): принудительный отзыв одной или всех активных RefreshSession пользователя без блокировки учётки — компрометация устройства, подозрительная активность, обращение пользователя в поддержку («выйти со всех устройств за меня»). После отзыва refresh невозможен; живые access-JWT дорабатывают до expirationDate (короткий TTL), gateway дополнительно отклоняет по claim sid через кэш отозванных сессий (интроспекция identity). Source of truth сессий — здесь; витрина «мои устройства» — контекст user-activity (ADR-0054).

Metadata

ПолеЗначение
Сервис/контекстidentity
API areaadmin (ADR-0053)
Feature groupadmin-sessions
Статусdraft
Документdocs/06-services/identity/api/admin/revoke-user-sessions.md

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

ПроверкаЗначение
Actor typeExternalSystem (система администрирования; m2m client-credentials)
Auth policy / Permissionspolicy AdminSystemOnly; каталожное соответствие — платформенный уровень user-activity.sessions.* для витрины + admin-право управления сессиями (для системы администрирования каталог справочный, ADR-0053)
Scope (RBAC)платформа (кросс-tenant)
Record-levelадминистратор видит/отзывает сессии любого пользователя; каждый доступ аудируется
Tenant isolationbypass tenant-фильтров под system-scope; refresh_sessions не tenanted — изоляция record-level по UserId (ADR-0052)
Auditобязателен: deep audit — actor, userId, sessionId/all, причина

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/admin/v1/users/{userId}/sessions/revoke
Success status200
Idempotency headerIdempotency-Key (Guid; обязателен)
CorrelationOTel trace (traceparent); X-Admin-User-Id — обязателен

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdrouteGuidДа!= Guid.Empty; существование — шаг 2
SessionIdbodyGuid?Нетnull — отозвать ВСЕ Active-сессии; иначе — конкретную (RefreshSession.Id, обязана принадлежать UserId)
Reasonbodystring?Нет≤ 512; свободный текст для аудита («компрометация устройства», «запрос пользователя, тикет N»)только audit
AdminUserIdheader X-Admin-User-IdGuidДа!= Guid.Emptyaudit-stamping
IdempotencyKeyheader Idempotency-KeyGuidДа!= Guid.Empty

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

ПолеТипОбязательностьИсточникMaskingОписание
UserIdGuidДакомандаПользователь.
RevokedSessionsCountintДабатч-отзывСколько сессий переведено Active → Revoked (0 — не было активных: идемпотентный успех).
RevokedSessionIdsIReadOnlyList<Guid>Да (может быть пустым)id мутированных записейОтозванные сессии — для кэша отозванных sid на gateway (fan-out системой администрирования при необходимости).
PII-минимизация

Слепки устройств (SessionDevice) в ответ не входят — просмотр списка сессий вынесен в отдельную admin-query витрины user-activity.

Алгоритм

  1. Контекст и доступ. Операцию выполняет система администрирования (m2m-клиент pin-admin-system, policy AdminSystemOnly). Повтор с тем же Idempotency-Key (admin:sessions-revoke:{IdempotencyKey}) возвращает сохранённый результат.
  2. Проверка входа и предусловий. Заголовки заполнены, Reason ≤ 512 — иначе ValidationError (400). Пользователь UserId существует — иначе IDENTITY_USER_NOT_FOUND (404); статус пользователя операцию НЕ ограничивает (отзыв валиден для любого статуса, включая Blocked/Archived — санитарная зачистка). Если SessionId задан, сессия существует и принадлежит UserId — иначе IDENTITY_SESSION_NOT_FOUND (404; принадлежность чужому пользователю отдельным кодом не раскрывается, fail closed). Допустимость состояния сессии: Active; иначе (Rotated/Revoked/ Expired) — идемпотентный успех без изменений (RevokedSessionsCount = 0).
  3. Бизнес-правила и переходы. Целевые сессии переводятся Active → Revoked (stateDiagram RefreshSession user-session.md): проставляются RevokedAt и RevokedReason = AdminBlock (admin-инициатива, enum user-session.md). Цепочки ротаций (ParentSessionId) дополнительно не трогаются: Rotated-предки уже не активны, replay-защита INV-S6 продолжает действовать.
  4. Что меняется. При SessionId = null отзываются все активные сессии пользователя, иначе — указанная сессия; число и идентификаторы отозванных возвращаются как RevokedSessionsCount и RevokedSessionIds. Правка не должна затирать конкурентный refresh той же сессии — проигравший получает ошибку статуса (refresh вернёт 401, что и требуется). Отзыв сессий, отметка идемпотентности и audit — в одном согласованном изменении.
  5. События. На каждую отозванную сессию фиксируется доменный SessionRevokedEvent (SessionId, UserId, RevokedReason) — лента расследований заботы (user-session.md). Интеграционного события отзыва сессии в контрактах identity нет: gateway узнаёт об отзыве через кэш интроспекции отозванных sid (internal-API), инвалидация — вытеснением TTL ≤ 60 сек.
  6. Результат. AdminRevokeUserSessionsResult: UserId, RevokedSessionsCount, RevokedSessionIds (для кэша отозванных sid на gateway). Слепки устройств в ответ не входят.

Диаграмма

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

ТипДетали
Integration eventsnone (см. шаг 5; блокировка учётки с отзывом — block-user с событием)
External callsnone
Cache / projections / searchкэш интроспекции sid на gateway — вытеснение TTL ≤ 60 сек; витрина сессий user-activity обновится при следующем чтении source of truth
Notificationsnone напрямую (алерт пользователю — инициатива системы администрирования)

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Невалидные заголовки/ReasonОшибка валидации запросаno retry
FORBIDDEN403Клиент не pin-admin-systemДоступ запрещёнno retry
IDENTITY_USER_NOT_FOUND404Пользователь не существуетПользователь не найденno retry
IDENTITY_SESSION_NOT_FOUND404SessionId не существует или принадлежит другому пользователюСессия не найденаno retry
CONCURRENCY_CONFLICT409Гонка версий c параллельным refreshПовторите операциюretry с тем же Idempotency-Key

Acceptance Criteria

  • Given пользователь с 4 активными сессиями, When revoke с SessionId = null, Then все 4 переходят Active → Revoked (RevokedReason = AdminBlock), RevokedSessionsCount = 4, RevokedSessionIds содержит 4 id; статус учётки не меняется.
  • Given конкретный SessionId активной сессии пользователя, When revoke, Then отзывается только она (RevokedSessionsCount = 1); прочие сессии живут.
  • Given SessionId принадлежит другому пользователю, When revoke, Then IDENTITY_SESSION_NOT_FOUND (404) — принадлежность чужому не раскрывается (fail closed).
  • Given активных сессий нет (или указанная уже Rotated/Revoked/Expired), When revoke, Then идемпотентный 200, RevokedSessionsCount = 0.
  • Given статус учётки Blocked или Archived, When revoke, Then успех — санитарная зачистка допустима для любого статуса.
  • Given параллельный refresh той же сессии, When revoke побеждает по версии, Then refresh проигравшего вернёт 401 (что и требуется).
  • Given сессии отозваны, у пользователя живой access-JWT, When обращение к gateway, Then отказ по кэшу отозванных sid в окне ≤ 60 сек; source of truth сессий — здесь, витрина user-activity обновится при следующем чтении.
  • Given повтор с тем же Idempotency-Key, When запрос по таймауту, Then сохранённый результат.

Совместимость и наблюдаемость

Breaking change

Новые optional поля (например, фильтр по платформе устройства) — additive; смена семантики SessionId = null — breaking (запрещена).

  • Logs: admin-sessions-revoked (userId, sessionId|all, count, adminUserId; IP/UA сессий не логируются повторно); metrics: identity_admin_operations_total{operation="revoke-sessions"}, identity_sessions_revoked_total{reason="AdminBlock"}; traces — OTel; deep audit. Runbook «массовый отзыв сессий» (README identity) — использование этой операции батчем по списку пользователей при инцидентах.

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

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

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

  • Сущность: user-session.md (RefreshSession, stateDiagram, SessionRevokeReason, INV-S6/S7); user.md.
  • Витрина сессий/устройств — docs/06-services/user-activity/ (ADR-0054); событие входа — user-signed-in.
  • Соседние admin-операции: block-user (блокировка = статус + отзыв), delete-user, reset-user-security.
  • ADR-0052 (record-level по UserId), ADR-0053.