POST /api/investors/me/2fa/enable-confirm — Подтверждение включения 2FA (confirmEnableTwoFactor)
Назначение
Второй шаг включения двухфакторной аутентификации: проверить СМС-код из
request-enable-two-factor.md и включить 2FA (IsTwoFactorEnabled = true,
INV-7: изменение 2FA только по подтверждению кодом). Вызывается аутентифицированным пользователем. После
включения вход по паролю/пин-коду дополнительно требует СМС-код.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | security |
| Статус | approved |
| Документ | docs/06-services/identity/api/investor/confirm-enable-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 (TwoFactorEnabled) |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/investors/me/2fa/enable-confirm |
| Success status | 200 |
| Idempotency header | не применяется; одноразовость — статус запроса Confirmed + оптимистическая блокировка |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса (body ConfirmEnableTwoFactorForm)
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
code | body | string | Да | min 1, max 16 | никогда не логируется |
Модель ответа ConfirmEnableTwoFactorCommandResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
investorId | string (Guid) | Да | UserEntity.Id | — | Идентификатор пользователя. |
Алгоритм
- Контекст и доступ. Операцию выполняет аутентифицированный пользователь над собственной записью
(
UserIdиз токена). - Проверка входа.
codeнепустой, ≤ 16 символов — иначеValidationError. - Проверки состояния. Получить пользователя; он
Active— иначеIDENTITY_USER_BLOCKED; 2FA ещё не включена — иначеIDENTITY_TWO_FACTOR_ALREADY_ENABLED. Найти его активный (Pending) запрос с цельюEnableTwoFactor(активный запрос на цель единственный, INV-S1) — нет запроса ⇒IDENTITY_CONFIRMATION_INVALID; запрос не истёк (INV-S3) — иначеIDENTITY_CONFIRMATION_EXPIRED. - Правила и переходы. Подтверждение кода: счётчик попыток увеличивается до сравнения (INV-S2; 5-я
неверная ⇒ статус
Exhausted+IDENTITY_CONFIRMATION_EXHAUSTED); сравнение HMAC-SHA256(code) с хэшем в постоянном времени (INV-S5) — неверно ⇒IDENTITY_CONFIRMATION_INVALID; верно ⇒ запросConfirmed,ConfirmedAt = now. Пользователь:IsTwoFactorEnabled = true,TwoFactorChangedAt = now; фиксируется доменное событиеUserSecurityChangedEvent(TwoFactorEnabled). - Что меняется. В одном согласованном изменении обновляются запрос и пользователь; неверные попытки
(счётчик) сохраняются до возврата ошибки; гонка двойного confirm разрешается оптимистической
блокировкой — проигравший получает
IDENTITY_CONFIRMATION_INVALID. - События. Интеграционных нет (
isTwoFactorEnabledне входит в снапшотpin.identity.users.v1); доменноеUserSecurityChangedEvent— аудит. - Результат.
investorId; HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | none |
| Внешние вызовы | none |
| Проекции / поиск | none |
| Уведомления | уведомление «2FA включена» через сервис notifications |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
| — (auth) | 401 | Нет/просрочен JWT | клиент выполняет re-login | re-login |
ValidationError | 400 | Пустой/длинный code | Список ошибок валидации | no retry |
IDENTITY_CONFIRMATION_INVALID | 400 | Нет Pending-запроса / неверный код (INV-S5) | «Неверный код подтверждения» | ввести заново |
IDENTITY_CONFIRMATION_EXPIRED | 400 | ExpiresAt <= now (INV-S3) | «Код истёк» | новый request-шаг |
IDENTITY_CONFIRMATION_EXHAUSTED | 400 | 5 неверных попыток (INV-S2) | «Превышено число попыток» | новый request-шаг |
IDENTITY_TWO_FACTOR_ALREADY_ENABLED | 400 | 2FA уже включена | «2FA уже включена» | no retry |
IDENTITY_USER_BLOCKED | 400 | Status != Active | «Учётная запись заблокирована» | no retry |
Acceptance Criteria
- Given активный пользователь с выключенной 2FA и непросроченным
Pending-запросомEnableTwoFactor, When confirm с вернымcode, Then запрос →Confirmed,IsTwoFactorEnabled = true,TwoFactorChangedAt = now, поднимаетсяUserSecurityChangedEvent(TwoFactorEnabled), ответ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-запросаEnableTwoFactorу актора, When confirm, ThenIDENTITY_CONFIRMATION_INVALID(400). - Given 2FA уже включена, When confirm, Then
IDENTITY_TWO_FACTOR_ALREADY_ENABLED(400); GivenStatus != Active, ThenIDENTITY_USER_BLOCKED(400). - Given два параллельных confirm одного запроса, When оба доходят до записи, Then один
включает 2FA, второй проигрывает по оптимистической блокировке (
IDENTITY_CONFIRMATION_INVALID).
Совместимость и наблюдаемость
- Logs:
UserId, результат подтверждения; metrics:identity_2fa_enable_confirms_total{result}; traces — OTel.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): Identity API — карта областей и реестр investor-области, POST /api/investors/me/2fa/disable-confirm — Подтверждение выключения 2FA (confirmDisableTwoFactor), POST /api/investors/me/2fa/enable-request — Запрос включения 2FA (requestEnableTwoFactor)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: user.md (INV-7,
IsTwoFactorEnabled), user-session.md (INV-S1..S5). - Пред-шаг: request-enable-two-factor.md; эффект на вход — confirm-sign-in.md, verify-sign-in-password.md.