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

Аутентификация

Канонический DDL находится в ../database-schema.md. Feature-документ описывает поведение и может использовать TypeScript/conceptual shapes; SQL в этом документе не используется для миграций.

Зачем нужно

Документ фиксирует целевую архитектуру аутентификации, сессий, JWT, auth flow и методов входа.

1. Ядро и плагины

1.1. Что встроено в ядро

Базовая аутентификация, которая работает без плагинов:

  • Регистрация по email или phone + verified authentication method; local password включён в базовой воронке, но не обязателен для OAuth/passwordless-only сценариев
  • Вход по email или phone + пароль
  • JWT (access + refresh tokens), ротация refresh
  • Сессии и устройства
  • Logout
  • Сброс пароля

1.2. Что расширяется плагинами

  • auth_method — дополнительные способы входа (email OTP, SMS OTP, TOTP). Могут заменить пароль в воронке входа и в отдельных passwordless/OAuth-only регистрационных сценариях.
  • oauth_provider — вход через сторонние системы (Google, GitHub, Telegram). Привязываются к существующему аккаунту.

1.3. Правило пароля

КонтекстПароль
РегистрацияТребуется хотя бы один verified authentication method. Password credential обязателен только если для пользователя включён local password login.
ВходНастраиваемо. Админ может убрать пароль из воронки входа (например, вход только по SMS OTP).
АккаунтУ каждого пользователя должен быть хотя бы один verified authentication method. Password credential optional для OAuth-only, passwordless, child/student_profile без user account и federation/SSO сценариев.

1.4. Один аккаунт — много методов входа

Один пользователь = один аккаунт. К аккаунту привязываются любые методы входа:

  • Email + пароль (базовый local login)
  • Phone + пароль
  • OAuth-провайдеры (Google, GitHub, Telegram, ...)
  • Auth-методы из плагинов (TOTP, SMS OTP, ...)

Автопривязка OAuth: если пользователь входит через OAuth и email из OAuth совпадает с существующим аккаунтом:

  • Провайдер передаёт email_verified: true → система автоматически привязывает OAuth к этому аккаунту
  • Провайдер не передаёт email_verified или передаёт false → система показывает экран подтверждения и привязывает только после явного согласия пользователя

Управление привязанными методами — в разделе "Безопасность" профиля.

1.5. Auth flow (воронка)

Админ конфигурирует цепочку шагов для входа и регистрации:

  • Базовая воронка — email/phone + пароль (работает из коробки)
  • Расширенная — админ добавляет шаги (подтверждение email кодом, согласие с условиями, ...)
  • Полная замена — админ заменяет базовую воронку целиком (вход только по SMS OTP)
  • Пользовательские шаги — пользователь может подключить себе дополнительную защиту (TOTP)

Состояние между шагами хранится на сервере (Redis). Каждый шаг — отдельный экран.


2. JWT и токены

2.1. Access token

  • TTL: 15 минут
  • Формат: ecosystem JWT access token, RS256, проверяется через JWKS identity. HS256/JWT_SECRET допустим только для internal identity-only session artifact и не принимается другими доменами.
  • Payload minimal claims: sub, iss, aud, exp, iat, tokenId, sessionId, auth_method, permission_version или bounded permissions summary. PII such as email belongs to ID token/userinfo, not required API access token.
  • Роли и permissions загружаются из БД при каждой генерации summary/version.
  • super_admin автоматически получает wildcard * в permissions

2.2. Refresh token

  • TTL: 30 дней
  • Формат: UUID, хранится в БД (таблица refresh_tokens)
  • Хранение на клиенте: httpOnly cookie systematika_id_refresh_token (secure, sameSite: strict)
  • Ротация: при refresh старый токен отзывается (isRevoked: true), создаётся новый
  • Повторное использование отозванного токена: логируется как потенциальная атака

2.3. Генерация и ротация

Login / Register
→ генерация refresh token (UUID) → сохранение в БД
→ генерация access token (JWT) с payload из БД
→ refresh token в httpOnly cookie, access token в response body

Access token истёк (401)
→ фронтенд отправляет refresh запрос (cookie автоматически)
→ backend: pessimistic_write транзакция
→ старый refresh token → isRevoked: true
→ новый refresh token → сохранение в БД
→ новый access token с актуальными ролями из БД
→ новый refresh token в httpOnly cookie

Refresh token истёк (30 дней)
→ пользователь должен залогиниться заново

3. Сессии и saved accounts

3.1. Сессии

Сессия создаётся при каждом успешном входе. Хранится в БД.

Поля сессии:

  • userId — владелец
  • refreshTokenId — привязка к refresh token
  • userAgent — строка user agent
  • ipAddress — IP при создании
  • browserName — название браузера
  • lastActivityAt — обновляется при refresh token
  • expiresAt — 30 дней (совпадает с TTL refresh token)
  • isRevoked — отозвана ли

Операции:

  • Просмотр активных сессий — раздел "Безопасность" в профиле
  • Отзыв конкретной сессии — инвалидирует refresh token
  • Отзыв всех сессий кроме текущей
  • Автоматическая очистка истёкших сессий (cron)

Сессия идентифицируется по refresh token. device_binding используется для saved accounts, child device authorizations и risk checks, но не заменяет refresh token.

3.2. Logout flow

POST /api/v2/identity/auth/logout (с httpOnly cookie)
→ извлечь refresh token из cookie
→ найти сессию по refreshTokenId
→ отозвать refresh token (isRevoked: true)
→ отозвать сессию (isRevoked: true)
→ очистить cookie systematika_id_refresh_token (Set-Cookie с maxAge=0)
→ вернуть 200

Фронтенд после 200:
→ сбросить access token из памяти
→ BroadcastChannel: { type: 'logout' } → все вкладки сбрасывают auth state
→ редирект на страницу входа
→ `saved_accounts` остаётся активным для быстрого повторного входа

Logout all (кроме текущей):

POST /api/v2/identity/security/sessions/revoke-all (JWT)
→ определить текущую сессию по tokenId из access token
→ отозвать все остальные сессии пользователя
→ отозвать все refresh tokens кроме текущего
→ вернуть { revokedCount }

Другие вкладки того же пользователя потеряют доступ при следующем refresh (access token истечёт → refresh вернёт 401).

3.3. Saved accounts

Сохранённые аккаунты хранятся в серверной таблице saved_accounts и привязаны к device_bindings. Клиент может кешировать только отображаемый snapshot, но source of truth остаётся на сервере.

Что хранится:

  • email / phone — для отображения
  • firstName, lastName — имя
  • avatarUrl — аватарка
  • lastUsedAt — когда последний раз входили

Поведение:

  • При успешном входе — аккаунт добавляется/обновляется в saved_accounts
  • При logout — аккаунт остаётся в списке (для быстрого повторного входа)
  • Пользователь может удалить аккаунт из списка; сервер выставляет revoked_at
  • При очистке браузера локальный snapshot пропадает, но серверная запись может быть восстановлена по доверенному device_binding
  • Пароль не хранится — при quick login пользователь вводит пароль

4. Auth flow — детали воронки

4.1. Два уровня конфигурации

Обязательная воронка (настраивает админ): Цепочка шагов, которые проходят все пользователи. Каждый шаг имеет флаг mandatory:

mandatoryЗначение
trueПользователь не может убрать или изменить этот шаг
falseПользователь может отключить этот шаг в настройках безопасности

Пример воронки входа:

1. email/phone (mandatory: true) — пользователь не может убрать
2. password или OTP (mandatory: true) — зависит от configured auth policy
3. TOTP (mandatory: false) — пользователь может отключить

Разрешённые дополнительные методы (настраивает админ, включает пользователь): Список auth_method плагинов, которые пользователь может добавить к своей воронке по желанию через настройки безопасности.

Пример: админ разрешил TOTP и SMS OTP. Пользователь в "Безопасности" включил TOTP → при входе после обязательных шагов появляется шаг "введи код из приложения".

4.2. Итоговая воронка пользователя

[Обязательные шаги (mandatory: true)]
+ [Необязательные шаги из воронки, которые пользователь не отключил]
+ [Дополнительные методы, которые пользователь включил сам]

