Перейти к основному содержимому

POST /api/investors/me/password/change — Смена/установка пароля (changePassword)

Назначение

Сменить или впервые установить пароль текущего пользователя, чтобы разрешить вход по паролю вместо кода из СМС. Вызывается аутентифицированным пользователем из личного кабинета. Если пароль уже установлен (hasPassword = true) — обязателен текущий пароль (INV-6). После установки вход предлагает шаг RequiresPassword.

Metadata

ПолеЗначение
Сервис/контекстidentity
API areapublic
Feature groupsecurity
Статусapproved
Документdocs/06-services/identity/api/investor/change-password.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (Bearer JWT)
Auth policy / Permissionsauthenticated; permission identity.security.manage-self
Scope (RBAC)self
Record-levelтолько собственная запись (UserId из sub)
Tenant isolationне применяется (ADR-0052)
Auditsecurity-change: событие UserSecurityChangedEvent, actor-stamping

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/investors/me/password/change
Success status200
Idempotency headerне применяется
Correlationсквозной trace через traceparent

Входные данные / параметры запроса (body ChangePasswordForm)

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДавалидный JWT401 без токена
currentPasswordbodystring?Условно: обязателен при установленном пароле (INV-6)max 128никогда не логируется
newPasswordbodystringДаmin 8, max 128 (INV-6 PasswordPolicy)никогда не логируется

Модель ответа ChangePasswordCommandResult

ПолеТипОбязательностьИсточникMaskingОписание
investorIdstring (Guid)ДаUserEntity.IdИдентификатор пользователя.

Алгоритм

  1. Контекст и доступ. Операцию выполняет аутентифицированный пользователь над собственной записью (UserId из токена).
  2. Проверка входа. newPassword непустой, длина 8–128; currentPassword ≤ 128 — иначе ValidationError.
  3. Проверки состояния. Получить пользователя; он Active — иначе IDENTITY_USER_BLOCKED; блокировка входа снята (LockoutUntil <= now, INV-8) — иначе IDENTITY_USER_LOCKED_OUT (защита от перебора текущего пароля).
  4. Правила и переходы.
    • если пароль уже установлен: currentPassword обязателен и должен пройти сверку; неуспех — счётчик неудачных попыток увеличивается (10 подряд ⇒ блокировка входа на 15 мин) и отказ IDENTITY_INVALID_CURRENT_PASSWORD;
    • если пароль ещё не установлен (первая установка): currentPassword не требуется;
    • при успехе: сохраняется новый хэш пароля (Argon2id), PasswordChangedAt = now, счётчик неудачных попыток обнуляется;
    • фиксируется доменное событие UserSecurityChangedEvent (вид PasswordChanged);
    • отзыв всех прочих активных refresh-сессий пользователя, кроме текущей (INV-S7, причина PasswordChanged).
  5. Что меняется. В одном согласованном изменении обновляется пользователь и отзываются прочие сессии; конкурентная смена пароля разрешается оптимистической блокировкой (правка не должна затирать чужую).
  6. События. Интеграционных нет (флаг hasPassword не входит в снапшот pin.identity.users.v1); доменное UserSecurityChangedEvent — лента админ-аудита.
  7. Результат. investorId; пароли и хэши в ответ, логи и события не попадают; HTTP 200.

Диаграмма

Побочные эффекты

ТипДетали
Интеграционные событияnone
Внешние вызовыnone
Проекции / поискnone
Уведомленияуведомление «пароль изменён» через сервис notifications
Семантика отзыва сессий

Отзываются все refresh-сессии пользователя, кроме текущей (INV-S7, причина PasswordChanged): на прочих устройствах обновление токена больше не пройдёт. Уже выданные access-JWT на других устройствах остаются валидными до истечения своего срока (короткоживущие) — мгновенного разлогина на них нет; фактический выход происходит на ближайшем refresh. Текущая сессия сохраняется, поэтому клиент, сменивший пароль, не разлогинивается.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/просрочен JWTклиент выполняет re-loginre-login
ValidationError400newPassword вне 8–128 симв.Список ошибок валидацииno retry
IDENTITY_INVALID_CURRENT_PASSWORD400Неверный/отсутствующий currentPassword при установленном пароле (INV-6)«Неверный текущий пароль»no retry
IDENTITY_USER_BLOCKED400Status != Active«Учётная запись заблокирована»no retry
IDENTITY_USER_LOCKED_OUT400LockoutUntil > now (INV-8)«Операция временно заблокирована»retry позже

Acceptance Criteria

  • Given активный пользователь с установленным паролем и верным currentPassword, When change-password с валидным newPassword (8–128), Then сохраняется новый хэш, PasswordChangedAt = now, счётчик неудачных попыток обнуляется, поднимается UserSecurityChangedEvent (PasswordChanged), прочие refresh-сессии отзываются, ответ 200 {investorId}.
  • Given пользователь без установленного пароля (первая установка), When change-password без currentPassword, Then пароль устанавливается, дальнейший вход предлагает шаг RequiresPassword.
  • Given пароль установлен, currentPassword неверен, When change-password, Then IDENTITY_INVALID_CURRENT_PASSWORD (400), счётчик неудачных попыток увеличивается; на 10-й подряд — блокировка входа на 15 минут.
  • Given LockoutUntil > now (INV-8), When change-password, Then IDENTITY_USER_LOCKED_OUT (400) без сверки пароля.
  • Given Status != Active, When change-password, Then IDENTITY_USER_BLOCKED (400).
  • Given newPassword короче 8 или длиннее 128, When change-password, Then ValidationError (400); значения паролей не попадают в логи/событие/ответ.

Совместимость и наблюдаемость

  • Ужесточение парольной политики — только серверно (min 8 остаётся контрактным минимумом); additive-поля результата допустимы.
  • Logs: UserId, вид операции (set/change), результат — без значений паролей; metrics: identity_password_changes_total{kind,result}; traces — OTel.

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы