Flutter Client / User Flows
Пути пользователя и диаграммы вызовов
Пошаговое описание процессов регистрации, шифрования, обмена ключами и WebRTC-звонков.
Сценарий А: Первичная регистрация и настройка E2EE
Этот путь выполняется при первом входе пользователя в аккаунт после регистрации или при сбросе ключей шифрования.
Последовательность действий
Пользователь Клиент (Flutter) Бэкенд (FastAPI)
│ │ │
├─ Вводит логин/пароль ──────>│ │
│ и нажимает "Войти" ├─ POST /auth/login ───────────>│
│ │<─ Возвращает JWT токены ──────┤
│ │ │
│ (Перенаправление на экран настройки аккаунта) │
├─ Вводит мастер-пароль ─────>│ │
│ ├─ Генерирует пару X25519 │
│ │ ключей (CryptoService) │
│ ├─ Производит KDF (PBKDF2) │
│ │ мастер-пароля │
│ ├─ Шифрует приватный ключ │
│ │ с AES-256-GCM │
│ │ │
│ ├─ POST /auth/setup-account ───>│ (Сохраняет
│ │ (Имя, Публичный ключ, │ публичный ключ
│ │ зашифрованный приватник) │ и зашифрованный
│ │ │ приватник)
│ │<─ Статус 200 OK ──────────────┤
│ │ │
│ ├─ Сохраняет приватный ключ в │
│ │ SecureStorage и ОЗУ-кэш │
▼ ▼ ▼
Сценарий Б: Начало чата и обмен ключами (Диффи-Хеллман)
Диаграмма показывает, как два пользователя устанавливают защищенный канал связи без передачи секретного ключа через сервер.
Схема обмена ключами
Алиса (Отправитель) Сервер (FastAPI) Боб (Получатель)
│ │ │
├─ Создает чат с Бобом │ │
├─ GET /users/by-username/bob ─────>│ │
│<─ Возвращает PubKey_Bob (X25519) ─┤ │
│ │ │
├─ Вычисляет общий секрет: │ │
│ ECDH(PrivKey_Alice, PubKey_Bob) │ │
├─ Кэширует общий ключ в ОЗУ │ │
│ │ │
├─ Шифрует текст с AES-GCM (shared) │ │
├─ Отправляет по сокету: ──────────>│ │
│ {"type": "private_message", ├─ Пересылает по сокету ───────>│
│ "content": "Base64(cipher)"} │ ├─ Получает событие
│ │ ├─ Проверяет локальный кэш
│ │ ├─ Если ключа нет:
│ │ │ скачивает PubKey_Alice
│ │ ├─ Вычисляет общий секрет:
│ │ │ ECDH(PrivKey_Bob, PubKey_Alice)
│ │ ├─ Дешифрует сообщение
▼ ▼ ▼
Сценарий В: WebRTC P2P видеозвонок
Связь между устройствами устанавливается через сигнальные сообщения по WebSockets.
Установление WebRTC соединения
Вызывающий (Алиса) WebSocket Сервер Вызываемый (Боб)
│ │ │
├─ Инициирует звонок │ │
├─ WS: {"type": "call_init"} ──────>│ │
│ ├─ Отправляет high-priority │
│ │ FCM пуш для пробуждения │
│ ├─ WS: Forward call_init ──────>│
│ │ ├─ Звонит телефон
│ │ ├─ Нажимает "Принять"
│ │<─ WS: {"type": "call_accept"}─┤
│<─ WS: Forward call_accepted ──────┤ │
│ │ │
│ (Алиса и Боб создают RTCPeerConnection и захватывают камеры) │
├─ Создает SDP Offer │ │
├─ WS: {"type": "offer"} ──────────>│ │
│ ├─ WS: Forward offer ──────────>│
│ │ ├─ Устанавливает Offer
│ │ ├─ Создает SDP Answer
│ │<─ WS: {"type": "answer"} ─────┤
│<─ WS: Forward answer ─────────────┤ │
│ │ │
│ (Параллельный обмен кандидатами ICE для пробития NAT) │
├─ WS: {"type": "ice_candidate"} ──>│ │
│ ├─ WS: Forward candidate ──────>│
│ │<─ WS: {"type": "ice_candidate"}
│<─ WS: Forward candidate ──────────┤ │
│ │ │
│<═════════════ УСТАНОВЛЕНО ПРЯМОЕ P2P СОЕДИНЕНИЕ ═════════════════>│
▼ ▼ ▼
Сценарий Г: Сопряжение десктопного приложения через QR-код
Диаграмма авторизации устройства-сателлита (ПК) с помощью основного телефона.
Схема сопряжения
Новое устройство (Десктоп) WebSocket Сервер Смартфон (Авторизован)
│ │ │
├─ Подключается к /ws/qr │ │
│ с room_id = UUID │ │
├─ Рисует на экране QR-код ─────────┼──────────────────────────────>│ Сканирует QR-код
│ │ ├─ Читает room_id
│ │ ├─ Расшифровывает свой
│ │ │ X25519 приватный ключ
│ │ ├─ Шифрует приватный ключ
│ │ │ ключом ПК (из QR-кода)
│ │ ├─ POST /auth/qr/approve
│ │<──────────────────────────────┤ (передает room_id, JWT,
│ │ │ зашифрованный приватник)
│ ├─ Находит комнату room_id │
│<─ WS: Отправляет JWT-токен ───────┤ │
│ и E2EE-ключи │ │
├─ Сохраняет ключи, │ │
│ закрывает WebSocket сопряжения │ │
▼ ▼ ▼