4.3. Принудительная настройка

Если админ добавил в воронку обязательный шаг (например, TOTP с mandatory: true), а у пользователя он ещё не настроен:

Пользователь входит
→ проходит настроенные шаги (email + password/OTP/OAuth)
→ система видит: обязательный шаг TOTP не настроен
→ принудительно запускает воронку настройки TOTP
→ после настройки — вход завершён

При следующих входах TOTP уже настроен → просто запрашивается код.

4.4. Конфигурация воронки в БД

interface AuthFlowConfig {
login: AuthFlowStep[] // шаги входа
registration: AuthFlowStep[] // шаги регистрации
allowedMethods: string[] // slug'и разрешённых дополнительных методов
}

interface AuthFlowStep {
stepId: string // ID шага (из плагина или встроенный)
mandatory: boolean // может ли пользователь отключить
order: number // порядок в цепочке
}

4.5. Пользовательские методы входа

Canonical storage is user_auth_methods in ../database-schema.md. Conceptual shape:

type UserAuthMethod = {
id: string;
userId: string;
type: 'password' | 'email_otp' | 'sms_otp' | 'totp' | 'oauth_provider';
provider?: string;
externalSubject?: string;
settings: Record<string, unknown>;
isEnabled: boolean;
isVerified: boolean;
lastUsedAt?: string;
};

Примеры:

  • Пользователь отключил пароль при входе: { type: "password", isEnabled: false }
  • Пользователь включил TOTP: { type: "totp", isEnabled: true, settings: { secretRef: "encrypted:..." } }

Ограничение: хотя бы один метод входа должен быть активен. Нельзя отключить всё.

Важно: поле enabled управляет доступностью метода при входе. Для метода password: пароль можно отключить как шаг входа (enabled: false), и password credential может отсутствовать у OAuth-only/passwordless/SSO пользователей. Инвариант ядра — не пароль у каждого пользователя, а хотя бы один verified authentication method.

4.6. Серверная сессия auth flow

Состояние между шагами хранится в Redis:

interface AuthFlowSession {
sessionId: string // одноразовый ID сессии
flow: 'login' | 'registration'
completedSteps: string[] // какие шаги пройдены
pendingSteps: string[] // какие шаги ещё впереди
tempData: Record<string, unknown> // данные между шагами
userId?: string // заполняется после идентификации
ttl: number // время жизни сессии (5 минут)
}

4.7. Один механизм входа

Login/register и multi-auth работают через единый auth flow:

  • Простой вход (email + пароль) — auth flow с двумя шагами
  • 2FA — дополнительный шаг в воронке (не отдельный TwoFactorService)
  • nFA — несколько дополнительных шагов (не отдельный NfaService)
  • OAuth — шаг в воронке или отдельная кнопка на экране входа

5. OAuth Authorization Server

Systematika ID — центральный identity provider экосистемы. Все внешние сервисы подключаются к нему через OAuth 2.0 / OIDC.

Эта подсистема вынесена в отдельный документ: oauth-server.md


6. Сброс пароля и верификация контактов

6.1. Принцип работы с транспортом

Ядро не знает КАК отправить сообщение — только ЧТО. Все коммуникации (коды, ссылки) отправляются через TransportRegistry, который маршрутизирует в подключённый плагин transport (email SMTP, SMS, push, Telegram и т.д.).

Ядро: "отправь код 847291 на user@mail.ru"
→ TransportRegistry: какой транспорт умеет email?
→ email-transport плагин: отправка через SMTP

6.2. Верификационные коды

Единая таблица verification_codes для всех целей:

ПолеЗначение
identifieremail или phone получателя
code6 символов, crypto.randomInt
purposecontact_verify, login, reset_password, sso_otc
statuspendingused / expired
attemptsсчётчик неудачных попыток
maxAttemptsлимит (5)
expiresAtTTL 10 минут

6.3. Два пути смены пароля

Путь 1 — "Забыл пароль" (без авторизации):

Экран входа → "Забыли пароль?"
→ ввод email или phone
→ выбор транспорта (если несколько доступны)
→ отправка 6-значного кода через транспорт
→ подтверждение: ввод кода вручную ИЛИ клик по ссылке
(ссылка = тот же код в URL: /reset-password?code=847291)
→ ввод нового пароля + подтверждение
→ пароль обновлён

Путь 2 — Настройки безопасности (авторизован):

Безопасность → "Сменить пароль"
→ ввод старого пароля + нового + подтверждение
→ пароль обновлён

ИЛИ

Безопасность → "Не помню текущий пароль"
→ тот же поток что Путь 1 (код/ссылка через транспорт)

6.4. Ссылка = код в URL

Ссылка для сброса — это тот же 6-значный код, зашитый в URL. Один механизм для обоих случаев:

  • Ввод вручную: пользователь получает код 847291, вводит на экране
  • Клик по ссылке: пользователь получает ссылку https://id.example.com/reset-password?code=847291, кликает → фронтенд извлекает код из URL и подставляет автоматически

Защита: TTL 10 минут + лимит 5 попыток + rate limit на отправку.

6.5. Верификация контактов

Подтверждение email/phone работает по тому же механизму:

Пользователь добавляет email/phone в профиле
→ отправка кода через транспорт
→ ввод кода или клик по ссылке
→ контакт подтверждён

6.6. Rate limiting на отправку кодов

ОграничениеЗначение
Отправка кода на один identifierМакс. 3 запроса за 10 минут
Отправка кода с одного IPМакс. 10 запросов за 10 минут
Cooldown между повторными отправками60 секунд

При превышении — 429 Too Many Requests с Retry-After заголовком.


6a. Rate limiting и brute-force защита

6a.1. Rate limiting на эндпоинтах

ЭндпоинтЛимитКлюч
POST /api/v2/identity/auth/flow/start10/минIP
POST /api/v2/identity/auth/flow/step10/минIP + sessionId
POST /api/v2/identity/auth/password/forgot5/часIP + identifier
POST /api/v2/identity/auth/password/verify-code10/минIP
POST /api/v2/identity/auth/password/reset5/часIP
POST /api/v2/identity/auth/refresh30/минIP + userId
POST /api/v2/identity/auth/verify/send3/10минIP + identifier

Реализация: middleware на уровне API-шлюза (Redis-backed counter, @nestjs/throttler или аналог).

6a.2. Brute-force защита на входе

Account lockout — после N неудачных попыток входа для одного аккаунта:

Неудачных попытокДействие
5 подрядЗадержка 30 секунд перед следующей попыткой
10 подрядЗадержка 5 минут
20 подрядАккаунт временно заблокирован на 30 минут

Счётчик сбрасывается при успешном входе. Хранится в Redis (auth:lockout:{userId}).

IP-based throttling — после N неудачных попыток входа с одного IP (любые аккаунты):

Неудачных попытокДействие
20 за 10 минутЗадержка 1 минута для всех запросов с IP
50 за 10 минутБлокировка IP на 30 минут

Хранится в Redis (auth:ip-throttle:{ip}).

Логирование: все неудачные попытки записываются в audit_logs (identity.login.failed) с IP, userAgent, identifier.

6a.3. Brute-force защита на кодах

Verification codes имеют встроенную защиту:

  • maxAttempts: 5 — после 5 неверных вводов код инвалидируется
  • TTL: 10 минут — код протухает
  • Rate limit на отправку (см. 6.6)

После инвалидации кода пользователь должен запросить новый (с учётом rate limit на отправку).


7. Вынесенные домены

Следующие подсистемы НЕ входят в identity auth subsystem:

ПодсистемаКуда вынесенаСвязь с auth
РефералыusersПри регистрации проверяется реферальный код
ПриглашенияorganizationsПри принятии приглашения может потребоваться регистрация
OAuth Authorization Serveroauth-serverИспользует auth для аутентификации пользователя
Inbound SSO (OTC)oauth-serverОдноразовые коды для SSO — часть OAuth AS

7.1. External Identities

OAuth-привязки к аккаунту пользователя. Живёт в identity auth subsystemе.

Canonical storage is user_auth_methods with type = oauth_provider, provider, external_subject, settings, is_enabled, is_verified.

