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

POST /api/investors/auth/request-sign-in-code — Первый шаг входа (requestSignInCode)

Назначение

Первый шаг входа существующего инвестора: по номеру телефона определить следующий шаг аутентификации (SignInNextStep) и, если способа «пароль/пин-код» нет, — отправить СМС-код и создать запрос подтверждения. Вызывается анонимным клиентом, когда пользователь выбирает явный вход по номеру (основной путь формы входа — sign-up-or-sign-in.md).

Metadata

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

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

ПроверкаЗначение
Actor typeAnonymous (гость, до выдачи токена)
Auth policy / Permissionsnone (анонимная операция входа)
Scope (RBAC)не применяется
Record-levelсвязка по PhoneNumber (запрос кода принадлежит номеру)
Tenant isolationне применяется — инвестор вне tenant (ADR-0052)
Auditread-sensitive: факт запроса кода логируется с маскированным номером

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/investors/auth/request-sign-in-code
Success status200
Idempotency headerне применяется (повтор = новый код, предыдущий Pending гасится — INV-S1)
Correlationсквозной trace через traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
phoneNumberbodystringДаmin 1, max 32; нормализация в E.164 перед поискомPII: маскирование в логах +7***1234
IpAddressconnection/middlewarestring?НетIPv4/IPv6 (≤ 45 симв.)ключ rate-limit INV-S4
UserAgentheader User-Agentstring?Нет≤ 512 симв.анти-фрод

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

ПолеТипОбязательностьИсточникMaskingОписание
nextStepSignInNextStepДаcomputed: RequiresPassword если задан пароль, иначе RequiresPinCode если задан пин-код, иначе RequiresSmsCodeСледующий шаг входа (enum — README.md).
signInConfirmationRequestIdstring (Guid)?Нет (задан только при nextStep = RequiresSmsCode)SignInConfirmationRequest.IdПередаётся в confirmSignIn.confirmationCode-варианте.
realConfirmationCodeDispatchedbool?Нет (задан при RequiresSmsCode)verifierfalse — код реально не отправлялся (тестовый контур).

Алгоритм

  1. Контекст и доступ. Актор анонимный; из middleware известны IpAddress/UserAgent. Повторный вызов легален (INV-S1).
  2. Проверка входа и лимиты. phoneNumber непустой, ≤ 32, валидный E.164 (нормализация) — иначе ValidationError. Rate-limit (INV-S4, счётчики по номеру и по IP) — превышение ⇒ IDENTITY_CONFIRMATION_RATE_LIMITED (429).
  3. Проверки сущностей. Найти пользователя по нормализованному номеру — не найден ⇒ IDENTITY_USER_NOT_FOUND (клиент переводит на signUpOrSignIn/signUp). Состояние: Status ∈ {Active, Invited} — иначе IDENTITY_USER_BLOCKED; блокировка входа снята (LockoutUntil <= now, INV-8) — иначе IDENTITY_USER_LOCKED_OUT.
  4. Правила и маппинг. Вычислить nextStep (маппинг в таблице ответа). Если nextStep != RequiresSmsCode — код не нужен, шаги записи не выполняются. Иначе сгенерировать код (CSPRNG, 4–6 цифр) и создать запрос подтверждения (INV-S8): цель SignIn, UserId, PhoneNumber = нормализованный, хэш кода (HMAC-SHA256), ExpiresAt = now + 5 мин, IpAddress, UserAgent; фиксируется доменное событие ConfirmationCodeRequestedEvent.
  5. Что меняется (только для RequiresSmsCode). В одном согласованном изменении: прежний активный (Pending) запрос той же пары (номер, SignIn) гасится в Superseded (INV-S1) и создаётся новый запрос.
  6. События. Интеграционных нет (вход ещё не совершён). Доменное ConfirmationCodeRequestedEvent вызывает СМС-verifier (внешний адаптер, timeout 5 с, 1 retry) и даёт признак realConfirmationCodeDispatched (false в тестовом контуре).
  7. Результат. nextStep; signInConfirmationRequestId и realConfirmationCodeDispatched — только для RequiresSmsCode (иначе null); сам код в ответ не попадает никогда; HTTP 200.

