38 KiB
Бизнес-постановка: Пользователь (User)
Статус: v1 — консолидация принятых решений из HAN_chat_specification (arch-00…arch-05, module-01, module-08); открытые вопросы зафиксированы в §17
Продукт: HAN Chat (клиентское приложение + api-backend + Keycloak)
Источники: архитектура HAN_chat_specification; макет Figma (не канон — только визуализация; при расхождении приоритет у этого ТЗ и arch-документов)
Связанный backlog: кнопка «Войти»; история устройств входа; дифференцированные ошибки OTP; debounce SMS; тестовый пользователь с фиксированным SMS-входом
Смежно: уведомления (контуры G/P), чат, согласия, UX-сессия, CRM Contact через bitrix-sync
Нормативная часть — §1–§16. §17 — ненормативный журнал решений и открытых вопросов; при расхождении с §1–§16 приоритет у §1–§16. При расхождении этого документа с arch/module после их обновления — приоритет у arch/module до синхронизации.
1. Цель
Дать клиенту устойчивую идентичность в HAN Chat: вход по подтверждённому телефону, локальную карточку пользователя в App DB, согласия, readonly-профиль в ЛК и связь с Contact в Bitrix24 — без смешения гостевого просмотра и персонального кабинета.
Гость изучает сервис без записи в App DB. Авторизованный клиент получает персональные данные, чат, персональные уведомления и профиль. Auth-идентичность принадлежит Keycloak; приложение владеет бизнес-карточкой пользователя, согласиями и кэшем профиля для UI.
2. Два режима клиента
| Режим | Кто это | Идентичность на бэкенде | Что доступно |
|---|---|---|---|
| G. Гость | Клиент без действующего JWT | Нет UserIdentity; опциональный локальный guest_session_id только на устройстве |
UI + GET /api/v1/public/*; гостевые уведомления (контур G) |
| A. Авторизованный | Клиент с валидным access token и выполненным bootstrap |
user_identities (keycloak_sub ↔ JWT sub) |
JWT API: чат, профиль, согласия, персональные уведомления, UX-сессия |
Правила:
- До успешного OTP +
bootstrapклиент — только гость. Write-endpoint (consents,session-start, чат, профиль и т.д.) требуют JWT. - После авторизации гостевой контент не переносится в персональный (как в уведомлениях: G не мигрирует в P).
guest_session_idне является auth и не открывает write API.- Наличие refresh token в secure storage позволяет вернуться в режим A без OTP (§6.3); отсутствие/истечение refresh → снова гость до следующего защищённого действия.
- Access token проверяет
api-backend; refresh выполняет только frontend через Keycloak. Backend refresh не делает.
3. Границы релиза
3.1. В scope
- OTP-only вход по номеру телефона (Keycloak; mock до controlled SMS cutover).
- OIDC Authorization Code + PKCE; refresh / logout; silent return без OTP при валидном refresh.
- Локальный
find-or-createUserIdentity+ минимальныйClientProfileчерезPOST /api/v1/auth/bootstrap. - Согласия: обязательные
personal_data+user_agreement, опциональныйmarketing; фиксация версий вuser_consents. - Readonly блочный профиль
GET /api/v1/me; зарезервированныйGET /api/v1/me/documents(MVP может быть пустым). - Аналитическая
UxSessionдля авторизованного клиента (session-start, заголовокX-Ux-Session-Id). - Асинхронный map/create Contact в Bitrix24 по телефону через
sync_queue(не блокирует вход). - Нормализация телефона в E.164; phone claim только из JWT, не из body клиента.
- Ownership: все пользовательские ресурсы адресуются через
user_idиз JWT.
3.2. Вне scope
- Пароль, email-OTP, social login, magic link.
- Редактирование профиля клиентом (PATCH/PUT отсутствуют).
- Доставка документов компании в блок «Документы» (post-MVP; API зарезервирован).
- История устройств входа (backlog п.19) — модель и UI позже.
- Смена телефона как продуктовый self-service flow (policy Keycloak есть; продуктовый UX — отдельно).
- Merge/reassignment двух
subна один телефон — только administrative policy, не side effect login. - Гостевая запись согласий и UX-сессии в App DB.
- Создание
UserIdentity/ClientProfileсервисомbitrix-sync(sync не участвует в OTP-flow).
4. Изменения UI (относительно гостевого экрана)
| Место | Гость | Авторизованный |
|---|---|---|
| Главная | Публичный контент, гостевые уведомления | Персональные уведомления, чат доступен |
| Отправка сообщения / популярный вопрос | Сначала согласия → OTP → bootstrap → отправка отложенного текста | Штатная отправка |
| Центр уведомлений | Auth-gate «Авторизоваться» | Список персональных |
| Профиль | Недоступен / ведёт на вход | Readonly блоки «Личные данные», «Документы» |
| Кнопка «Войти» | Запускает поток авторизации | Скрыта / заменена профилем (по макету) |
| Выход | — | Очистка tokens → гостевой UI |
Визуал — по Figma. Figma не канон поведения и состава полей профиля: канон — §5.5 и arch-01.
5. Бизнес-модель
5.1. Слои идентичности (не смешивать)
| Слой | Где живёт | Что хранит | Master |
|---|---|---|---|
| IdP user | Keycloak (keycloak schema) |
Realm user, phone verified, sessions, tokens | Keycloak |
| UserIdentity | App DB user_identities |
Локальный user_id, связь keycloak_sub, кэш auth-телефона, last_login_at |
Keycloak для телефона/sub; App DB для бизнес-FK |
| ClientProfile | App DB client_profiles |
Кэш полей UI без CRM-идентификаторов | UI-поля — последнее успешно синхронизированное значение (входящий поток MVP — Bitrix24); auth-телефон инициирует sync, но master телефона — Keycloak |
| Bitrix Contact | CRM Bitrix24 | Карточка клиента в CRM | Bitrix24 для CRM-полей; связь user_id ↔ b24_id хранится только в bitrix_sync.entity_external_mapping |
| Гость | Только устройство | UI-state, локальные согласия до OTP, опционально guest_session_id |
Нет серверной записи |
Инвариант: один verified phone ↔ один active Keycloak sub. Один sub ↔ одна active UserIdentity. Один user_id ↔ один ClientProfile.
5.2. Связанные сущности (часть домена «пользователь», но не сам User)
| Сущность | Роль | Когда появляется |
|---|---|---|
UserConsent |
Факт принятия документа конкретной версии | bootstrap или POST /consents |
UxSession |
Аналитический период активности | session-start только у авторизованного |
Dialog / Message |
Чат с оператором | После auth, лениво при первом сообщении |
| Notification (P) | Персональные уведомления | user_id NOT NULL |
UxSession не является механизмом авторизации и не заменяет JWT.
5.3. Согласия
| Тип | Обязательность (seed) | Документ / URL | Версия |
|---|---|---|---|
personal_data |
да (consent.personal_data.required=true) |
consent.personal_data.document_url (+ политика consent.privacy_policy.document_url) |
consent.personal_data.version |
user_agreement |
да | consent.user_agreement.document_url |
consent.user_agreement.version |
marketing |
нет | consent.marketing.document_url |
consent.marketing.version |
Правила:
- Pop-up согласий показывается до OTP; до получения JWT факт принятия хранится только на клиенте.
- Серверная фиксация — в
bootstrap(атомарно с созданием пользователя) или позже черезPOST /api/v1/consentsпри смене версий документов. - Запись
UserConsentimmutable: unique(user_id, consent_type, document_version); исправление — новая версия документа или administrative action с audit. - Обязательные согласия без
accepted: true→403 consents_required; вход в ЛК / write API с непринятыми актуальными обязательными версиями блокируется. - Keycloak consent screen не заменяет продуктовые согласия API.
5.4. Auth-телефон
- Единственный канал MVP: номер телефона + OTP.
- Нормализация: libphonenumber → canonical E.164.
- В App DB телефон пишется только из JWT claims при
bootstrap/ обновлении identity, никогда из body клиента. - Порядок claim:
phone_number, иначеpreferred_usernameтолько если значение валидно как E.164. - Отсутствие/невалидность при bootstrap →
400 phone_claim_missing. - Утечка существования номера запрещена на стороне Keycloak (одинаковый внешний ответ для нового/существующего).
- В
ClientProfileпри создании копируется вrussian_phone(минимальный профиль); дальнейшее обогащение — из CRM sync.
5.5. Профиль (UI)
Блочная модель. Редактирование клиентом недоступно.
Блок «Личные данные»:
| Поле | Источник отображения | Примечание |
|---|---|---|
ФИО (full_name) |
client_profiles |
Может быть null до sync из Bitrix24 |
Гражданство (citizenship) |
client_profiles |
null до заполнения |
Телефон РФ (russian_phone) |
client_profiles |
При bootstrap = auth-телефон |
Зарубежный телефон (foreign_phone) |
client_profiles |
Опционально |
Email (email) |
client_profiles |
Опционально |
Блок «Документы»:
- перечень документов компании, дата, наименование, скачивание;
- в MVP список может быть пустым; доставка из Bitrix24 — post-MVP;
- API:
GET /api/v1/me/documents,GET /api/v1/documents/{id},.../download-urlс audit.
Макет Figma может показывать дополнительные секции (патент, РВП и т.п.) — это не канон MVP-модели данных; расширение блоков — отдельное решение.
5.6. Жизненный цикл пользователя
| Состояние | Условие | Что видит клиент |
|---|---|---|
| Гость | Нет валидного JWT | Публичный UI |
| OTP in progress | Идёт challenge в Keycloak | Экраны телефона / кода |
| Authenticated, bootstrap pending | Есть JWT, нет local UserIdentity |
Frontend обязан вызвать bootstrap; прочие protected → 409 «bootstrap required» |
| Authenticated, ready | Есть UserIdentity + актуальные обязательные согласия |
Полный ЛК |
| Soft-deleted | record_status='D' на identity (админ) |
Доступ запрещён; детали — operational policy |
Бизнес-«удаление аккаунта» клиентом в MVP не моделируется. Soft-delete — административный контур (arch-05).
5.7. Константы (app_settings)
| Ключ | Смысл | Default / seed |
|---|---|---|
auth.phone.enabled |
Вход по телефону | true |
auth.password.enabled |
Пароль | false |
otp.phone.max_send_attempts_per_24h |
Лимит отправок OTP | 3 |
otp.phone.min_seconds_between_attempts |
Минимальный интервал между отправками | 30 |
otp.phone.max_verify_attempts |
Лимит проверок кода | 5 |
otp.phone.code_length |
Длина кода | 6 |
otp.phone.ttl_seconds |
TTL кода | 60 |
otp.phone.sms_order_timeout_ms |
Таймаут заказа SMS | 3000 |
consent.* |
URL/версии/required флагов согласий | см. arch-04 |
ux.session.idle_timeout_minutes |
Idle → новая UX-сессия | 30 |
Счётчики OTP ведёт Keycloak/SPI, не api-backend. Продуктовые otp.phone.* Keycloak читает через settings bridge GET /internal/settings/v1/otp.
6. Поведение UI и сценарии
6.1. Гостевой режим
- Клиент открывает приложение → гостевой UI.
- Доступен только
GET /api/v1/public/*(+ статика). - Согласия и
session-startв App DB не пишутся. - Попытка защищённого действия (сообщение, Центр уведомлений, профиль) → поток авторизации.
6.2. Поток первой авторизации (OTP)
- Триггер: отправка сообщения / популярный вопрос / «Войти» / иное действие, требующее auth.
- Pop-up согласий; обязательные должны быть приняты локально.
- Форма телефона → Keycloak OTP-flow (mock или real SMS через
sms-service). - Успешная проверка OTP → tokens (Authorization Code + PKCE).
POST /api/v1/auth/bootstrapс локальными согласиями иdevicemetadata.POST /api/v1/analytics/session-startпри необходимости новой UX-сессии.- Триггер БД ставит
contact.map_or_createвsync_queue(асинхронно; ошибка CRM не откатывает вход). - Frontend продолжает исходное действие (в т.ч. отложенное сообщение / популярный вопрос).
UX-инвариант (backlog п.21): ошибка отправки отложенного сообщения после успешного bootstrap не должна выглядеть как «не удалось завершить вход». Вход завершён на шаге 5–6; ошибка Bitrix/чата показывается в контексте чата.
6.3. Возврат без OTP
- Есть валидный refresh token → Refresh Token Grant → access token.
- При необходимости —
session-start. - OTP не показывается.
- Нет/истёк refresh → гость до следующего защищённого действия.
6.4. Поддержание сессии (tokens)
- Frontend проактивно обновляет access token (~60 с до
exp), single-flight. - Успешный refresh не создаёт новую UX-сессию.
401от API → один refresh + retry исходного запроса; провал refresh → очистка tokens → гость.- То же для WebSocket
/api/v1/realtime.
6.5. UX-сессия
Новая UxSession только при:
start_reason |
Когда |
|---|---|
first_launch |
В памяти нет ux_session_id |
cold_start |
Kill app / закрытие вкладки |
idle_timeout |
Простой > ux.session.idle_timeout_minutes |
ux_session_id хранится только в памяти (не в localStorage). Передаётся как X-Ux-Session-Id. Отсутствие заголовка API не блокирует (кроме endpoint, где id обязателен).
6.6. Профиль
- Открывается только авторизованным.
- Данные —
GET /api/v1/me; поля могут быть частично пустыми до CRM sync. - Редактирование недоступно; изменение ФИО/email и т.п. — через процессы компании (Bitrix24 → sync).
- Документы — отдельный блок; скачивание с audit.
6.7. Выход
- Frontend инициирует logout у Keycloak (revocation по policy модуля).
- Очищает access/refresh tokens и in-memory UX-сессию.
- UI переходит в гостевой режим.
- Локальный
UserIdentityв App DB не удаляется.
7. Матрицы поведения
7.1. Что требует auth
| Действие | Гость | Авторизованный |
|---|---|---|
GET /api/v1/public/* |
да | да |
| Просмотр главной / гостевых уведомлений | да | нет (после входа — только P) |
| Отправка сообщения / вложение | нет → OTP | да |
POST /auth/bootstrap |
нет (нужен JWT после OTP) | да (идемпотентно) |
POST /consents, session-start |
нет | да |
GET /me, чат, персональные уведомления |
нет | да |
WS /api/v1/realtime |
нет | да |
7.2. Источник истины полей
| Поле / факт | Master | Куда кэшируется |
|---|---|---|
sub / существование IdP user |
Keycloak | user_identities.keycloak_sub |
| Auth-телефон | Keycloak | user_identities.phone_number, seed client_profiles.russian_phone |
| Согласия (версия + accepted) | App DB user_consents |
— |
| ФИО, гражданство, email | Последний успешный sync (MVP: Bitrix → App) | client_profiles |
foreign_phone |
Не синхронизируется в первом релизе | client_profiles |
| Связь с Contact | bitrix-sync |
Только bitrix_sync.entity_external_mapping; в App DB не кэшируется |
| Tokens / auth session | Keycloak | secure storage на клиенте |
ux_session_id |
App DB + память клиента | заголовок запросов |
7.3. Bootstrap — идемпотентность
| Повторный вызов | Результат |
|---|---|
Тот же sub, те же версии согласий |
200, тот же user_id; last_login_at обновляется; дублей consent нет |
Тот же sub, новые версии согласий |
Новые immutable строки consent + update identity |
| JWT без phone claim | 400 phone_claim_missing |
| Обязательные consents не accepted | 403 consents_required |
Application-код не пишет в sync_queue: задачи создают триггеры на insert/update UserIdentity / ClientProfile.
8. Идентификация
| Идентификатор | Назначение |
|---|---|
keycloak_sub |
Subject JWT; ключ find-or-create |
user_id |
PK user_identities; FK всех персональных сущностей App DB |
phone_number |
Auth-телефон E.164 |
guest_session_id |
Локальный UUID устройства; не auth |
ux_session_id |
Аналитическая сессия |
b24_id |
Внутренний идентификатор Contact; используется только bitrix-sync |
device_id |
Opaque id устройства в bootstrap / session-start; в audit/log не копируется как PII |
Публичные id — UUID (в App DB предпочтительно UUID v7, как в остальных доменах).
9. API
Общие конвенции — arch-02.
9.1. Клиентские (JWT)
| Метод и путь | Назначение |
|---|---|
POST /api/v1/auth/bootstrap |
Find-or-create пользователя + согласия |
POST /api/v1/consents |
Повторная фиксация версий согласий |
POST /api/v1/analytics/session-start |
Новая UxSession |
GET /api/v1/me |
Readonly блочный профиль |
GET /api/v1/me/documents |
Список документов (MVP может быть пустым) |
GET /api/v1/documents/{id} |
Metadata документа (owner only) |
GET /api/v1/documents/{id}/download-url |
Presigned GET + audit |
Auth у Keycloak: публичные OIDC endpoints через /auth/* (не часть api-backend).
9.2. Public (без JWT)
| Метод и путь | Назначение |
|---|---|
GET /api/v1/public/settings (и связанные public) |
Флаги auth, URL/версии согласий, OTP UI-параметры по is_public |
9.3. Internal (смежные)
| Метод и путь | Кто → кто | Назначение |
|---|---|---|
GET /internal/settings/v1/otp |
Keycloak SPI → api-backend | Продуктовые OTP limits |
POST /internal/sms/v1/send |
Keycloak → sms-service | Заказ SMS OTP (real mode) |
9.4. Ошибки (домен пользователя)
| Код | HTTP | Когда |
|---|---|---|
phone_claim_missing |
400 | Нет канонического телефона в JWT при bootstrap |
validation_error |
400 | Невалидные версии/тело согласий или device |
unauthorized |
401 | Нет/невалиден JWT |
consents_required |
403 | Обязательные согласия не приняты |
resource_state_conflict |
409 | Protected endpoint до bootstrap («bootstrap required»); открытый вопрос TBD-1 по унификации с 404 для consents |
profile_not_found |
404 | Профиль не найден (по контракту envelope) |
rate_limit_exceeded |
429 | Превышен лимит |
Дифференцированные тексты ошибок OTP на UI (неверный код / истёк / лимит send / лимит verify) — backlog п.10; контракт Keycloak/frontend уточняется отдельно, в этом ТЗ фиксируется требование продукта.
9.5. Rate limiting
| Зона | Identity |
|---|---|
Auth edge (nginx) |
IP |
| bootstrap / consents / session-start | user + IP |
| OTP product limits | phone (Keycloak counters) |
10. Модель данных (схема han_app)
Общие правила — module-01 §9.1 / arch-05: UUID PK, timestamptz UTC, common fields, soft-delete A/D, FK ON DELETE RESTRICT.
10.1. user_identities
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | user_id |
keycloak_sub |
varchar(255) NOT NULL UNIQUE | JWT sub |
phone_number |
varchar(32) NOT NULL | E.164 из JWT |
last_login_at |
timestamptz NOT NULL | Обновляется на bootstrap |
| common fields | обязательны |
Индексы: unique keycloak_sub; index на phone_number для CRM map. Телефон в App DB не unique: identity master — Keycloak; временный конфликт при merge/миграции допустим на уровне данных, но продуктово один phone = один active sub.
10.2. user_consents
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | |
user_id |
uuid FK | |
ux_session_id |
uuid NULL | Если сессия уже есть |
consent_type |
varchar | personal_data | user_agreement | marketing |
document_version |
varchar | Версия из app_settings |
accepted |
boolean | |
accepted_at |
timestamptz | |
client_ip |
inet | |
user_agent_hash |
varchar | |
| device snapshot | jsonb / поля | По module-01 (device_json и т.п.) |
| common fields | обязательны |
Unique (user_id, consent_type, document_version). Записи immutable.
10.3. client_profiles
| Поле | Тип | Описание |
|---|---|---|
id |
uuid PK | |
user_id |
uuid UNIQUE FK | 1:1 с identity |
full_name |
varchar NULL | |
citizenship |
varchar NULL | |
russian_phone |
varchar NULL | Seed из auth-телефона |
foreign_phone |
varchar NULL | |
email |
varchar NULL | |
source_updated_at |
timestamptz NULL | Метка источника sync |
| common fields | обязательны |
CRM Contact ID и mapping в client_profiles отсутствуют. PII не попадает в логи и generic audit payload.
10.4. ux_sessions
id = ux_session_id; user_id; start_reason; platform; app_version; device_id; started_at; common fields.
10.5. Триггеры sync
| Событие | task_type |
|---|---|
Insert active UserIdentity / ClientProfile без mapping |
contact.map_or_create |
| Изменение tracked profile / auth-phone полей | contact.update |
Подавление эха: GUC han.sync_suppress при записи из bitrix-sync. Ошибка CRM не откатывает bootstrap и чат.
11. Фоновые и смежные процессы
| Процесс | Владелец | Связь с пользователем |
|---|---|---|
| OTP challenge / counters / expiry | Keycloak SPI | До появления App user |
| SMS order / delivery journal | sms-service |
Только доставка кода |
contact.map_or_create / contact.update / contact.deactivate |
bitrix-sync |
После bootstrap / изменения телефона / деактивации |
contact.rebind |
bitrix-sync |
Audited административное исправление ошибочного mapping |
| Token refresh / logout | Frontend + Keycloak | Не трогает App DB identity |
| Retention UX-сессий (если введён) | ops / module | Не удаляет UserIdentity |
12. Audit и observability
event_type |
Actor | Когда |
|---|---|---|
auth.bootstrap |
user | Успешный bootstrap |
consent.recorded |
user | Запись согласий (bootstrap или /consents) |
session_start |
user | Новая UX-сессия |
document.download_url_issued |
user | Скачивание документа профиля |
| OTP security events | Keycloak | Send/verify attempts (schema keycloak, phone HMAC/masked) |
В audit нет: полного phone/email/name в свободном тексте логов общего контура, OTP raw code, tokens, presigned URL.
Метрики (минимум): число bootstrap/сутки, доля consents_required, доля phone_claim_missing. Latency map Contact и доля пользователей без active mapping спустя N минут считаются bitrix-sync по собственной схеме.
13. Хранение данных и PII
UserIdentity,ClientProfile,UserConsent— прикладные строки, soft-delete, физическое удаление запрещено (arch-05).- Auth-мастер PII телефона — Keycloak; App DB держит кэш для FK/CRM/UI.
- Согласия хранятся бессрочно как юридически значимый журнал (immutable rows).
- Гостевые локальные согласия на устройстве до OTP не являются серверным журналом и при сбое до bootstrap могут быть потеряны — клиент проходит согласия снова.
- Right-to-erasure / удаление аккаунта клиентом — вне scope MVP; потребует отдельной политики по IdP + App DB + CRM.
14. Смежные сервисы
| Компонент | Ответственность в домене User |
|---|---|
| Frontend | Гость/ЛК, согласия UI, OTP UX, tokens, refresh, bootstrap/session-start, профиль readonly, отложенное сообщение после входа |
| Keycloak | IdP, OTP, phone uniqueness, tokens, sessions |
| api-backend | Bootstrap, consents, me/profile, JWT validation, ownership, settings bridge OTP |
| sms-service | Durable order SMS (real mode) |
| bitrix-sync | Map/update Contact; не создаёт UserIdentity |
| nginx | /auth/*, edge rate limit auth |
| App DB | Таблицы §10, триггеры sync |
Порядок работ (если дорабатывать домен): (1) Keycloak OTP + phone claims → (2) bootstrap + consents + identity/profile → (3) session-start → (4) me/profile UI → (5) CRM map → (6) documents post-MVP.
15. Влияние на arch-документы
| Документ | Статус относительно этой постановки |
|---|---|
arch-00-glossary.md |
Термины UserIdentity, ClientProfile, UserConsent, UxSession, guest_session_id, keycloak_sub уже заданы |
arch-01-system-architecture.md |
Потоки гостя, OTP, возврата, профиля — канон сценариев |
arch-02-api-contracts.md |
Контракты bootstrap / consents / session-start / me |
arch-04-settings-and-content.md |
auth.*, otp.phone.*, consent.*, ux.session.* |
module-01-api-backend.md |
Таблицы и алгоритмы bootstrap |
module-08-keycloak.md |
OTP-only phone flow |
module-07-bitrix-sync.md |
Обработка contact.* задач |
Этот документ не заменяет module-спеки; он собирает бизнес-смысл сущности «Пользователь» для аналитики и смежных фич (уведомления, чат, документы).
16. Критерии приёмки
- Гость видит только public API; write без JWT недоступен;
guest_session_idне открывает API. - Вход только по телефону + OTP; пароль/email/social отсутствуют.
- После OTP
bootstrapсоздаёт/находитUserIdentity, минимальныйClientProfile, пишет согласия; телефон берётся из JWT, не из body. - Повторный bootstrap идемпотентен;
last_login_atобновляется. - Без обязательных согласий —
403 consents_required; без phone claim —400 phone_claim_missing. - Protected endpoint до bootstrap — безопасный отказ (
409до закрытия TBD-1). - Возврат с валидным refresh — без OTP; провал refresh — гостевой UI.
session-startтолько с JWT; в гостевом режиме не вызывается; idle/cold/first_launch создают новую UX-сессию.GET /meотдаёт блочный readonly профиль; PATCH/PUT нет.- Ошибка CRM sync не ломает вход; Contact мапится асинхронно.
- Один active phone ↔ один active
subна стороне Keycloak; merge не происходит молча при login. - Отложенное сообщение / популярный вопрос после auth уходит штатно; ошибка доставки не маскируется под ошибку входа.
- Выход очищает tokens и возвращает в гостевой UI, не удаляя
UserIdentity. - PII не светится в обычных логах/audit payload; OTP code не логируется.
17. Журнал решений и открытых вопросов (ненормативно)
17.1. Принятые решения
| # | Решение | Раздел / источник |
|---|---|---|
| D1 | Два режима: гость и авторизованный; гостевые данные в App DB не пишутся | §2, arch-01 |
| D2 | Слои IdP / UserIdentity / ClientProfile / Bitrix Contact разделены; master auth — Keycloak | §5.1 |
| D3 | OTP-only phone; password disabled | §3, module-08 |
| D4 | Согласия продуктовые в API; Keycloak их не заменяет; серверная запись только после JWT | §5.3 |
| D5 | Телефон только из JWT claims | §5.4, arch-02 |
| D6 | Профиль readonly и блочный; документы — отдельный блок, доставка post-MVP | §5.5 |
| D7 | Bootstrap атомарный + идемпотентный; sync через триггеры БД | §7.3 |
| D8 | UX-сессия ≠ auth; только для авторизованных; хранение id в памяти | §6.5, arch-00 |
| D9 | CRM не блокирует авторизацию | §6.2 |
| D10 | Один verified phone = один active sub |
module-08 |
17.2. Открытые вопросы
| # | Вопрос | Предложение | Влияние |
|---|---|---|---|
| Q1 | Единый код для protected endpoint до bootstrap: 409 vs 404 (TBD-1 module-01) |
Оставить 409 resource_state_conflict |
OpenAPI, клиентский UX |
| Q2 | Дифференцированные ошибки OTP на UI (backlog п.10) | Зафиксировать словарь кодов Keycloak → frontend texts | module-08 + frontend |
| Q3 | История устройств входа (backlog п.19) | Отдельная сущность/таблица, не смешивать с UxSession |
Новая постановка |
| Q4 | Debounce/backoff SMS после интеграции провайдера (backlog п.11) | Надстройка над otp.phone.min_seconds_between_attempts |
Keycloak SPI |
| Q5 | Тестовый пользователь с фиксированным SMS (backlog п.16) | Операционный allow-list / mock per-phone, не дырка в prod limits | ops + module-08 |
| Q6 | Продуктовый self-service смены телефона | Позже: re-auth + OTP нового номера + invalidate sessions | Keycloak + bootstrap |
| Q7 | Клиентское удаление аккаунта / right-to-erasure | Вне MVP; отдельная юридическая и техническая постановка | IdP + App + CRM |
| Q8 | Расширение блоков профиля сверх «Личные данные» / «Документы» (как в Figma-моках) | Только после продуктового решения; Figma не канон | UI + client_profiles / новые таблицы |
18. Связь с уведомлениями
| Аспект | Гость | Авторизованный пользователь |
|---|---|---|
| Контур уведомлений | G (guest_notifications) |
P (notifications.user_id) |
| Бейдж непрочитанных | нет | да |
| Центр уведомлений | auth-gate | список |
| Перенос G → P при логине | запрещён | — |
Домен User задаёт, кто видит контур P; домен Notification задаёт что показывается. Владелец персональных записей — всегда user_identities.id.