Автопривязка при OAuth-входе: система проверяет все контакты из OAuth userInfo (email → user_emails, phone → user_phones, provider-specific ID → user_auth_methods). Если совпадение найдено → привязывает к существующему аккаунту. Если нет → создаёт новый аккаунт с OAuth method как verified authentication method; установка local password опциональна и зависит от auth policy.

Привязка по email выполняется только при email_verified: true от провайдера. Если провайдер не передаёт это поле — привязка требует ручного подтверждения пользователем (см. раздел 1.4).

7.2. Auth Flow Config — хранение

Конфигурация воронки хранится в canonical auth_flow_steps из ../database-schema.md. Разрешённые дополнительные методы — platform-owned system settings, hosted by identity-api only during bootstrap. Identity auth reads them through the platform settings abstraction; they are not identity-owned tables.

7.3. MFA — определяется через user_auth_methods

MFA не хранится в отдельной таблице. Пользователь считается MFA-protected, если в user_auth_methods есть хотя бы один дополнительный активный метод помимо пароля (например, type: 'totp', isEnabled: true).

Нет отдельных полей mfa_enabled / mfa_method — это выводимое состояние:

// MFA включён, если есть enabled метод кроме password
const hasMfa = userAuthMethods.some(m => m.type !== 'password' && m.isEnabled);

Источник истины — таблица user_auth_methods (см. раздел 4.5).


8. Серверные сервисы

8.1. Целевая структура

apps/identity-api/src/auth/
├── auth.module.ts

├── flow/
│ ├── auth-flow.service.ts # Оркестратор воронки: start, advance, complete
│ └── auth-flow-session.service.ts # Redis-хранилище сессий auth flow

├── tokens/
│ ├── auth-token.service.ts # Генерация/ротация/отзыв access + refresh
│ └── token-cleanup.service.ts # Cron: очистка истёкших токенов и сессий

├── sessions/
│ └── session.service.ts # CRUD сессий в БД

├── password/
│ └── password.service.ts # Смена пароля (оба пути), хеширование; работает с таблицей user_credentials (не users.passwordHash)

├── verification/
│ └── verification-code.service.ts # Генерация, отправка, проверка 6-значных кодов

└── entities/
├── refresh-token.entity.ts
├── session.entity.ts
├── user-credentials.entity.ts
└── verification-code.entity.ts

8.2. Ответственности

СервисОтветственностьЗависимости
AuthFlowServiceОркестратор воронки: сборка шагов (обязательные + пользовательские), start/advance/complete, принудительная настройка ненастроенных обязательных шаговAuthFlowSessionService, AuthTokenService, SessionService, PluginRegistries, PasswordService
AuthFlowSessionServiceRedis CRUD для одноразовых сессий auth flow (tempData, completedSteps, pendingSteps). TTL 5 минутRedis
AuthTokenServiceГенерация access token (JWT), генерация/ротация/отзыв refresh token, проверка повторного использованияSessionService, JwtService
SessionServiceCRUD сессий в БД, отзыв конкретной/всех, обновление lastActivityAtPrisma repository
PasswordServiceХеширование (bcrypt), проверка пароля, смена (старый+новый), сброс через код; хранит хэши в таблице user_credentials (отдельная таблица, не поле users.passwordHash)VerificationCodeService
VerificationCodeServiceГенерация 6-значного кода, сохранение в БД, отправка через TransportRegistry, проверка (attempts, TTL)TransportRegistry, Prisma repository
TokenCleanupServiceCron (каждый час): удаление истёкших refresh tokens, сессий, verification codesPrisma repository

8.3. Граф зависимостей

AuthFlowService
├── AuthFlowSessionService (Redis)
├── AuthTokenService
│ └── SessionService
├── PasswordService
│ └── VerificationCodeService
│ └── TransportRegistry (плагины)
├── AuthMethodRegistry (плагины)
└── OAuthProviderRegistry (плагины)

TokenCleanupService (cron, независимый)

Нет циклических зависимостей.


9. API-эндпоинты

9.1. Auth flow (публичные)