Диаграмма

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

ТипДетали
Интеграционные событияnone
Внешние вызовыСМС-verifier: отправка кода (timeout 5 с, 1 retry; сбой → операция падает IDENTITY_SMS_DISPATCH_FAILED)
Проекции / поискинкремент rate-limit счётчиков INV-S4
УведомленияСМС с кодом (канал verifier)

Предусловия и постусловия

  • Пред: актор анонимный; phoneNumber нормализуется в E.164; лимиты INV-S4 не превышены. Учётка с этим номером существует и во входопригодном состоянии (Active/Invited, без активного lockout).
  • Пост (RequiresPassword/RequiresPinCode): состояние не меняется — вычислен только nextStep, запрос кода и записи не создаются; клиент показывает форму пароля/пин-кода.
  • Пост (RequiresSmsCode): создан Pending-запрос цели SignIn (TTL 5 мин); прежний Pending пары (номер, SignIn) погашен в Superseded (INV-S1); СМС-код отправлен. Токен не выдаётся — вход завершит confirm-sign-in.md.

Acceptance Criteria

  • Given зарегистрированный номер, у пользователя задан пароль, When POST .../request-sign-in-code, Then 200 {nextStep = RequiresPassword} без отправки СМС; signInConfirmationRequestId и realConfirmationCodeDispatched = null.
  • Given зарегистрированный номер, пароля нет, но задан пин-код, When запрос, Then 200 {nextStep = RequiresPinCode} без СМС.
  • Given зарегистрированный номер без пароля и пин-кода, When запрос, Then 200 {nextStep = RequiresSmsCode, signInConfirmationRequestId, realConfirmationCodeDispatched}; создан Pending-запрос SignIn, код отправлен.
  • Given активный Pending-запрос SignIn уже есть, When повторный запрос, Then прежний → Superseded (INV-S1), создаётся новый, отправляется новый код (повтор легален).
  • Given номер не зарегистрирован, When запрос, Then IDENTITY_USER_NOT_FOUND (400); клиент переводит на signUpOrSignIn/signUp.
  • Given учётка Blocked/Archived, When запрос, Then IDENTITY_USER_BLOCKED (400).
  • Given LockoutUntil > now (INV-8), When запрос, Then IDENTITY_USER_LOCKED_OUT (400).
  • Given превышен лимит СМС (INV-S4, по номеру/IP), When запрос, Then IDENTITY_CONFIRMATION_RATE_LIMITED (429).
  • Given verifier отказал после retry, When запрос ветки RequiresSmsCode, Then IDENTITY_SMS_DISPATCH_FAILED (400). Код в ответ не попадает ни в одной ветке.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400Пустой/длинный/невалидный phoneNumberСписок ошибок валидацииno retry
IDENTITY_USER_NOT_FOUND400Номер не зарегистрирован«Пользователь не найден»no retry (перейти к регистрации)
IDENTITY_USER_BLOCKED400Status ∈ {Blocked, Archived}«Учётная запись заблокирована»no retry
IDENTITY_USER_LOCKED_OUT400LockoutUntil > now (INV-8)«Вход временно заблокирован, повторите позже»retry после LockoutUntil
IDENTITY_CONFIRMATION_RATE_LIMITED429Превышен лимит СМС (INV-S4)«Слишком много запросов кода»retry ≥ 60 сек
IDENTITY_SMS_DISPATCH_FAILED400Отказ verifier после retry«Не удалось отправить код»retry

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

  • Новые optional поля — без breaking change; неизвестные значения SignInNextStep клиент трактует как форму СМС-кода (safe default).
  • Logs: маскированный номер, nextStep, requestId; metrics: identity_sign_in_code_requests_total (label next_step), identity_sms_dispatch_failures_total; traces — OTel.

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

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

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