POST /api/investors/auth/sign-up — Регистрация инвестора (signUp)
Назначение
Явная регистрация нового инвестора: телефон + имя, отправка СМС-кода подтверждения. Вызывается анонимным
клиентом. В отличие от sign-up-or-sign-in.md (AddOrGet) занятый номер — ошибка
(паттерн DUPLICATE_PHONE), а firstName обязателен. Вход завершает
confirm-sign-in.md по выданному signInConfirmationRequestId.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | identity |
| API area | public |
| Feature group | auth |
| Статус | approved |
| Документ | docs/06-services/identity/api/investor/sign-up.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | Anonymous |
| Auth policy / Permissions | none |
| Scope (RBAC) | не применяется |
| Record-level | связка по PhoneNumber |
| Tenant isolation | не применяется — создаётся инвестор (CompanyId = null, ADR-0052) |
| Audit | create: RegisteredVia = SmsSignUp, CreatedByUserId = null |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/investors/auth/sign-up |
| Success status | 200 |
| Idempotency header | не применяется; повтор с тем же номером → IDENTITY_USER_PHONE_TAKEN |
| Correlation | сквозной trace через traceparent |
Входные данные / параметры запроса (body SignUpForm)
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
phoneNumber | body | string | Да | min 1, max 32; E.164; уникальность (INV-1) | PII, маскирование |
firstName | body | string | Да | min 1, max 128 | PII |
IpAddress / UserAgent | middleware | string? | Нет | ≤ 45 / ≤ 512 | rate-limit / анти-фрод |
Реферальная привязка
referrerUrl в форме SignUpForm отсутствует — реферальная привязка при этом пути передаётся полем
referrerUrl последующего confirm-sign-in.md либо через
signUpOrSignIn.
Модель ответа SignUpCommandResult
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
signInConfirmationRequestId | string (Guid) | Да | SignInConfirmationRequest.Id | — | Передаётся в confirm-sign-in. |
realConfirmationCodeDispatched | bool | Да | verifier | — | false — код реально не отправлялся (тестовый контур). |
Алгоритм
- Контекст и доступ. Актор анонимный; из middleware известны
IpAddress/UserAgent. - Проверка входа и лимиты.
phoneNumberнепустой ≤ 32,firstNameнепустой ≤ 128 — иначеValidationError; нормализация E.164. Rate-limit (INV-S4) — превышение ⇒IDENTITY_CONFIRMATION_RATE_LIMITED(429). Уникальность: номер уже зарегистрирован ⇒IDENTITY_USER_PHONE_TAKEN(паттерн DUPLICATE_PHONE; клиент предлагает вход). - Что читаем. Только проверка дубля номера; других сущностей не требуется.
- Правила и маппинг. Создаётся инвестор —
Type = Investor,CompanyId = null(INV-2),Status = Active,Name = FullName(firstName, lastName: ""),PhoneNumber = нормализованный,RegisteredVia = SmsSignUp,ContactsManagedBy = SelfService,SyncSource = Pin; фиксируется доменное событиеUserRegisteredEvent. Сгенерировать код (CSPRNG); создать запрос подтверждения (цельSignIn,UserId,PhoneNumber = нормализованный, хэш кода (HMAC-SHA256),ExpiresAt = now + 5 мин,IpAddress,UserAgent) + доменноеConfirmationCodeRequestedEvent. - Что меняется. В одном согласованном изменении создаются пользователь и запрос подтверждения
(гашение прежних не требуется — номер новый; при гонке действует INV-S1 на шаге confirm). Гонка двойной
регистрации отсекается ограничением уникальности номера →
IDENTITY_USER_PHONE_TAKEN. - События.
UserUpsertedKafkaEvent→pin.identity.users.v1(ключ =UserId;Id,Type = Investor,CompanyId = null,Status,Name,PhoneNumberв E.164 — доверенные потребители,RegisteredVia = SmsSignUp,ReferrerUrl = null,Version). ДоменноеConfirmationCodeRequestedEvent— отправка СМС через verifier. - Результат.
signInConfirmationRequestId,realConfirmationCodeDispatched; токен не выдаётся — его вернётconfirm-sign-in; HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Интеграционные события | UserUpsertedKafkaEvent / pin.identity.users.v1 / ключ = UserId (потребители: referral, profile, crm, messenger, user-activity, notifications) |
| Внешние вызовы | СМС-verifier (timeout 5 с, 1 retry) |
| Проекции / поиск | rate-limit-счётчик |
| Уведомления | СМС с кодом |
Кросс-сервисный контракт и окно согласованности
- Предоставляет:
UserUpsertedKafkaEventв топикpin.identity.users.v1,ContractVersion = 1, ключ =UserId; снапшот{Id, Type = Investor, CompanyId = null, Status, Name, PhoneNumber (E.164), RegisteredVia = SmsSignUp, ReferrerUrl = null, Version}. Потребители:referral,profile,crm,messenger,user-activity,notifications. В отличие отsignUpOrSignInреферальная привязка здесь не передаётся (поляreferrerUrlв форме нет) — она приходит позже черезconfirmSignIn.referrerUrl. - Ожидает от других: ничего синхронно, кроме внешнего СМС-verifier (timeout 5 с, 1 retry).
- Окно согласованности: учётка доступна в identity сразу; профиль, контакт, связывание anonymous-сессии в downstream — асинхронно (лаг — секунды). Токен на этом шаге не выдаётся.
Предусловия и постусловия
- Пред: актор анонимный;
phoneNumber(E.164) свободен (INV-1);firstNameнепустой ≤ 128; лимиты INV-S4 не превышены. - Пост (успех): создан инвестор (
Type = Investor,CompanyId = null,Status = Active,RegisteredVia = SmsSignUp,ContactsManagedBy = SelfService,SyncSource = Pin); зафиксированUserRegisteredEvent; опубликованUserUpsertedKafkaEvent; созданPending-запросSignIn; отправлен код. Вход завершит confirm-sign-in.md.
Acceptance Criteria
- Given свободный номер и непустой
firstName, WhenPOST /sign-up, Then200{signInConfirmationRequestId, realConfirmationCodeDispatched}, создан инвестор, опубликованUserUpsertedKafkaEvent(key =UserId,ReferrerUrl = null), отправлен СМС-код. - Given номер уже зарегистрирован, When
POST /sign-up, ThenIDENTITY_USER_PHONE_TAKEN(400, паттерн DUPLICATE_PHONE); клиент предлагает вход. (Осознанное отличие отsignUpOrSignIn, где занятый номер = вход.) - Given пустой/слишком длинный
firstNameили невалидный номер, WhenPOST /sign-up, ThenValidationError(400). - Given два параллельных
sign-upна один свободный номер, When оба доходят до создания, Then уникальность номера пропускает одного, второй получаетIDENTITY_USER_PHONE_TAKEN. - Given превышен лимит СМС (INV-S4), When
POST /sign-up, ThenIDENTITY_CONFIRMATION_RATE_LIMITED(429). - Given verifier отказал, When
POST /sign-up, ThenIDENTITY_SMS_DISPATCH_FAILED(400).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
ValidationError | 400 | Пустые/длинные поля | Список ошибок валидации | no retry |
IDENTITY_USER_PHONE_TAKEN | 400 | Номер уже зарегистрирован (INV-1; паттерн DUPLICATE_PHONE) | «Номер уже используется — войдите» | no retry (перейти ко входу) |
IDENTITY_CONFIRMATION_RATE_LIMITED | 429 | INV-S4 | «Слишком много запросов кода» | retry ≥ 60 сек |
IDENTITY_SMS_DISPATCH_FAILED | 400 | Отказ verifier | «Не удалось отправить код» | retry |
Совместимость и наблюдаемость
- Новые optional поля — additive.
- Logs: маскированный номер, результат; metrics:
identity_sign_ups_total{result},identity_duplicate_phone_total; traces — OTel.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (2): Identity API — карта областей и реестр investor-области, POST /api/investors/auth/confirm-sign-in — Подтверждение входа (confirmSignIn)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: user.md (INV-1/2,
RegistrationSource = SmsSignUp), user-session.md. - Продолжение: confirm-sign-in.md; альтернативный путь — sign-up-or-sign-in.md.