POST /api/investors/me/2fa/disable-request — Запрос выключения 2FA (requestDisableTwoFactor)
Назначение
Первый шаг выключения двухфакторной аутентификации: отправить СМС-код на текущий телефон пользователя и
создать запрос подтверждения с целью DisableTwoFactor (INV-7: выключение — только по подтверждению
кодом, защита от отключения 2FA угнанным токеном). Вызывается аутентифицированным пользователем.
Выключение применит confirm-disable-two-factor.md.
| Поле | Значение |
|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | security |
| Статус | approved |
| Документ | docs/06-services/identity/api/investor/request-disable-two-factor.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Bearer JWT) |
| Auth policy / Permissions | authenticated; permission identity.security.manage-self |
| Scope (RBAC) | self |
| Record-level | только собственная запись; код — только на телефон владельца |
| Tenant isolation | не применяется (ADR-0052) |
| Audit | security-sensitive: факт запроса логируется |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/investors/me/2fa/disable-request |
| Success status | 200 |
| Idempotency header | не применяется (повтор гасит предыдущий Pending — INV-S1 Superseded) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса
Body отсутствует.
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
IpAddress / UserAgent | middleware | string? | Нет | ≤ 45 / ≤ 512 | rate-limit / анти-фрод |
Модель ответа RequestDisableTwoFactorCommandResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|
twoFactorConfirmationRequestId | string (Guid) | Да | SignInConfirmationRequest.Id | — | Идентификатор запроса подтверждения. |
realConfirmationCodeDispatched | bool | Да | verifier | — | false — код не отправлялся реально (тестовый контур). |
Алгоритм
- Контекст и доступ. Операцию выполняет аутентифицированный пользователь над собственной записью
(
UserId из токена); из middleware известны IpAddress/UserAgent.
- Проверка и лимиты. Rate-limit (INV-S4) — превышение ⇒
IDENTITY_CONFIRMATION_RATE_LIMITED (429).
- Проверки состояния. Получить пользователя; он
Active — иначе IDENTITY_USER_BLOCKED; 2FA
включена — иначе IDENTITY_TWO_FACTOR_NOT_ENABLED.
- Правила и маппинг. Сгенерировать код (CSPRNG); создать запрос подтверждения (INV-S8): цель
DisableTwoFactor, UserId, PhoneNumber = телефон пользователя, хэш кода (HMAC-SHA256),
ExpiresAt = now + 5 мин, IpAddress, UserAgent; фиксируется доменное событие
ConfirmationCodeRequestedEvent (→ СМС-verifier).
- Что меняется. В одном согласованном изменении: прежний активный (
Pending) запрос той же цели
гасится в Superseded (INV-S1) и создаётся новый запрос.
- События. Интеграционных нет; доменное
ConfirmationCodeRequestedEvent → СМС-verifier.
- Результат.
twoFactorConfirmationRequestId, realConfirmationCodeDispatched; HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Интеграционные события | none |
| Внешние вызовы | СМС-verifier (timeout 5 с, 1 retry) |
| Проекции / поиск | rate-limit-счётчик |
| Уведомления | СМС с кодом |
Предусловия и постусловия
- Пред: пользователь аутентифицирован и
Active; 2FA включена (IsTwoFactorEnabled = true); телефон
подтверждён (код уходит на текущий номер владельца, вход не принимается).
- Пост (успех): создан
Pending-запрос цели DisableTwoFactor (TTL 5 мин); прежний Pending той же
цели погашен в Superseded (INV-S1); СМС-код отправлен через verifier. IsTwoFactorEnabled не
меняется — выключение применит только confirm-disable-two-factor.md
по коду (INV-7). Интеграционные события не публикуются.
Acceptance Criteria
- Given
Active-пользователь с включённой 2FA, When POST .../2fa/disable-request, Then
создан Pending-запрос DisableTwoFactor, код отправлен на текущий телефон владельца, ответ 200
с twoFactorConfirmationRequestId и realConfirmationCodeDispatched; IsTwoFactorEnabled не тронут.
- Given уже есть активный
Pending-запрос DisableTwoFactor, When повторный запрос, Then
прежний переходит в Superseded (INV-S1), создаётся новый, отправляется новый код (в тест-контуре
realConfirmationCodeDispatched = false).
- Given 2FA не включена, When запрос, Then
IDENTITY_TWO_FACTOR_NOT_ENABLED (400).
- Given учётка
Blocked/Archived, When запрос, Then IDENTITY_USER_BLOCKED (400).
- Given превышен лимит отправки кода (INV-S4), When запрос, Then
IDENTITY_CONFIRMATION_RATE_LIMITED (429), retry ≥ 60 сек.
- Given verifier отказал после retry, When запрос, Then
IDENTITY_SMS_DISPATCH_FAILED (400).
- Given запрос без валидного JWT, When запрос, Then
401, клиент уходит на re-login.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
| — (auth) | 401 | Нет/просрочен JWT | клиент выполняет re-login | re-login |
IDENTITY_TWO_FACTOR_NOT_ENABLED | 400 | 2FA не включена | «2FA не включена» | no retry |
IDENTITY_USER_BLOCKED | 400 | Status != Active | «Учётная запись заблокирована» | no retry |
IDENTITY_CONFIRMATION_RATE_LIMITED | 429 | INV-S4 | «Слишком много запросов кода» | retry ≥ 60 сек |
IDENTITY_SMS_DISPATCH_FAILED | 400 | Отказ verifier | «Не удалось отправить код» | retry |
Совместимость и наблюдаемость
- Logs:
UserId; metrics: identity_2fa_disable_requests_total; traces — OTel.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы