Initial commit
This commit is contained in:
@@ -0,0 +1,566 @@
|
||||
# arch-01. Общая архитектура системы
|
||||
|
||||
> Термины — в [`arch-00-glossary.md`](arch-00-glossary.md).
|
||||
> Приоритет документов — в [`README.md`](README.md).
|
||||
|
||||
## Назначение
|
||||
|
||||
HAN Chat - приложение для мигрантов, где стартовый экран знакомит клиента с сервисом и предлагает задать вопрос. Авторизация не требуется при первом входе: она запрашивается при попытке отправить первое сообщение, потому что в переписке могут обрабатываться персональные данные.
|
||||
|
||||
## Зафиксированные решения MVP
|
||||
|
||||
- Авторизация: только OTP по **номеру телефона** (email-канал в MVP не используется).
|
||||
- Вторая сторона чата: Битрикс24 Open Lines.
|
||||
- Master source auth-данных: Keycloak; профиль в UI — кэш App DB с двусторонней sync через `bitrix-sync`.
|
||||
- Диалог приложения соответствует диалогу в Битрикс24 Open Lines.
|
||||
- Лиды и сделки в MVP не используются.
|
||||
- Файлы production-хранилища: Selectel S3, бакет **S3-data** (логическое имя; физически два бакета — `han-chat-attachments` для файлов чата и `han-chat-documents` для документов компании).
|
||||
- Файлы до проверки: Selectel S3, бакет **S3-quarantine**; после `200 allow` — перенос в S3-data (attachments). Имена бакетов — [`arch-00-glossary.md`](arch-00-glossary.md); права доступа — ниже и в «Принципы безопасности».
|
||||
- Мультиязычность в первом релизе не нужна, но тексты должны храниться по мнемоникам для будущих переводов.
|
||||
- Среда на первом этапе одна и проектируется как боевая.
|
||||
- Вложения чата MVP: **только изображения и PDF** — см. [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), «Разрешённые типы файлов чата».
|
||||
- SMS OTP на старте: **заглушка** — пользователь вводит фиксированный код из `.env` (`KEYCLOAK_OTP_MOCK_CODE`); SMS не отправляется. Интеграция с SMS-провайдерами — в бэклоге (см. [`!Backlog.md`](../../HAN_chat/!Backlog.md)).
|
||||
- Популярный вопрос при выборе **автоматически отправляется как сообщение**; если пользователь не авторизован — сначала согласия и OTP, затем отправка.
|
||||
- Перечень таблиц и миграций App DB проектирует модуль `database` (и владельцы схем других сервисов); arch фиксирует только **разделение схем** PostgreSQL и контракты между сервисами.
|
||||
|
||||
## Пользовательские сценарии
|
||||
|
||||
1. Клиент открывает мобильное или web-приложение и видит главный экран с приветствием, популярными вопросами и полем ввода.
|
||||
2. Frontend определяет, нужна ли **новая UX-сессия**, и при необходимости отправляет событие **`session_start`** (см. «Аналитическая UX-сессия»). Клиент может изучить сервис без авторизации.
|
||||
3. Если у клиента сохранён **действующий refresh token**, frontend выполняет silent refresh **без OTP** (см. «Поток возврата пользователя»).
|
||||
4. Клиент нажимает популярный вопрос — frontend подставляет текст вопроса и **инициирует отправку сообщения** (тот же поток, что ручной ввод). Либо клиент вводит свой текст и отправляет.
|
||||
5. Если клиент не авторизован, перед отправкой первого сообщения frontend показывает pop-up с согласиями и запускает OTP (см. «Поток авторизации»).
|
||||
6. После успешной авторизации api-backend создаёт или находит локального пользователя по `keycloak_sub`, связывает ранее сохранённые согласия с `guest_session_id`, создаёт или обновляет профиль; триггер App DB ставит задачу в `sync_queue` для `bitrix-sync`.
|
||||
7. api-backend выполняет find-or-create диалога (см. «Создание диалога») и отправляет сообщение (текст популярного вопроса или введённый клиентом).
|
||||
8. Сообщение клиента проходит Message Safety и через Bitrix24 Local App направляется в Битрикс24 Open Lines.
|
||||
9. Ответ оператора из Битрикс24 Open Lines поступает через Bitrix24 Local App в api-backend и отображается в чате приложения.
|
||||
10. Клиент может открыть историю диалогов.
|
||||
11. Клиент может открыть профиль, где данные структурированы блоками: «Личные данные» и «Документы». В дальнейшем могут добавляться новые блоки.
|
||||
12. Редактирование профиля из профиля недоступно. Для изменения данных клиент переходит в чат и пишет запрос оператору.
|
||||
|
||||
## Компоненты верхнего уровня
|
||||
|
||||
- Expo App: единая frontend-кодовая база для iOS, Android и web.
|
||||
- Keycloak: identity provider, OTP-only авторизация по номеру телефона.
|
||||
- api-backend: Python-приложение с REST API, realtime-доставкой сообщений и бизнес-логикой.
|
||||
- Nginx Reverse Proxy: единая публичная точка входа, HTTPS termination и маршрутизация на Keycloak/API/frontend web/Bitrix24.
|
||||
- Message Safety Service: отдельный сервис проверки входящих сообщений; синхронный вызов из API → `200 allow` | `403 deny` | `203 pending` + `task_id`.
|
||||
- Bitrix24 Local App: локальное приложение, custom connector `han_mobile_app` для Bitrix24 Open Lines: чат, OAuth, webhook-события, маппинг `dialog_id` ↔ `bitrix_chat_id`.
|
||||
- Bitrix24 sync service: двусторонняя синхронизация App DB ↔ Битрикс24 CRM (Contact на MVP; маппинг ID, очередь через триггеры, webhook от роботов Bitrix24).
|
||||
- Managed PostgreSQL (приватная сеть, одна база): схемы `han_app`, `bitrix_sync`, `bitrix_local`, `keycloak`, `message_safety` — отдельный DB-user на схему.
|
||||
- Redis: rate limits, временные счетчики OTP и realtime/service coordination.
|
||||
- S3-data: production-хранилище проверенных файлов чата (`han-chat-attachments`) и документов компании (`han-chat-documents`).
|
||||
- S3-quarantine: временное хранилище загруженных файлов до вердикта Message Safety Service (`han-chat-quarantine`); read-only для `message-safety`.
|
||||
- observability: JSON-логи в stdout, `request_id`, `trace_id`, **`ux_session_id`** (если передан), базовая трассировка через OpenTelemetry Collector.
|
||||
|
||||
## Инфраструктура развёртывания (зафиксировано)
|
||||
|
||||
На первом этапе весь backend-контур работает на **одной VM** в облаке провайдера:
|
||||
|
||||
- `nginx`, `api-backend`, `message-safety`, `keycloak`, `bitrix-sync`, `bitrix-local-app`, `redis`, `otel-collector` — в Docker Compose на VM;
|
||||
- публичный доступ из интернета только через `nginx` (порты 80/443);
|
||||
- внутренние сервисы общаются по Docker-сети на localhost VM.
|
||||
|
||||
Базы данных — **managed PostgreSQL** того же провайдера в **том же облачном кластере/VPC**, **без публичного доступа** из интернета. VM подключается к БД только по приватной сети.
|
||||
|
||||
Схема данных в managed PostgreSQL (перечень таблиц внутри схем — в модульных спецификациях, не в arch-*):
|
||||
|
||||
| База / схема | Сервисы | Назначение схемы |
|
||||
|---|---|---|
|
||||
| одна база / `han_app` | `api-backend`, `bitrix-sync` (ограниченный GRANT) | прикладные данные приложения, очередь sync, audit |
|
||||
| одна база / `bitrix_sync` | `bitrix-sync` | worker state, retry/dead letter, sync audit |
|
||||
| одна база / `message_safety` | `message-safety` | verdict cache, safety_task, rule config |
|
||||
| одна база / `bitrix_local` | `bitrix-local-app` | OAuth, inbox, `dialog_sessions` |
|
||||
| одна база / `keycloak` | Keycloak | учётные записи, realm, сессии IdP |
|
||||
|
||||
Redis на первом этапе остаётся на VM в Docker (ephemeral/coordination). Selectel S3 — внешнее object storage: три бакета (`han-chat-quarantine`, `han-chat-attachments`, `han-chat-documents`); см. [`arch-00-glossary.md`](arch-00-glossary.md).
|
||||
|
||||
## Контекстная схема
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
Client[Expo Mobile/Web App]
|
||||
Nginx[Nginx Reverse Proxy]
|
||||
Keycloak[Keycloak OTP]
|
||||
API[Python api-backend]
|
||||
Safety[Message Safety Service]
|
||||
DB[(PostgreSQL)]
|
||||
Redis[(Redis)]
|
||||
Sync[Bitrix24 sync service]
|
||||
LocalApp[Bitrix24 Local App]
|
||||
Bitrix[Bitrix24 CRM]
|
||||
S3Data[(S3-data: attachments + documents)]
|
||||
S3Q[(S3-quarantine)]
|
||||
Obs[observability]
|
||||
|
||||
Client -->|HTTPS REST + Realtime| Nginx
|
||||
Nginx -->|/auth| Keycloak
|
||||
Nginx -->|/api + /realtime| API
|
||||
Keycloak --> DB
|
||||
API --> DB
|
||||
API --> Redis
|
||||
API -->|upload / move / delete| S3Q
|
||||
API -->|promote delivered files| S3Data
|
||||
API -->|internal check message| Safety
|
||||
Safety --> DB
|
||||
Safety --> Redis
|
||||
Safety -->|read scan| S3Q
|
||||
API -->|send messages| LocalApp
|
||||
API -->|App DB writes| DB
|
||||
Sync -->|sync_queue + profile| DB
|
||||
Sync -->|CRM Contact REST| Bitrix
|
||||
Bitrix -->|robot webhook| Sync
|
||||
Bitrix -->|ONIMCONNECTOR*| LocalApp
|
||||
LocalApp -->|imconnector.send.messages/status| Bitrix
|
||||
LocalApp -->|normalized inbox events| API
|
||||
API -->|WebSocket/SSE or polling fallback| Client
|
||||
API --> Obs
|
||||
Safety --> Obs
|
||||
Sync --> Obs
|
||||
LocalApp --> Obs
|
||||
LocalApp --> DB
|
||||
```
|
||||
|
||||
## Архитектурные границы
|
||||
|
||||
### Frontend
|
||||
|
||||
Отвечает за:
|
||||
|
||||
- стартовый экран с приветствием, популярными вопросами, полем ввода, историей и профилем;
|
||||
- гостевой режим до первого сообщения;
|
||||
- показ pop-up с обязательными согласиями на обработку персональных данных и пользовательское соглашение, а также необязательным согласием на рекламные коммуникации;
|
||||
- сбор данных устройства для передачи в backend;
|
||||
- **управление аналитической UX-сессией** на клиенте: определение начала нового периода активности, хранение `ux_session_id` и `last_activity_at` **только в памяти**, отправка `session_start`, заголовок `X-Ux-Session-Id` во всех запросах;
|
||||
- хранение access token и refresh token в безопасном хранилище после авторизации;
|
||||
- **жизненный цикл access token**: проактивное обновление по расписанию (до истечения `exp`) и обработка **`401`** от `api-backend` (см. «Обновление access token (frontend)»);
|
||||
- при открытии приложения: проверку refresh token → silent refresh через Keycloak **или** OTP-flow при истечении refresh token;
|
||||
- отображение входящих сообщений от оператора;
|
||||
- загрузку файлов в чат через backend;
|
||||
- работу с текстовыми мнемониками;
|
||||
- отправку `traceparent`/correlation id в backend.
|
||||
|
||||
Frontend не должен:
|
||||
|
||||
- хранить бизнес-логику синхронизации с Битрикс24;
|
||||
- принимать решения о доступе к чужим документам или диалогам;
|
||||
- обращаться напрямую к Битрикс24, Selectel S3 или базе данных.
|
||||
|
||||
### api-backend
|
||||
|
||||
Отвечает за:
|
||||
|
||||
- публичные настройки приложения для frontend;
|
||||
- проверку JWT от Keycloak для защищенных операций; при истёкшем или невалидном access token — **`401`** (refresh выполняет frontend, не backend);
|
||||
- локальную регистрацию пользователя приложения: `find-or-create` `UserIdentity` по `keycloak_sub`, создание минимального `ClientProfile` для нового пользователя, обновление `last_login_at` для существующего (после OTP — см. `POST /api/v1/auth/bootstrap`);
|
||||
- **приём события `session_start`**: запись `UxSession`, audit/analytics-событие; **не** используется для контроля доступа;
|
||||
- валидация данных получаемых от frontend (соответствие типов данных, проверка обязательности полей, проверка формата данных, диапазоны значений, размер полей) через Pydantic
|
||||
- хранение согласий пользователя в App DB (`guest_session_id`, **`ux_session_id`**, **`client_ip`**, версии документов);
|
||||
- профиль, структурированный блоками;
|
||||
- API чата, истории, файлов и документов;
|
||||
- realtime-доставку входящих сообщений клиенту;
|
||||
- отправку сообщений клиента в Open Lines через Bitrix24 Local App;
|
||||
- прием нормализованных входящих событий Open Lines от Bitrix24 Local App;
|
||||
- хранение истории диалогов;
|
||||
- запись данных профиля в App DB (синхронизация с Bitrix24 — триггеры → `sync_queue` → `bitrix-sync`, без участия api-backend);
|
||||
- загрузку файлов из чата в S3-quarantine до проверки;
|
||||
- синхронный вызов Message Safety Service (`POST /internal/safety/v1/messages/check`) и интерпретацию ответа: `200 allow`, `403 deny`, `203 pending` + `task_id`;
|
||||
- при `200`: перенос файлов quarantine → S3-data, сохранение сообщения, отправка в Bitrix24;
|
||||
- при `403`: удаление файлов из quarantine, безопасный ответ клиенту;
|
||||
- при `203`: сохранение сообщения со статусом ожидания проверки, ответ клиенту «обрабатывается», опрос `GET /internal/safety/v1/messages/tasks/{task_id}` и доставка цепочки после финального `200` или cleanup после `403`;
|
||||
- auth-aware rate limits для сообщений, пользовательских и сервисных операций;
|
||||
- аудит пользовательских действий;
|
||||
- единые ошибки и валидацию входных данных.
|
||||
|
||||
### Bitrix24 Local App
|
||||
|
||||
Отвечает за Open Lines (чат):
|
||||
|
||||
- регистрацию локального приложения Bitrix24;
|
||||
- OAuth lifecycle Bitrix24 и хранение токенов портала;
|
||||
- регистрацию и активацию custom connector `han_mobile_app` для открытой линии 8;
|
||||
- прием публичных событий Bitrix24 `ONIMCONNECTOR*` на `/bitrix/handler`;
|
||||
- нормализацию событий Open Lines в доменные события HAN;
|
||||
- хранение `dialog_sessions`: связка `external_chat_id` (=`dialog_id` приложения) ↔ `bitrix_chat_id` ↔ `session_id`;
|
||||
- хранение локального inbox до готовности API;
|
||||
- internal API для api-backend: `POST /internal/openlines/v1/messages`, `GET /internal/openlines/v1/dialogs/{external_chat_id}`;
|
||||
- forward нормализованных событий оператора в API (`BITRIX_API_FORWARD_URL`);
|
||||
- вызовы `imconnector.send.messages` и `imconnector.send.status.delivery`.
|
||||
|
||||
Не отвечает за:
|
||||
|
||||
- CRM Contact mapping и синхронизацию прочих CRM-сущностей;
|
||||
- сохранение сообщений и истории чата в App DB;
|
||||
- realtime-доставку в Expo App;
|
||||
- бизнес-логику профиля и документов.
|
||||
|
||||
### Bitrix24 sync service
|
||||
|
||||
Отвечает за **двустороннюю** синхронизацию данных между App DB и Битрикс24 CRM:
|
||||
|
||||
- **маппинг ID** сущностей приложения ↔ Bitrix24 (`bitrix_contact_id`, `entity_external_mapping`);
|
||||
- **App DB → Bitrix24:** обработка очереди `sync_queue` (триггеры App DB) — map/create Contact по телефону, push обновлений полей;
|
||||
- **Bitrix24 → App DB:** приём webhook от роботов Bitrix24, обновление профиля с GUC `han.sync_suppress`;
|
||||
- реестр синхронизируемых сущностей (MVP: Contact; post-MVP: Lead, Deal, Document);
|
||||
- повторные попытки, rate limiting Bitrix REST, dead letter;
|
||||
- прямой доступ к схеме `han_app` и собственной `bitrix_sync`.
|
||||
|
||||
Не отвечает за:
|
||||
|
||||
- hot path чата Open Lines;
|
||||
- OAuth lifecycle локального приложения Bitrix24;
|
||||
- создание `UserIdentity` / `ClientProfile` в auth-flow;
|
||||
- хранение `dialog_sessions`.
|
||||
|
||||
### Keycloak
|
||||
|
||||
Отвечает за:
|
||||
|
||||
- OTP-only регистрацию и вход;
|
||||
- OTP по номеру телефона; проверка кода — в Keycloak (заглушка `KEYCLOAK_OTP_MOCK_*` или SMS-провайдер, см. arch-04 и «Поток авторизации»);
|
||||
- хранение учетных записей;
|
||||
- выдачу и обновление токенов;
|
||||
- настройку realm, clients, roles, policies.
|
||||
|
||||
Парольная авторизация, magic link и социальные логины не входят в MVP.
|
||||
|
||||
### Nginx Reverse Proxy
|
||||
|
||||
Отвечает за:
|
||||
|
||||
- прием внешнего HTTPS-трафика;
|
||||
- TLS termination;
|
||||
- редирект HTTP на HTTPS (на веб-домене; для выделенного API-домена HTTP не допускается — см. «Принципы безопасности»);
|
||||
- маршрутизацию `/api/*` и `/realtime/*` в api-backend;
|
||||
- маршрутизацию `/auth/*` или выделенного auth-домена в Keycloak;
|
||||
- маршрутизацию публичных `/bitrix/*` endpoint в `bitrix-local-app`;
|
||||
- маршрутизацию `/bitrix/sync/*` webhook endpoint в `bitrix-sync`;
|
||||
- защиту internal endpoint `bitrix-local-app` через private network или `nginx allowlist`;
|
||||
- отсутствие публичной маршрутизации к `message-safety` — сервис доступен только из внутренней Docker-сети;
|
||||
- передачу `X-Forwarded-For`, `X-Forwarded-Proto`, `X-Forwarded-Host`, `X-Request-ID`;
|
||||
- базовые лимиты размера запроса и timeout;
|
||||
- грубые edge rate limits по IP, route и зоне риска;
|
||||
- TLS 1.2/1.3, HSTS, security headers и скрытие технологических заголовков;
|
||||
- кэширование публичных endpoint настроек и контента;
|
||||
- запрет доступа к внутренним сервисам и техническим портам извне.
|
||||
|
||||
### Message Safety Service
|
||||
|
||||
Отвечает за:
|
||||
|
||||
- проверку **входящих сообщений от пользователя** (текст, ссылки, файлы);
|
||||
- **внутреннюю** orchestration: синхронно текст и ссылки; при необходимости — async-проверка файлов;
|
||||
- HTTP-контракт для api-backend:
|
||||
- `200` — синхронная проверка завершена, **allow**;
|
||||
- `403` — синхронная проверка завершена, **deny**;
|
||||
- `203` + `task_id` — нужна async-проверка (обычно файлы), сообщение в обработке;
|
||||
- финальный вердикт async-задачи по `GET /internal/safety/v1/messages/tasks/{task_id}`: `200 allow` | `403 deny` | `203 pending`;
|
||||
- SHA-256 хеширование и lookup кэша вердиктов;
|
||||
- отдельный pipeline проверки ссылок;
|
||||
- запись verdict cache, `safety_task` и audit в схеме `message_safety`;
|
||||
- internal API: `POST /internal/safety/v1/messages/check`, `GET /internal/safety/v1/messages/tasks/{task_id}`.
|
||||
|
||||
Не отвечает за:
|
||||
|
||||
- загрузку файлов клиентом, presigned URL, перемещение quarantine → S3-data, удаление из quarantine;
|
||||
- сохранение сообщений, истории диалогов (CRM sync — зона `bitrix-sync`, не api-backend);
|
||||
- доставку в Bitrix24 Open Lines и realtime клиенту;
|
||||
- проверку JWT, согласий, edge rate limits;
|
||||
- polling `task_id` на стороне клиента — только api-backend (фоновый worker или internal loop).
|
||||
|
||||
api-backend не решает, sync или async нужна проверка: это определяет Message Safety Service по результатам фазы текста/ссылок и кэша файлов.
|
||||
|
||||
## Гостевая сессия (до JWT)
|
||||
|
||||
До OTP frontend работает в гостевом режиме с локально сгенерированным **`guest_session_id`** (UUID v4):
|
||||
|
||||
- создаётся при первом запуске приложения, хранится в secure storage устройства;
|
||||
- передаётся в `POST /api/v1/consents` вместе с согласиями и device metadata;
|
||||
- api-backend сохраняет согласия с привязкой к `guest_session_id` (TTL записи — 24 ч);
|
||||
- после успешного OTP api-backend **связывает** записи согласий и device session с `UserIdentity` по `keycloak_sub`;
|
||||
- `guest_session_id` не используется для доступа к защищённым ресурсам после выдачи JWT.
|
||||
|
||||
## Аналитическая UX-сессия (`ux_session_id`)
|
||||
|
||||
**UX-сессия** — период непрерывной активности пользователя в приложении для аналитики и сквозной трассировки. Это **не** сессия Keycloak, **не** refresh/access token и **не** механизм авторизации.
|
||||
|
||||
### Роли компонентов
|
||||
|
||||
**Frontend** (источник истины по правилам сессии):
|
||||
|
||||
- хранит `ux_session_id` и `last_activity_at` **только в памяти** (не в localStorage/secure storage);
|
||||
- при новой сессии вызывает `POST /api/v1/analytics/session-start` и сохраняет полученный `ux_session_id`;
|
||||
- обновляет `last_activity_at` при пользовательской активности и при возврате из фона;
|
||||
- при resume проверяет `(now - last_activity_at) > idle_timeout` → при превышении — новая сессия;
|
||||
- передаёт **`X-Ux-Session-Id`** во **всех** запросах к backend (public и JWT).
|
||||
|
||||
**api-backend**:
|
||||
|
||||
1. принимает `session_start`, создаёт запись **`UxSession`**, возвращает `ux_session_id`;
|
||||
2. пишет analytics/audit-событие `session_start` (без PII);
|
||||
3. включает `ux_session_id` из заголовка в JSON-логи (если передан);
|
||||
4. **не** блокирует запросы при отсутствии или неизвестном `ux_session_id` — это не auth.
|
||||
|
||||
`request_id` — один HTTP-запрос; `ux_session_id` — период UX-активности для аналитики и корреляции логов.
|
||||
|
||||
## Поток возврата пользователя (без OTP)
|
||||
|
||||
1. Клиент открывает приложение (UX-сессия определяется по правилам выше, независимо от auth).
|
||||
2. Frontend проверяет наличие refresh token в secure storage.
|
||||
3. Если refresh token **действителен** — frontend запрашивает новый access token у Keycloak (Refresh Token Grant), **OTP не показывается**.
|
||||
4. Frontend работает как авторизованный пользователь (история, профиль, чат).
|
||||
5. Если refresh token **отсутствует или истёк** — клиент остаётся в гостевом режиме до сценария, требующего auth; при первом сообщении — «Поток авторизации» с OTP.
|
||||
|
||||
## Обновление access token (frontend)
|
||||
|
||||
Пока refresh token **действителен**, frontend **сам** поддерживает актуальный access token — **не** полагаясь только на открытие приложения и **не** дожидаясь истечения refresh token (использует его для обновления access token заранее).
|
||||
|
||||
### Проактивное обновление по расписанию
|
||||
|
||||
1. После получения tokens (OTP или refresh) frontend сохраняет access token, refresh token и момент истечения access token (`exp` из JWT или `expires_in` из ответа Keycloak).
|
||||
2. Запускает таймер/scheduler: обновить access token **до** наступления `exp` (рекомендуемый запас — **60 с** до `exp`; константа модуля frontend).
|
||||
3. По срабатыванию таймера — **Refresh Token Grant** к Keycloak, сохранение новой пары tokens, перепланирование следующего обновления.
|
||||
4. **Single-flight:** параллельные refresh-запросы не дублируются (один in-flight refresh, остальные ждут результат).
|
||||
5. Успешный refresh access token **не** создаёт новую UX-сессию и **не** вызывает `session_start`.
|
||||
|
||||
### Обработка `401` от `api-backend`
|
||||
|
||||
Если запрос с access token вернул **`401`** (токен уже истёк или отклонён):
|
||||
|
||||
1. HTTP-клиент frontend **один раз** инициирует Refresh Token Grant (если refresh ещё не выполняется — через тот же single-flight).
|
||||
2. При успехе — подставляет новый access token и **повторяет исходный запрос** (без бесконечных retry).
|
||||
3. При неудаче refresh (`invalid_grant`, истёк refresh token, ошибка Keycloak) — очищает tokens, переводит UI в **гостевой режим**; повторная авторизация — через OTP при следующем защищённом действии.
|
||||
4. Запросы, пришедшие во время in-flight refresh, **ставятся в очередь** и выполняются после успешного обновления (или отклоняются при провале refresh).
|
||||
5. Тот же принцип — для **WebSocket** `/api/v1/realtime`: при ошибке auth — refresh и переподключение с новым access token.
|
||||
|
||||
### Разделение ответственности
|
||||
|
||||
| Компонент | Поведение |
|
||||
|---|---|
|
||||
| **Frontend** | scheduler refresh, intercept `401`, retry, single-flight, хранение tokens |
|
||||
| **Keycloak** | выдача и ротация tokens (Refresh Token Grant) |
|
||||
| **api-backend** | проверка JWT; при невалидном/expired access token — **`401`**, refresh **не** выполняет |
|
||||
|
||||
## Создание диалога (MVP)
|
||||
|
||||
- Диалог создаётся **лениво** при первой отправке сообщения авторизованным клиентом.
|
||||
- Frontend перед `POST .../messages` вызывает `POST /api/v1/dialogs` (idempotency key), получает `dialog_id` и использует его далее.
|
||||
- Популярный вопрос: после auth тот же порядок — `POST /dialogs` → `POST .../messages` с текстом вопроса.
|
||||
- `dialog_id` = `external_chat_id` для Open Lines (см. [`arch-00-glossary.md`](arch-00-glossary.md), «Идентификаторы»).
|
||||
- При первой доставке в Bitrix24 `bitrix-local-app` создаёт запись `dialog_sessions`.
|
||||
|
||||
## Поток авторизации (OTP)
|
||||
|
||||
Срабатывает, когда клиент **ещё не имеет действующего refresh token** (первый вход) или refresh token **истёк**. Если refresh token валиден — см. «Поток возврата пользователя».
|
||||
1. Клиент находится в гостевом режиме (`guest_session_id` уже создан).
|
||||
2. Клиент инициирует отправку сообщения (ручной ввод или популярный вопрос).
|
||||
3. Frontend показывает pop-up с тремя согласиями.
|
||||
4. Клиент обязан принять согласие на обработку персональных данных и пользовательское соглашение.
|
||||
5. Клиент может опционально согласиться на рекламные коммуникации.
|
||||
6. Если обязательные согласия не даны, отправка блокируется.
|
||||
7. Frontend вызывает `POST /api/v1/consents` с `guest_session_id`, версиями документов, device metadata, IP/user agent (через backend).
|
||||
8. Frontend запрашивает публичные настройки и показывает форму ввода номера телефона (единственный канал MVP).
|
||||
9. Keycloak запускает OTP-flow по телефону: клиент вводит номер, инициируется «отправка» OTP (при заглушке SMS фактически не уходит — см. arch-04).
|
||||
10. Лимиты OTP проверяются по **`app_settings`** (`otp.phone.*`).
|
||||
11. Клиент вводит OTP и отправляет его в Keycloak.
|
||||
12. **Keycloak проверяет корректность введённого OTP**:
|
||||
- при **`KEYCLOAK_OTP_MOCK_ENABLED=true`** (MVP и любой режим с включённой заглушкой): введённое значение должно **совпадать** с `KEYCLOAK_OTP_MOCK_CODE` из `.env`;
|
||||
- при **`KEYCLOAK_OTP_MOCK_ENABLED=false`** (после интеграции с SMS-провайдером, см. бэклог): введённое значение должно **совпадать** с одноразовым OTP, сгенерированным Keycloak и отправленным провайдером на телефон клиента (с учётом TTL и лимита попыток).
|
||||
- при неверном коде Keycloak возвращает ошибку; frontend не получает tokens, шаг 13 не выполняется.
|
||||
13. При успешной проверке frontend получает tokens через OIDC Authorization Code Flow with PKCE.
|
||||
14. Frontend вызывает **`POST /api/v1/auth/bootstrap`** с JWT и `guest_session_id` (см. arch-02).
|
||||
15. api-backend выполняет `find-or-create` пользователя, связывает согласия с `guest_session_id`, при необходимости привязывает `user_id` к текущей **`UxSession`** по `ux_session_id`.
|
||||
16. Триггер App DB ставит задачу `contact.map_or_create` в `sync_queue`; `bitrix-sync` асинхронно находит или создает Contact в Битрикс24. Авторизация не должна синхронно зависеть от ответа Битрикс24 CRM.
|
||||
17. Frontend создаёт диалог и отправляет отложенное сообщение (см. «Создание диалога» и поток чата).
|
||||
|
||||
## Поток работы с чатом: клиент -> Битрикс24
|
||||
|
||||
В MVP сообщение клиента — **`content_kind`** `text` или `file`, не оба (см. [`arch-02-api-contracts.md`](arch-02-api-contracts.md), «Формат исходящего сообщения»).
|
||||
|
||||
**Текстовое сообщение:**
|
||||
|
||||
1. Frontend вызывает `POST /api/v1/dialogs` (если `dialog_id` ещё нет), затем отправляет `POST /api/v1/dialogs/{dialog_id}/messages` с непустым `text` (без вложения).
|
||||
2. Nginx и API применяют rate limits.
|
||||
3. API **синхронно** вызывает Message Safety Service (`POST /internal/safety/v1/messages/check`) — шаги текст и ссылки.
|
||||
4. Далее — общая ветка вердикта (п. 5–8 ниже).
|
||||
|
||||
**Файловое сообщение:**
|
||||
|
||||
1. Frontend инициализирует **одно** вложение (`POST .../attachments/init`), загружает файл; api-backend сохраняет его в **S3-quarantine**.
|
||||
2. Frontend отправляет `POST /api/v1/dialogs/{dialog_id}/messages` с `attachment_id` и `checksum` (поле `text` пустое).
|
||||
3. Nginx и API применяют rate limits.
|
||||
4. API **синхронно** вызывает Message Safety Service — шаг проверки файла (текст и ссылки пропускаются, если `text` пуст).
|
||||
|
||||
**Общая ветка вердикта (оба типа):**
|
||||
|
||||
5. **`403 deny`**: API удаляет quarantine (если был файл), возвращает клиенту безопасную ошибку; в Bitrix24 ничего не уходит.
|
||||
6. **`200 allow`**: API переносит файл в S3-data (если был), сохраняет сообщение, отправляет в Bitrix24, подтверждает клиенту (realtime/polling).
|
||||
7. **`203 pending` + `task_id`**: api-backend сохраняет сообщение со статусом ожидания проверки, отвечает клиенту, что сообщение обрабатывается; quarantine не трогает.
|
||||
8. Фоновый процесс API опрашивает `GET /internal/safety/v1/messages/tasks/{task_id}`:
|
||||
- финальный **`200 allow`** → S3-data, Bitrix24, статус «доставлено», realtime клиенту;
|
||||
- финальный **`403 deny`** → удаление quarantine, статус «отклонено», уведомление клиенту;
|
||||
- **`203 pending`** → повтор опроса с backoff.
|
||||
|
||||
## Поток работы с чатом: Битрикс24 -> клиент
|
||||
|
||||
1. Оператор отвечает клиенту в Битрикс24 Open Lines.
|
||||
2. Битрикс24 отправляет `ONIMCONNECTOR*` webhook/event в `bitrix-local-app`.
|
||||
3. `bitrix-local-app` проверяет `application_token`, нормализует payload и сохраняет idempotent inbox.
|
||||
4. `bitrix-local-app` обогащает событие данными из `dialog_sessions` и forward-ит в API, если `BITRIX_API_FORWARD_URL` включен.
|
||||
5. api-backend находит локальный диалог по `external_chat_id` (= `dialog_id`, см. [`arch-00-glossary.md`](arch-00-glossary.md)).
|
||||
6. api-backend сохраняет входящее сообщение в App DB (`sender_type=company`), а файл — в Selectel S3 (documents) с metadata в App DB. По факту сообщения API обновляет `Dialog.status`: входящее от оператора → `waiting_for_client`, исходящее от клиента → `waiting_for_company`.
|
||||
7. `bitrix-local-app` подтверждает доставку в Bitrix24 через `imconnector.send.status.delivery`.
|
||||
8. api-backend публикует событие для frontend через WebSocket/SSE. Если realtime недоступен, frontend получает сообщение через polling `GET /api/v1/dialogs/{dialog_id}/messages?after=...`.
|
||||
9. Frontend отображает сообщение оператора в чате.
|
||||
10. При получении от `bitrix-local-app` доменного события `dialog.closed` (Bitrix24 `ONIMCONNECTORDIALOGFINISH`) API переводит `Dialog.status` в `closed`.
|
||||
|
||||
## Документы компании (post-MVP)
|
||||
|
||||
Доставка документов из Bitrix24 в приложение **не входит в MVP** — см. [`!Backlog.md`](../../HAN_chat/!Backlog.md), п. 9.
|
||||
|
||||
В MVP блок профиля «Документы» и API `GET /api/v1/me/documents` зарезервированы; список может быть пустым. Контракт endpoint — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md).
|
||||
|
||||
## Профиль клиента
|
||||
|
||||
Профиль должен быть блочным.
|
||||
|
||||
Блок "Личные данные":
|
||||
|
||||
- ФИО;
|
||||
- гражданство;
|
||||
- номер телефона в РФ;
|
||||
- зарубежный номер телефона;
|
||||
- email.
|
||||
|
||||
Блок "Документы":
|
||||
|
||||
- перечень документов, отправленных клиенту компанией (в MVP — пустой до реализации бэклога);
|
||||
- дата отправки;
|
||||
- наименование документа;
|
||||
- возможность скачать документ (после реализации доставки).
|
||||
|
||||
Редактирование данных профиля недоступно.
|
||||
|
||||
### Sync профиля и master для PII
|
||||
|
||||
App DB — **локальный кэш** для UI. Двусторонний sync — `bitrix-sync` (имена полей — [`arch-00-glossary.md`](arch-00-glossary.md)):
|
||||
|
||||
- **Auth-телефон:** master — Keycloak (`UserIdentity.phone_number`); изменения могут инициировать `contact.update` через триггеры.
|
||||
- **Поля профиля для UI:** master — последнее успешно синхронизированное значение; основной входящий поток на MVP — правки сотрудником в Bitrix24 (webhook → App DB).
|
||||
- **App → Bitrix:** триггеры `han_app` → `sync_queue` (`contact.update`).
|
||||
- **Bitrix → App:** webhook робота → `bitrix-sync`; запись с GUC `han.sync_suppress` (без эхо в очередь).
|
||||
- **Конфликт:** побеждает более позднее событие (`updated_at`, audit в `bitrix_sync`).
|
||||
|
||||
## Аудит скачиваний
|
||||
|
||||
При выдаче presigned URL на скачивание (`GET .../download-url`, вложения чата) api-backend пишет audit-событие в App DB:
|
||||
|
||||
| Поле | Значение |
|
||||
|---|---|
|
||||
| `event_type` | `attachment.download_url_issued` / `document.download_url_issued` |
|
||||
| `user_id` | текущий пользователь из JWT |
|
||||
| `resource_type` | `attachment` / `document` |
|
||||
| `resource_id` | UUID сущности |
|
||||
| `ux_session_id` | из заголовка `X-Ux-Session-Id` |
|
||||
| `request_id` | из заголовка запроса |
|
||||
| `ip`, `user_agent` | из proxy headers |
|
||||
|
||||
В audit **не** сохраняются presigned URL, содержимое файлов и PII. Формат таблицы — в модуле `database`.
|
||||
|
||||
## Realtime (кратко)
|
||||
|
||||
Детальный контракт — [`arch-02-api-contracts.md`](arch-02-api-contracts.md), раздел «Realtime».
|
||||
|
||||
- Transport: WebSocket `WS /api/v1/realtime` (JWT).
|
||||
- Fallback: polling `GET /api/v1/dialogs/{dialog_id}/messages?after=...`.
|
||||
- События: новое сообщение, смена статуса сообщения/диалога.
|
||||
|
||||
## Принципы безопасности
|
||||
|
||||
- Все защищенные пользовательские API требуют валидный JWT.
|
||||
- Гостевые API доступны только для публичных настроек и стартового контента.
|
||||
- Все внешние пользовательские соединения работают через HTTPS.
|
||||
- HTTP допускается только для веб-домена как вход для редиректа на HTTPS. Для api домена HTTP не допускается.
|
||||
- TLS завершается на reverse proxy; внутренний HTTP между контейнерами допускается только в закрытой backend-сети.
|
||||
- TLS 1.0/1.1 и слабые шифры запрещены.
|
||||
- HSTS обязателен после проверки домена и сертификата.
|
||||
- INPUT-validation на api-backend
|
||||
- использовать только Параметризованные SQL-запросы
|
||||
- обязательное Экранирование вывода
|
||||
- настройка CORS только на разрешенные домены (указать в .env)
|
||||
- настройка Secure Headers (CSP, X-Frame-Options и др.)
|
||||
- Доступ к профилю, диалогам, сообщениям, файлам и документам ограничен текущим `user_id`.
|
||||
- Все запросы, содержащие в себе ссылку на сущность, которая относится к конкретному пользователю (ИД продукта, услуги, чата, документа и тп), проверяются backend_api на соответствие тому пользователю, от которого пришел запрос.
|
||||
- Все публичные id создаются в формате UUID.
|
||||
- Сервисные API защищаются внутренней сетью Docker/VPC плюс service token (перечень переменных — [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), «Service tokens (internal API)»).
|
||||
- Rate limits применяются минимум на двух уровнях: edge-лимиты в `nginx` и пользовательские лимиты в API с состоянием в Redis.
|
||||
- Входящие сообщения пользователя: синхронный `POST /internal/safety/v1/messages/check` → `200` | `403` | `203`; при `203` API опрашивает `task_id` до финального вердикта.
|
||||
- Файлы пользователя до финального `allow` только в S3-quarantine; в S3-data — после `200 allow`.
|
||||
- Клиент **не пишет** напрямую в S3; загрузка только через `api-backend`.
|
||||
- `message-safety` — read-only к S3-quarantine, без прав записи в бакеты.
|
||||
- Все изменяемые параметры, телефоны, лимиты, mime types и флаги хранятся в настройках ([`arch-04-settings-and-content.md`](arch-04-settings-and-content.md)).
|
||||
- PII-данные не пишутся в логи в открытом виде.
|
||||
- Документы и файлы чата должны иметь контроль доступа и аудит скачиваний.
|
||||
|
||||
## Backend-репозиторий и инфраструктура
|
||||
|
||||
### Состав backend-контура
|
||||
|
||||
Минимальный production-like контур на одной VM: `nginx`, `api-backend`, `message-safety`, `keycloak`, `bitrix-sync`, `bitrix-local-app`, `redis`, `otel-collector`. Managed PostgreSQL и Selectel S3 находятся вне Docker Compose.
|
||||
|
||||
### Предлагаемая структура backend-репозитория
|
||||
|
||||
```text
|
||||
backend/
|
||||
docker-compose.yml # корневой compose: nginx + include сервисов + networks/volumes
|
||||
.env.example
|
||||
nginx/
|
||||
docker-compose.yml
|
||||
nginx.conf
|
||||
conf.d/
|
||||
certs/
|
||||
.gitkeep
|
||||
api-backend/
|
||||
app/
|
||||
docker-compose.yml
|
||||
tests/
|
||||
pyproject.toml
|
||||
Dockerfile
|
||||
message-safety/
|
||||
app/
|
||||
docker-compose.yml
|
||||
tests/
|
||||
pyproject.toml
|
||||
Dockerfile
|
||||
bitrix-local-app/
|
||||
app/
|
||||
docker-compose.yml
|
||||
deploy/
|
||||
tests/
|
||||
pyproject.toml
|
||||
Dockerfile
|
||||
bitrix-sync/
|
||||
app/
|
||||
docker-compose.yml
|
||||
tests/
|
||||
pyproject.toml
|
||||
Dockerfile
|
||||
keycloak/
|
||||
docker-compose.yml
|
||||
realm/
|
||||
themes/
|
||||
providers/
|
||||
redis/
|
||||
docker-compose.yml
|
||||
observability/
|
||||
docker-compose.yml # сервис otel-collector
|
||||
otel-collector.yaml
|
||||
```
|
||||
|
||||
Детальная внутренняя структура каждого сервиса (`app/`, модули, миграции) определяется в профильных спецификациях модулей (TBD).
|
||||
|
||||
### Compose-контур
|
||||
|
||||
Корневой `backend/docker-compose.yml` подключает сервисные compose-файлы через `include`.
|
||||
|
||||
Публикация портов наружу разрешена только `nginx` (`80/443`). Остальные сервисы доступны через Docker-сети и private VPC.
|
||||
Reference in New Issue
Block a user