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 area | admin (ADR-0053) |
| Feature group | admin-sessions |
| Статус | draft |
| Документ | docs/06-services/identity/api/admin/revoke-user-sessions.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | ExternalSystem (система администрирования; m2m client-credentials) |
| Auth policy / Permissions | policy AdminSystemOnly; каталожное соответствие — платформенный уровень user-activity.sessions.* для витрины + admin-право управления сессиями (для системы администрирования каталог справочный, ADR-0053) |
| Scope (RBAC) | платформа (кросс-tenant) |
| Record-level | администратор видит/отзывает сессии любого пользователя; каждый доступ аудируется |
| Tenant isolation | bypass tenant-фильтров под system-scope; refresh_sessions не tenanted — изоляция record-level по UserId (ADR-0052) |
| Audit | обязателен: deep audit — actor, userId, sessionId/all, причина |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/admin/v1/users/{userId}/sessions/revoke |
| Success status | 200 |
| Idempotency header | Idempotency-Key (Guid; обязателен) |
| Correlation | OTel trace (traceparent); X-Admin-User-Id — обязателен |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | route | Guid | Да | != Guid.Empty; существование — шаг 2 | — |
SessionId | body | Guid? | Нет | null — отозвать ВСЕ Active-сессии; иначе — конкретную (RefreshSession.Id, обязана принадлежать UserId) | — |
Reason | body | string? | Нет | ≤ 512; свободный текст для аудита («компрометация устройства», «запрос пользователя, тикет N») | только audit |
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | audit-stamping |
IdempotencyKey | header Idempotency-Key | Guid | Да | != Guid.Empty | — |
Модель ответа AdminRevokeUserSessionsResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
UserId | Guid | Да | команда | — | Пользователь. |
RevokedSessionsCount | int | Да | батч-отзыв | — | Сколько сессий переведено Active → Revoked (0 — не было активных: идемпотентный успех). |
RevokedSessionIds | IReadOnlyList<Guid> | Да (может быть пустым) | id мутированных записей | — | Отозванные сессии — для кэша отозванных sid на gateway (fan-out системой администрирования при необходимости). |
Слепки устройств (SessionDevice) в ответ не входят — просмотр списка сессий вынесен в отдельную
admin-query витрины user-activity.
Алгоритм
- Контекст и доступ. Операцию выполняет система администрирования (m2m-клиент
pin-admin-system, policyAdminSystemOnly). Повтор с тем жеIdempotency-Key(admin:sessions-revoke:{IdempotencyKey}) возвращает сохранённый результат. - Проверка входа и предусловий. Заголовки заполнены,
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). - Бизнес-правила и переходы. Целевые сессии переводятся
Active → Revoked(stateDiagramRefreshSessionuser-session.md): проставляютсяRevokedAtиRevokedReason = AdminBlock(admin-инициатива, enum user-session.md). Цепочки ротаций (ParentSessionId) дополнительно не трогаются:Rotated-предки уже не активны, replay-защита INV-S6 продолжает действовать. - Что меняется. При
SessionId = nullотзываются все активные сессии пользователя, иначе — указанная сессия; число и идентификаторы отозванных возвращаются какRevokedSessionsCountиRevokedSessionIds. Правка не должна затирать конкурентный refresh той же сессии — проигравший получает ошибку статуса (refresh вернёт 401, что и требуется). Отзыв сессий, отметка идемпотентности и audit — в одном согласованном изменении. - События. На каждую отозванную сессию фиксируется доменный
SessionRevokedEvent(SessionId,UserId,RevokedReason) — лента расследований заботы (user-session.md). Интеграционного события отзыва сессии в контрактах identity нет: gateway узнаёт об отзыве через кэш интроспекции отозванныхsid(internal-API), инвалидация — вытеснением TTL ≤ 60 сек. - Результат.
AdminRevokeUserSessionsResult:UserId,RevokedSessionsCount,RevokedSessionIds(для кэша отозванныхsidна gateway). Слепки устройств в ответ не входят.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (см. шаг 5; блокировка учётки с отзывом — block-user с событием) |
| External calls | none |
| Cache / projections / search | кэш интроспекции sid на gateway — вытеснение TTL ≤ 60 сек; витрина сессий user-activity обновится при следующем чтении source of truth |
| Notifications | none напрямую (алерт пользователю — инициатива системы администрирования) |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Невалидные заголовки/Reason | Ошибка валидации запроса | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system | Доступ запрещён | no retry |
IDENTITY_USER_NOT_FOUND | 404 | Пользователь не существует | Пользователь не найден | no retry |
IDENTITY_SESSION_NOT_FOUND | 404 | SessionId не существует или принадлежит другому пользователю | Сессия не найдена | no retry |
CONCURRENCY_CONFLICT | 409 | Гонка версий 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, ThenIDENTITY_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 сохранённый результат.
Совместимость и наблюдаемость
Новые 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) — использование этой операции батчем по списку пользователей при инцидентах.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): DELETE /api/admin/v1/users/{userId} — Удаление пользователя: архив vs полное удаление (admin), Identity Admin API — реестр методов и общие правила области, POST /api/admin/v1/users/{userId}/block — Блокировка пользователя (admin), POST /api/admin/v1/users/{userId}/security/reset — Ручной сброс факторов входа (admin)
- События (1): Event Contract: pin.identity.user-signed-in (UserSignedInKafkaEvent)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: 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.