HTTPПутьНазначение
POST/api/v2/identity/auth/flow/startНачать воронку (login/registration)
POST/api/v2/identity/auth/flow/stepОбработать шаг воронки (rate limit: 10/мин)
GET/api/v2/identity/auth/flow/configПубличная конфигурация воронки
POST/api/v2/identity/auth/refreshОбновить access token
POST/api/v2/identity/auth/logoutВыход
GET/api/v2/identity/auth/meПрофиль текущего пользователя

9.2. Пароль

HTTPПутьAuthНазначение
POST/api/v2/identity/auth/password/forgotпубличныйОтправить код сброса
POST/api/v2/identity/auth/password/verify-codeпубличныйПроверить код
POST/api/v2/identity/auth/password/resetпубличныйУстановить новый пароль
POST/api/v2/identity/auth/password/changeJWTСменить пароль (старый + новый)

9.3. Сессии (JWT)

HTTPПутьНазначение
GET/api/v2/identity/security/sessionsСписок активных сессий
DELETE/api/v2/identity/security/sessions/:idОтозвать сессию
POST/api/v2/identity/security/sessions/revoke-allОтозвать все кроме текущей

9.4. Привязанные методы входа (JWT)

HTTPПутьНазначение
GET/api/v2/identity/security/auth-methodsСписок привязанных методов
POST/api/v2/identity/security/auth-methods/:type/setupНачать настройку метода
POST/api/v2/identity/security/auth-methods/:type/verifyПодтвердить настройку
DELETE/api/v2/identity/security/auth-methods/:typeОтвязать метод

9.5. Верификация контактов (JWT)

HTTPПутьНазначение
POST/api/v2/identity/auth/verify/sendОтправить код подтверждения email/phone
POST/api/v2/identity/auth/verify/confirmПодтвердить код

10. Фронтенд

10.1. Страницы

СтраницаПутьНазначение
AuthPage/:lang/authВход и регистрация (единая страница, auth flow)
ResetPasswordPage/:lang/reset-passwordСброс пароля (+ обработка ?code= из ссылки)

AuthPageV2 переименовывается в AuthPage. RegisterPage удаляется.

10.2. StepRenderer — декомпозиция

modules/auth/components/
├── StepRenderer.tsx — оркестратор: определяет тип шага, рендерит нужный
├── steps/
│ ├── ContactStep.tsx — ввод email/phone (встроенный)
│ ├── PasswordStep.tsx — ввод пароля (встроенный)
│ ├── PluginStep.tsx — рендер плагинного шага (iframe или template)
│ ├── OAuthStep.tsx — кнопки OAuth-провайдеров
│ ├── ConsentStep.tsx — согласие с условиями
│ └── SetupStep.tsx — принудительная настройка (TOTP setup и т.п.)
├── PluginIframe.tsx — iframe + postMessage протокол
├── GroupStepRenderer.tsx — несколько шагов на одном экране
└── AuthStepTemplate.tsx — generic рендер по полям из schema

Встроенные шаги: ContactStep, PasswordStep, ConsentStep. Остальные — плагинные или через AuthStepTemplate.

Хардкоженные BUILT_IN_STEPS (inn, snils, birthdate, gender, name, surname) — убраны. Эти шаги конфигурируются в воронке как поля регистрации, рендерятся через AuthStepTemplate.

GitHub, Telegram — OAuth-провайдеры, рендерятся через OAuthStep.

10.3. Saved accounts

Компонент SavedAccountsList читает безопасный список через identity API. Quick login: заполняет email/phone, пользователь вводит пароль → обычный auth flow.

10.4. Auth state на фронтенде

isAuthenticated — синхронное, вычисляемое значение. Не useQuery, не async. Выводится из наличия access token в памяти (или его наличия в store):

// Синхронно — не useQuery
const isAuthenticated = Boolean(getAccessToken());

Cross-tab синхронизация — использовать BroadcastChannel, не storage event:

// Правильно: BroadcastChannel
const channel = new BroadcastChannel('auth');
channel.postMessage({ type: 'logout' });
channel.onmessage = (e) => { if (e.data.type === 'logout') logout(); };

// Неправильно: storage event ненадёжен для этой цели

При logout в одной вкладке все остальные вкладки получают сообщение через BroadcastChannel и синхронно сбрасывают auth state.


11. UX/UI

11.1. Auth flow — визуальная структура

Auth flow рендерится внутри AuthLayout — полноэкранный центрированный лейаут:

  • Фон: bg-bg-primary (адаптируется к теме)
  • Карточка: max-w-sm, rounded-2xl, border border-border, bg-bg-secondary, shadow-sm
  • Шапка карточки: кнопка "Назад" (абсолютно слева) + логотип по центру
  • Тело карточки: контент текущего шага (px-8 pb-8 pt-4)
  • Под карточкой: переключатель языка (глобус) + переключатель темы (солнце/луна)
  • Mobile: карточка без рамки и тени, полная ширина с px-4

11.2. Шаги auth flow

StepRenderer отображает шаги последовательно. Каждый шаг — отдельный экран внутри карточки.

Встроенные шаги:

ШагЧто видит пользователь
phone-emailПоле ввода "Телефон или email". Автоопределение формата. При обнаружении нескольких аккаунтов — переход к AccountSelectionStep
passwordПоле пароля. Ссылка "Забыли пароль?" → ForgotPasswordModal. При регистрации — поле подтверждения + индикатор сложности
otp-codeВвод одноразового кода
oauthРедирект на внешний provider
consentДва чекбокса: согласие с условиями использования и политикой конфиденциальности (ссылки на /terms и /privacy)
mfa-setupНастройка второго фактора
account-selectionВыбор аккаунта при нескольких совпадениях
qrQR-код для сканирования

Profile/KYC fields (first-name, last-name, inn, snils, birthdate, gender, passport) are not built-in auth steps. They are registration profile fields rendered by AuthStepTemplate from user_field_definitions or owned by CRM/KYC flows.

Плагинные шаги (dynamic):

Шаги, не входящие в BUILT_IN_STEPS, рендерятся через один из трёх механизмов:

  1. PluginIframe — sandboxed iframe с postMessage API (тема, инициализация, отправка)
  2. dangerouslySetInnerHTML — HTML-контент от плагина (DOMPurify санитизация)
  3. AuthStepTemplate — стандартная схема полей (title, description, fields[])

Групповые шаги:

GroupStepRenderer — несколько шагов на одном экране (вертикально). Каждое поле рендерится через StepFieldInline. Одна кнопка "Продолжить" внизу.

11.3. Saved accounts

При наличии сохранённых аккаунтов для текущего device binding экран входа показывает SavedAccountsList:

  • Список аватаров с именами для быстрого входа
  • Нажатие на аккаунт → предзаполнение email/телефона → переход к шагу пароля
  • Кнопка "Другой аккаунт" внизу списка

11.4. Onboarding (пост-регистрация)

4-шаговый flow внутри AuthLayout:

ШагСодержимоеПропуск
1Имя + Фамилиянет
2Загрузка аватара с превьюда (кнопка "Пропустить")
3Языковые предпочтения (Switch)да
4Установка пароляда

11.5. Восстановление пароля

ForgotPasswordModal — модальное окно внутри auth flow:

  • Поле ввода email/телефона
  • Отправка кода/ссылки для сброса
  • ResetPasswordPage — отдельная страница для ввода нового пароля

11.6. OAuth / SSO экраны

  • SingleLogoutPage — обработчик RP-Initiated Logout (OIDC). Показывает спиннер во время выполнения logout, затем редирект.
  • InboundLoginPage — бесшовный вход по токену от внешнего сервиса (callback handler на /inbound-login.html)
  • При наличии OAuth-параметров в URL (client_id, redirect_uri) на лендинге — автоматический переход в OAuth flow

11.7. Состояния и обратная связь

  • Loading: спиннер на кнопке "Продолжить" (disabled + spinner)
  • Ошибка валидации: красный текст под полем (ErrorMessage), aria-invalid на инпуте
  • Ошибка сервера: toast с сообщением
  • Brute-force блокировка: toast + таймер обратного отсчёта до разблокировки
  • Код верификации: CodeInput (6 отдельных боксов для цифр) + ResendTimer (обратный отсчёт до повторной отправки)