Files
bare/docs/decisions/017-devices-and-envelope.md
T
mayatnikovandClaude Fable 5 ae55c846aa ADR-015…024 и спецификации v1: криптография, протокол, хранение, UI, деплой, план
Закрыты все открытые вопросы проектирования. Пароль не покидает клиент
(два ключа из мастера), TOFU для публичных ключей, устройства и конверт,
атомарный rekey комнат, регистрация и контакты, схема SQLite и драйвер
без cgo, сессии и CSRF, деплой через nginx+systemd, правила пушей,
айдентика «Скобы» на системном mono. Иконки PWA в web/icons.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
2026-08-22 10:53:24 +03:00

4.8 KiB
Raw Blame History

ADR-017: Устройства, конверт сообщения и доставка

Контекст

Очередь per-device (ADR-008) требует идентификации устройства. Формат конверта определяет, какие метаданные видит сервер, — это часть модели угроз, а не деталь реализации. Время сообщения: клиентский ULID (ADR-009) несёт часы клиента, которые врут.

Решение

Устройство. Клиент при первом входе на устройстве генерирует deviceId — 16 случайных байт, base64url — и хранит его в IndexedDB. Регистрирует через POST /api/devices; идентификатор принадлежит аккаунту. Заголовок X-Device обязателен на запросах, где важно устройство: ACK, отправка (чтобы не возвращать эхо отправившему устройству), push-подписка; поток событий получает устройство в query — EventSource не умеет заголовки. Устройство, не появлявшееся 90 дней, удаляется вместе с очередью и подпиской.

Конверт. JSON, открытые поля: id (ULID, генерирует клиент), to ({dm: nick} или {room: id}), from (ставит сервер из сессии, клиентское значение игнорируется), keyId ("dm" для 1:1, идентификатор ключа для комнаты), iv, ct, ts (миллисекунды сервера). Внутри шифротекста — JSON {t: текст}. Открытые поля привязаны к шифротексту через AAD: bare-msg-v1|id|chat|from|keyId, где chatdm:a:b (ники по возрастанию) или room:id.

Часы. Сервер принимает сообщение, только если метка времени в ULID отличается от серверных часов не больше чем на 5 минут; иначе 400 clock_skew и клиент просит проверить часы. ULID присваивается в момент попытки отправки, не в момент набора: отложенное офлайном сообщение получает свежий идентификатор при повторе. Сортировка — по id, отображение времени — по ts.

Доставка. POST /api/messages в одной транзакции кладёт конверт в очередь каждого устройства каждого получателя (в 1:1 получатели — оба ника, в комнате — все участники), кроме устройства-отправителя. Подключённым по SSE устройствам конверт отправляется сразу. POST /api/ack {ids} удаляет конверты из очереди устройства. При подключении SSE сервер сначала отдаёт всю очередь устройства, затем event: ready, затем живые события. Каждые 20 секунд — комментарий-пинг.

Идемпотентность. Повторный POST с тем же id после ACK получателей породит повторную доставку; клиент сливает по id и дублей не показывает. Сервер не хранит историю идентификаторов — это противоречило бы ADR-008.

Лимиты. Текст — до 4000 символов, тело запроса — до 32 КиБ. Rate limiting — ADR-021.

Следствия

  • Серверу видны: кто, кому или в какую комнату, когда и какого размера. Ровно то, что модель угроз и так относит к метаданным.
  • Отправитель получает своё сообщение на другие устройства тем же путём, что и получатели: мультидевайс без отдельной логики.
  • Устройство определяется браузерным профилем: два браузера на одном телефоне — два устройства.
  • Чистка IndexedDB браузером стирает deviceId; следующий вход создаёт новое устройство, старое отомрёт по сроку.