POST /api/admin/v1/users/{userId}/security/reset — Ручной сброс факторов входа (admin)
Назначение
Операция «ручная верификация/сброс» из ADR-0053: пользователь потерял доступ (забыл пароль/пин,
утратил устройство 2FA, залочен по попыткам) и его личность подтверждена вручную (Служба заботы по
регламенту верификации → админ выполняет сброс). Сбрасывает выбранные факторы входа
(PasswordHash/PinCodeHash/IsTwoFactorEnabled/lockout) и отзывает все активные сессии — после
сброса пользователь входит заново штатным СМС-flow по своему номеру и настраивает факторы с нуля.
Новые значения пароля/пин-кода API не принимает (запрещено: plaintext-секреты через admin-канал) — только сброс в «не задано».
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | admin (ADR-0053) |
| Feature group | admin-users |
| Статус | draft |
| Документ | docs/06-services/identity/api/admin/reset-user-security.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | ExternalSystem (система администрирования; m2m client-credentials) |
| Auth policy / Permissions | policy AdminSystemOnly; в системе администрирования операция доступна после процедуры ручной верификации личности (граница admin-identity, ADR-0053) |
| Scope (RBAC) | платформа (кросс-tenant) |
| Tenant isolation | bypass tenant-фильтров под system-scope |
| Audit | обязателен: deep audit — actor, userId, какие факторы сброшены, VerificationTicketId |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/admin/v1/users/{userId}/security/reset |
| Success status | 200 |
| Idempotency header | Idempotency-Key (Guid; обязателен) |
| Correlation | OTel trace (traceparent); X-Admin-User-Id — обязателен |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | route | Guid | Да | != Guid.Empty | — |
ResetPassword | body | bool | Да | — | PasswordHash → null |
ResetPinCode | body | bool | Да | — | PinCodeHash → null |
DisableTwoFactor | body | bool | Да | — | IsTwoFactorEnabled → false |
ResetLockout | body | bool | Да | — | FailedAccessAttempts → 0, LockoutUntil → null |
VerificationTicketId | body | Guid | Да | != Guid.Empty; id тикета заботы, где зафиксирована ручная верификация личности (support-контекст; существование identity не проверяет — принадлежит admin-процессу, только аудит) | audit |
Comment | body | string? | Нет | ≤ 1024 | только audit |
AdminUserId | header X-Admin-User-Id | Guid | Да | != Guid.Empty | audit-stamping |
IdempotencyKey | header Idempotency-Key | Guid | Да | != Guid.Empty | — |
Хотя бы один из ResetPassword/ResetPinCode/DisableTwoFactor/ResetLockout = true (иначе
ValidationError).
Модель ответа AdminResetUserSecurityResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
UserId | Guid | Да | UserEntity.Id | — | Пользователь. |
PasswordReset | bool | Да | вычисление (флаг ∧ был задан) | — | Пароль фактически сброшен. |
PinCodeReset | bool | Да | то же | — | Пин-код фактически сброшен. |
TwoFactorDisabled | bool | Да | то же | — | 2FA фактически выключена. |
LockoutReset | bool | Да | то же | — | Lockout фактически снят. |
RevokedSessionsCount | int | Да | батч-отзыв | — | Отозвано активных сессий. |
Version | long | Да | UserEntity.Version | — | Версия после мутации. |
Хэши/секреты в ответ не попадают никогда (правило user.md).
Алгоритм
- Контекст и доступ. Операцию выполняет система администрирования (m2m-клиент
pin-admin-system, policyAdminSystemOnly) после процедуры ручной верификации личности. Повтор с тем жеIdempotency-Key(admin:user-security-reset:{IdempotencyKey}) возвращает сохранённый результат. - Проверка входа и предусловий. Задан хотя бы один флаг сброса,
VerificationTicketIdне пуст,Comment ≤ 1024— иначеValidationError(400). ПользовательUserIdсуществует — иначеIDENTITY_USER_NOT_FOUND(404). Статус ∈ {Active,Invited,Blocked} (сброс заблокированному допустим — готовит вход после unblock);Archived→IDENTITY_USER_INVALID_STATUS_TRANSITION(409). ДляContactsManagedBy = TalentSyncоперация допустима: секреты входа локальны для PIN и синком не управляются (talent-replica.md), INV-4 user.md не затрагивается. - Бизнес-правила и переходы. По установленным флагам сбрасываются факторы входа:
ResetPassword→ пароль снимается (PasswordHash → null,PasswordChangedAt = now, производный флагhasPasswordстанетfalse);ResetPinCode→ пин-код снимается (PinCodeHash → null,hasPinCode = false);DisableTwoFactor→ 2FA выключается (IsTwoFactorEnabled → false,TwoFactorChangedAt = now) — без СМС-подтверждения (INV-7 user.md описывает self-service flow; admin-сброс — отдельный привилегированный путь, компенсируется ручной верификацией и аудитом);ResetLockout→ снимается блокировка попыток (FailedAccessAttempts → 0,LockoutUntil → null, INV-8 снимается). Флаги, для которых фактор и так не задан, — без эффекта (в ответеfalse); фиксируется вид изменения на каждый применённый сброс.
- Что меняется. Учётка обновляется по применённым флагам. Все активные сессии отзываются (INV-S7):
Active → RevokedcRevokedReason = PasswordChanged. Висящие одноразовые запросы кодов гасятся:SignInConfirmationRequestв статусеPending → Superseded(открытые 2FA/смены-контакта flow обнуляются). Правка не должна затирать конкурентное изменение (CONCURRENCY_CONFLICT). Новые значения пароля/пин-кода операция не принимает — только сброс в «не задано». - События. Интеграционных нет: состав публичных полей
UserUpsertedKafkaEventне меняется (секреты не публикуются), права не менялись. ДоменныеUserSecurityChangedEventиSessionRevokedEvent— лента админ-аудита/расследований (user.md, user-session.md). Изменение учётки, отзыв сессий, гашение кодов, отметка идемпотентности и audit — в одном согласованном изменении. - Результат.
AdminResetUserSecurityResult:UserId, флаги фактически применённого сброса (PasswordReset/PinCodeReset/TwoFactorDisabled/LockoutReset— по состоянию до мутации),RevokedSessionsCount,Version. Хэши/секреты в ответ не попадают.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (публичный снапшот пользователя не изменился) |
| External calls | none (СМС не отправляется — пользователь сам инициирует вход) |
| Cache / projections / search | none |
| Notifications | рекомендуемое расширение: уведомление пользователю «факторы входа сброшены поддержкой» — инициируется системой администрирования, не identity |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Все флаги false / нет VerificationTicketId | Ошибка валидации запроса | no retry |
FORBIDDEN | 403 | Клиент не pin-admin-system | Доступ запрещён | no retry |
IDENTITY_USER_NOT_FOUND | 404 | Пользователь не существует | Пользователь не найден | no retry |
IDENTITY_USER_INVALID_STATUS_TRANSITION | 409 | Status = Archived | Пользователь в архиве | no retry |
CONCURRENCY_CONFLICT | 409 | Гонка версий | Повторите операцию | retry с тем же Idempotency-Key |
Acceptance Criteria
- Given учётка
Activeс паролем, пином, включённой 2FA и активным lockout, When reset со всеми флагами =trueи валиднымVerificationTicketId, ThenPasswordHash/PinCodeHash → null,IsTwoFactorEnabled = false, lockout снят, все активные сессии отозваны, висящиеSignInConfirmationRequestвPending → Superseded, ответ сPasswordReset/PinCodeReset/ TwoFactorDisabled/LockoutReset = true; секреты в ответ не попадают. - Given у учётки не задан пароль, а
ResetPassword = true, When reset, ThenPasswordReset = false(фактор и так отсутствовал); остальные применённые флаги — по состоянию до мутации. - Given все четыре флага
false, When reset, ThenValidationError(400) — требуется хотя бы один сброс. - Given пустой
VerificationTicketId, When reset, ThenValidationError(400). - Given учётка
Blocked, When reset, Then успех (сброс готовит вход после unblock); статусArchived→IDENTITY_USER_INVALID_STATUS_TRANSITION(409). - Given Talent-managed учётка (
ContactsManagedBy = TalentSync), When reset, Then успех — секреты входа локальны для PIN и синком не управляются. - Given повтор с тем же
Idempotency-Key, When запрос по таймауту, Then сохранённый результат (replay), факторы повторно не трогаются. - Given сброс выполнен, Then интеграционные события не публикуются (публичный снапшот не изменён); пользователь входит заново штатным СМС-flow по своему номеру.
Совместимость и наблюдаемость
- Новые optional-флаги сброса — additive; приём значений секретов — запрещён навсегда (security rule, не совместимостное).
- Logs:
admin-security-reset(userId, применённые флаги, adminUserId, verificationTicketId; без секретов/PII); metrics:identity_admin_operations_total{operation="reset-security"}; traces — OTel; deep audit. Алерт на всплеск операций (возможная компрометация системы администрирования).
Обратные ссылки
Автоссылки: где используется этот документ.
- 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}/sessions/revoke — Отзыв сессий пользователя (admin)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: user.md (INV-6/7/8,
hasPassword/hasPinCode), user-session.md (INV-S7). - Соседние admin-операции: block-user, delete-user, revoke-user-sessions.
- Self-service аналоги:
changePassword/setPinCode/clearPinCode/2FA-flow. ADR-0053.