POST /api/investors/me/2fa/disable-confirm — Подтверждение выключения 2FA (confirmDisableTwoFactor)
Назначение
Второй шаг выключения двухфакторной аутентификации: проверить СМС-код из
request-disable-two-factor.md и выключить 2FA (IsTwoFactorEnabled = false, INV-7). Вызывается аутентифицированным пользователем. После выключения вход по паролю/пин-коду
больше не требует СМС-код.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | security |
| Статус | approved |
| Документ | docs/06-services/identity/api/investor/confirm-disable-two-factor.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Bearer JWT) |
| Auth policy / Permissions | authenticated; permission identity.security.manage-self |
| Scope (RBAC) | self |
| Record-level | Pending-запрос ищется строго по UserId актора |
| Tenant isolation | не применяется (ADR-0052) |
| Audit | security-change: событие UserSecurityChangedEvent (TwoFactorDisabled) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/investors/me/2fa/disable-confirm |
| Success status | 200 |
| Idempotency header | не применяется; одноразовость — статус запроса Confirmed + оптимистическая блокировка |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса (body ConfirmDisableTwoFactorForm)
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
code | body | string | Да | min 1, max 16 | никогда не логируется |
Модель ответа ConfirmDisableTwoFactorCommandResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
investorId | string (Guid) | Да | UserEntity.Id | — | Идентификатор пользователя. |
Алгоритм
- Контекст и доступ. Операцию выполняет аутентифицированный пользователь над собственной записью
(
UserIdиз токена). - Проверка входа.
codeнепустой, ≤ 16 символов — иначеValidationError. - Проверки состояния. Получить пользователя; он
Active— иначеIDENTITY_USER_BLOCKED; 2FA включена — иначеIDENTITY_TWO_FACTOR_NOT_ENABLED. Найти его активный (Pending) запрос подтверждения с цельюDisableTwoFactor— нет запроса ⇒IDENTITY_CONFIRMATION_INVALID; запрос не истёк (INV-S3) — иначеIDENTITY_CONFIRMATION_EXPIRED. - Правила и переходы. Подтверждение кода: счётчик попыток увеличивается до сравнения (INV-S2, 5-я
неверная ⇒ статус
Exhausted+IDENTITY_CONFIRMATION_EXHAUSTED); сравнение кода с хэшем в постоянном времени (INV-S5) — неверно ⇒IDENTITY_CONFIRMATION_INVALID; верно ⇒ запросConfirmed,ConfirmedAt = now. Пользователь:IsTwoFactorEnabled = false,TwoFactorChangedAt = now; фиксируется доменное событиеUserSecurityChangedEvent(TwoFactorDisabled). - Что меняется. В одном согласованном изменении обновляются запрос и пользователь; неверные попытки сохраняются до возврата ошибки; гонка двойного confirm разрешается оптимистической блокировкой.
- События. Интеграционных нет; доменное
UserSecurityChangedEvent(TwoFactorDisabled) — аудит. - Результат.
investorId; HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none |
| Внешние вызовы | none |
| Проекции / поиск | none |
| Уведомления | уведомление «2FA выключена» (security-алерт) через сервис notifications |
Почему это аудируется
Выключение 2FA — типовой признак компрометации аккаунта: угнанный токен/сессия отключает второй фактор,
чтобы закрепиться. Поэтому UserSecurityChangedEvent (TwoFactorDisabled) пишется в аудит, а владельцу
сразу уходит security-уведомление о выключении.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
| — (auth) | 401 | Нет/просрочен JWT | клиент выполняет re-login | re-login |
ValidationError | 400 | Пустой/длинный code | Список ошибок валидации | no retry |
IDENTITY_CONFIRMATION_INVALID | 400 | Нет Pending-запроса / неверный код | «Неверный код подтверждения» | ввести заново |
IDENTITY_CONFIRMATION_EXPIRED | 400 | INV-S3 | «Код истёк» | новый request-шаг |
IDENTITY_CONFIRMATION_EXHAUSTED | 400 | INV-S2 | «Превышено число попыток» | новый request-шаг |
IDENTITY_TWO_FACTOR_NOT_ENABLED | 400 | 2FA уже выключена | «2FA не включена» | no retry |
IDENTITY_USER_BLOCKED | 400 | Status != Active | «Учётная запись заблокирована» | no retry |
Acceptance Criteria
- Given активный пользователь с включённой 2FA и непросроченным
Pending-запросомDisableTwoFactor, When confirm с вернымcode, Then запрос →Confirmed,IsTwoFactorEnabled = false,TwoFactorChangedAt = now, поднимаетсяUserSecurityChangedEvent(TwoFactorDisabled), ответ200; последующий вход по паролю/пин-коду больше не требует СМС. - Given тот же контекст, но
codeневерный, When confirm, Then счётчик попыток растёт; на 5-й неверной — запрос →ExhaustedиIDENTITY_CONFIRMATION_EXHAUSTED; до этого —IDENTITY_CONFIRMATION_INVALID(400); 2FA остаётся включённой. - Given
Pending-запрос истёк (INV-S3), When confirm, ThenIDENTITY_CONFIRMATION_EXPIRED(400) — требуется новый request-шаг. - Given нет
Pending-запросаDisableTwoFactorу актора, When confirm, ThenIDENTITY_CONFIRMATION_INVALID(400). - Given 2FA уже выключена, When confirm, Then
IDENTITY_TWO_FACTOR_NOT_ENABLED(400); GivenStatus != Active, ThenIDENTITY_USER_BLOCKED(400). - Given два параллельных confirm одного запроса, When оба доходят до записи, Then один
выключает 2FA, второй проигрывает по оптимистической блокировке (
IDENTITY_CONFIRMATION_INVALID).
Совместимость и наблюдаемость
- Logs:
UserId, результат; metrics:identity_2fa_disable_confirms_total{result}; traces — OTel.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): Identity API — карта областей и реестр investor-области, POST /api/investors/me/2fa/disable-request — Запрос выключения 2FA (requestDisableTwoFactor)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: user.md (INV-7), user-session.md (INV-S1..S5).
- Пред-шаг: request-disable-two-factor.md; включение — confirm-enable-two-factor.md.