POST /api/v1/auth/sign-out — Выход (отзыв refresh-сессии)
Отзыв текущей refresh-сессии (Active → Revoked, RevokedReason = UserLogout) и очистка refresh-cookie.
Опция allDevices = true отзывает все активные сессии пользователя (UserRevokedAll).
Назначение
Пользователь завершает сессию на устройстве или разом на всех устройствах (например, при утере телефона).
Витрина списка сессий — контекст user-activity (ADR-0054), source of truth и отзыв — здесь.
Owning-контекст — identity.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | auth |
| Статус | approved |
| Документ | docs/06-services/identity/api/investor/sign-out.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (валидный access-JWT; при истёкшем access выход выполняется без сервера — клиент чистит хранилище) |
| Auth policy / Permissions | authenticated; permission не требуется (self-операция; каталожное право витрины — user-activity.sessions.revoke-self) |
| Scope (RBAC) | не применяется |
| Record-level | отзываются только сессии UserId = actor (claim sub); sid claim определяет «текущую» |
| Tenant isolation | не применяется — RefreshSession не tenanted, record-level по UserId |
| Audit | RevokedByUserId = actor, событие SessionRevokedEvent на каждую отозванную |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/auth/sign-out |
| Success status | 204 |
| Idempotency header | не применяется — повтор по уже отозванной сессии возвращает 204 (идемпотентный исход by design) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | Required | record-level |
SessionId | identity claims (sid) | Guid? | Нет (нет у токенов без сессии) | — | связь access ↔ session (user-session.md) |
AllDevices | body | bool | Нет | default false | — |
Модель ответа SignOutCommandResult
HTTP-тело отсутствует (204 No Content); внутренняя модель для логов:
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
RevokedCount | int | Да | число затронутых сессий | — | 0 — валидный исход (токен без sid / сессия уже отозвана). |
Алгоритм
- Контекст и доступ. Актор — аутентифицированный пользователь; известны
UserId(sub) и, опционально,SessionId(sid). Требуется только аутентификация. - Проверка. Существование сессии не проверяется как ошибка: отсутствие/уже-отозванность →
RevokedCount = 0, всё равно204(выход обязан удаваться всегда). - Правила и переходы. Переход
Active → Revoked(stateDiagram user-session.md) для набора сессий:AllDevices = false: отзывается активная сессияUserId = actor,Id = SessionId,RevokedReason = UserLogout; при отсутствииSessionId— изменений нет,RevokedCount = 0;AllDevices = true: отзываются все активные сессииUserId = actor,RevokedReason = UserRevokedAll(включая текущую). Маппинг:Status = Revoked,RevokedAt = now,RevokedByUserId = actor,UpdatedAt = now.
- Что меняется. Пакетный (set-based) отзыв в одном согласованном изменении, без конфликтов
(терминальное состояние; гонка с параллельным refresh отсекается условием
Status == Active). - События. Доменное
SessionRevokedEvent(SessionId,UserId,RevokedReason) — по одному на отозванную сессию. Интеграционное событие не публикуется: витринаuser-activityподписана на доменную ленту через internal-механизм; публикация вpin.identity.sign-ins.v1— только про входы (user-session.md «События»). - Результат.
204; web —Set-Cookie: pin:refresh=; Max-Age=0(очистка). Access-JWT остаётся криптографически валиден доexp(≤ 15 мин) — привилегированные операции дополнительно гейтятся check-permission (deny по отозванной сессии не проверяется — деградация принята и ограничена коротким TTL access-токена).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none (см. шаг 5) |
| Внешние вызовы | none |
| Проекции / поиск | none (permissions не меняются — кэш perm:{userId} не трогается) |
| Уведомления | none |
Витрина устройств user-activity (ADR-0054) подписана на доменную ленту сессий и снимает отозванные записи
асинхронно (лаг — секунды): пользователь сразу теряет refresh (cookie очищена), а в списке устройств
отозванная сессия исчезает с задержкой. Уже выданный access-JWT остаётся валиден до exp (≤ 15 мин) —
привилегированные операции доотсекаются check-permission;
на неприв. read-операциях (напр. GET /me) отзыв не проверяется (принятая деградация, окно ≤ TTL access).
Предусловия и постусловия
- Пред: предъявлен access-JWT (валидный или уже истёкший — во втором случае клиент выходит локально
без сервера). Известны
UserId(sub) и, опционально,SessionId(sid). - Пост (
AllDevices = false): активная сессияId = SessionId→Revoked(UserLogout); при отсутствииsidизменений нет (RevokedCount = 0). Cookie refresh очищена. - Пост (
AllDevices = true): все активные сессииUserId = actor→Revoked(UserRevokedAll), включая текущую; на каждую —SessionRevokedEvent.
Acceptance Criteria
- Given аутентифицированный пользователь с активной текущей сессией, When
POST /sign-out(allDevices = false), Then204, текущая сессияActive → Revoked(UserLogout),RevokedCount = 1, cookiepin:refreshочищена, публикуетсяSessionRevokedEvent. - Given пользователь с 3 активными сессиями, When
POST /sign-out(allDevices = true), Then204, все 3 →Revoked(UserRevokedAll),RevokedCount = 3, 3×SessionRevokedEvent. - Given access-JWT без claim
sid, WhenPOST /sign-out(allDevices = false), Then204,RevokedCount = 0(no-op — валидный исход). - Given сессия уже отозвана, When повторный
POST /sign-out, Then204,RevokedCount = 0(идемпотентный исход, событие повторно не публикуется). - Given нет/просрочен access-JWT, When
POST /sign-out, Then401; клиент всё равно очищает локальное хранилище (эквивалент выхода).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
UNAUTHORIZED | 401 | Нет/просрочен access-JWT | — | клиент чистит локально (эквивалент выхода) |
INTERNAL | 500 | Инфраструктурный сбой | generic | retry; клиент всё равно чистит локальное хранилище |
Выход идемпотентен и «всегда успешен» by design.
Совместимость и наблюдаемость
- Токены без
sidдают no-op204— клиент может мигрировать раньше бэкенда. - Logs:
UserId,RevokedCount,AllDevices; metrics:identity_sign_out_total{all_devices}; traces: OTel; dashboardidentity-overview; runbook: массовый отзыв сессий (README identity).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): GET /api/investors/me — Текущий инвестор (getCurrentInvestor), Identity API — карта областей и реестр investor-области, POST /api/v1/auth/refresh — Обновление пары токенов (refresh-flow)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущность: domain/user-session.md (stateDiagram,
SessionRevokeReason). - Вход/ротация: refresh-token.md; витрина сессий:
docs/06-services/user-activity/(ADR-0054).