POST /api/investors/auth/request-sign-in-code — Первый шаг входа (requestSignInCode)
Назначение
Первый шаг входа существующего инвестора: по номеру телефона определить следующий шаг аутентификации
(SignInNextStep) и, если способа «пароль/пин-код» нет, — отправить СМС-код и создать запрос
подтверждения. Вызывается анонимным клиентом, когда пользователь выбирает явный вход по номеру (основной
путь формы входа — sign-up-or-sign-in.md).
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | auth |
| Статус | approved |
| Документ | docs/06-services/identity/api/investor/request-sign-in-code.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | Anonymous (гость, до выдачи токена) |
| Auth policy / Permissions | none (анонимная операция входа) |
| Scope (RBAC) | не применяется |
| Record-level | связка по PhoneNumber (запрос кода принадлежит номеру) |
| Tenant isolation | не применяется — инвестор вне tenant (ADR-0052) |
| Audit | read-sensitive: факт запроса кода логируется с маскированным номером |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/investors/auth/request-sign-in-code |
| Success status | 200 |
| Idempotency header | не применяется (повтор = новый код, предыдущий Pending гасится — INV-S1) |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса (body RequestSignInCodeForm)
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
phoneNumber | body | string | Да | min 1, max 32; нормализация в E.164 перед поиском | PII: маскирование в логах +7***1234 |
IpAddress | connection/middleware | string? | Нет | IPv4/IPv6 (≤ 45 симв.) | ключ rate-limit INV-S4 |
UserAgent | header User-Agent | string? | Нет | ≤ 512 симв. | анти-фрод |
Модель ответа RequestSignInCodeCommandResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
nextStep | SignInNextStep | Да | computed: RequiresPassword если задан пароль, иначе RequiresPinCode если задан пин-код, иначе RequiresSmsCode | — | Следующий шаг входа (enum — README.md). |
signInConfirmationRequestId | string (Guid)? | Нет (задан только при nextStep = RequiresSmsCode) | SignInConfirmationRequest.Id | — | Передаётся в confirmSignIn.confirmationCode-варианте. |
realConfirmationCodeDispatched | bool? | Нет (задан при RequiresSmsCode) | verifier | — | false — код реально не отправлялся (тестовый контур). |
Алгоритм
- Контекст и доступ. Актор анонимный; из middleware известны
IpAddress/UserAgent. Повторный вызов легален (INV-S1). - Проверка входа и лимиты.
phoneNumberнепустой, ≤ 32, валидный E.164 (нормализация) — иначеValidationError. Rate-limit (INV-S4, счётчики по номеру и по IP) — превышение ⇒IDENTITY_CONFIRMATION_RATE_LIMITED(429). - Проверки сущностей. Найти пользователя по нормализованному номеру — не найден ⇒
IDENTITY_USER_NOT_FOUND(клиент переводит наsignUpOrSignIn/signUp). Состояние:Status ∈ {Active, Invited}— иначеIDENTITY_USER_BLOCKED; блокировка входа снята (LockoutUntil <= now, INV-8) — иначеIDENTITY_USER_LOCKED_OUT. - Правила и маппинг. Вычислить
nextStep(маппинг в таблице ответа). ЕслиnextStep != RequiresSmsCode— код не нужен, шаги записи не выполняются. Иначе сгенерировать код (CSPRNG, 4–6 цифр) и создать запрос подтверждения (INV-S8): цельSignIn,UserId,PhoneNumber = нормализованный, хэш кода (HMAC-SHA256),ExpiresAt = now + 5 мин,IpAddress,UserAgent; фиксируется доменное событиеConfirmationCodeRequestedEvent. - Что меняется (только для
RequiresSmsCode). В одном согласованном изменении: прежний активный (Pending) запрос той же пары (номер,SignIn) гасится вSuperseded(INV-S1) и создаётся новый запрос. - События. Интеграционных нет (вход ещё не совершён). Доменное
ConfirmationCodeRequestedEventвызывает СМС-verifier (внешний адаптер, timeout 5 с, 1 retry) и даёт признакrealConfirmationCodeDispatched(false в тестовом контуре). - Результат.
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, Then200{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 запрос, ThenIDENTITY_USER_BLOCKED(400). - Given
LockoutUntil > now(INV-8), When запрос, ThenIDENTITY_USER_LOCKED_OUT(400). - Given превышен лимит СМС (INV-S4, по номеру/IP), When запрос, Then
IDENTITY_CONFIRMATION_RATE_LIMITED(429). - Given verifier отказал после retry, When запрос ветки
RequiresSmsCode, ThenIDENTITY_SMS_DISPATCH_FAILED(400). Код в ответ не попадает ни в одной ветке.
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Пустой/длинный/невалидный phoneNumber | Список ошибок валидации | no retry |
IDENTITY_USER_NOT_FOUND | 400 | Номер не зарегистрирован | «Пользователь не найден» | no retry (перейти к регистрации) |
IDENTITY_USER_BLOCKED | 400 | Status ∈ {Blocked, Archived} | «Учётная запись заблокирована» | no retry |
IDENTITY_USER_LOCKED_OUT | 400 | LockoutUntil > now (INV-8) | «Вход временно заблокирован, повторите позже» | retry после LockoutUntil |
IDENTITY_CONFIRMATION_RATE_LIMITED | 429 | Превышен лимит СМС (INV-S4) | «Слишком много запросов кода» | retry ≥ 60 сек |
IDENTITY_SMS_DISPATCH_FAILED | 400 | Отказ verifier после retry | «Не удалось отправить код» | retry |
Совместимость и наблюдаемость
- Новые optional поля — без breaking change; неизвестные значения
SignInNextStepклиент трактует как форму СМС-кода (safe default). - Logs: маскированный номер,
nextStep,requestId; metrics:identity_sign_in_code_requests_total(labelnext_step),identity_sms_dispatch_failures_total; traces — OTel.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (4): Identity API — карта областей и реестр investor-области, POST /api/investors/auth/confirm-sign-in — Подтверждение входа (confirmSignIn), POST /api/investors/auth/sign-up-or-sign-in — Войти или зарегистрироваться (signUpOrSignIn), POST /api/investors/auth/verify-sign-in-password — Проверка пароля при входе (verifySignInPassword)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: user-session.md (INV-S1..S5), user.md (INV-8).
- Следующие шаги flow: confirm-sign-in.md, verify-sign-in-password.md.