Files
bare/docs/decisions/043-form-before-rights.md
mayatnikovandClaude Opus 5 05586218e1 Этап 3: TOFU и комнаты — ключ комнаты, атомарный rekey, участники
Сервер: комнаты, состав и завёрнутые ключи; смена состава и rekey одним
атомарным запросом с проверкой keys[].to против итогового состава; обрезка
до двух последних ключей; передача владения по joined_at; события room
каждому со своим ключом и room_left удалённым; членство и keyId в проверках
сообщения; миграция 002.

Клиент: TOFU на всех путях, по которым публичный ключ доходит до клиента;
предупреждение о смене ключа с блокировкой отправки и повторной расшифровкой
сохранённого raw; заворачивание и разворачивание ключей комнаты, включая
себе — тем же кодом, без ветвления; расшифровка любым известным keyId;
экраны участников, создание комнаты, карточка контакта с двумя отпечатками.

ADR-037: roomId генерирует клиент. crypto.md вплетает roomId в заворачивание,
а оно происходит до запроса — создатель обязан привязать ключ к идентификатору,
которого по прежнему протоколу ещё не существовало.
ADR-039: завёрнутый ключ принимается только от участника. Иначе сервер
подставляет ключ, завёрнутый посторонним аккаунтом, TOFU молчит — ник
незнакомый, первый ключ запоминается молча, — и комната уезжает на ключ
сервера. Одно подменённое поле в ответе, без сговора и подмены кода.
ADR-041: долг по rekey — состояние комнаты, а не свойство события. Владелец,
офлайн в момент выхода участника, не узнавал о долге никогда, и комната
навсегда оставалась на ключе, который вышедший знает.
ADR-038, 040, 042, 043, 044: тексты экранов комнат, снятие pending при
возврате прежнего ключа, порядок ключей, форма раньше прав, экран покинутой
комнаты.

Приёмка на боевом: комната на троих, добавленный четвёртый читает только
новое, вышедший после rekey новых не получает и писать не может,
keys_mismatch, key_exists, not_owner, owner, room_conflict, чужой X-Device.
Подмена public_key в базе даёт у собеседника предупреждение и блокирует
отправку — проверено в Chrome.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
2026-08-22 22:18:13 +03:00

3.0 KiB
Raw Permalink Blame History

ADR-043: Форма запроса проверяется раньше прав

Уточняет ADR-026: перечень кодов исчерпывающий, значит и порядок их выдачи должен быть записан.

Контекст

docs/protocol.md перечисляет проверки POST /api/rooms/{id}/members в одном порядке — «Только владелец (403 not_owner). Проверки: все add существуют…», — а сервер отвечает в другом: форму ников и завёрнутых ключей он разбирает до обращения к хранилищу, то есть до проверки владения. Порядок наружу виден: не владелец с кривым ником в add получал не not_owner.

Иначе и не сделать: чтобы спросить хранилище о правах, запрос сначала надо разобрать. Утечки в этом нет — проверка чисто синтаксическая и о комнате ничего не сообщает.

Два кода при этом расходились с документом. Повтор ника в keys[].to отвечал 400 invalid, хотя множество keys[].to составу в этом случае не равно и протокол называет 400 keys_mismatch. Ошибка формы ника в add отвечала 404 unknown_user, а та же ошибка в remove400 invalid: один класс входа, два разных ответа.

Решение

  • Форма запроса проверяется раньше прав и раньше существования сущностей. Записано строкой в «Общих правилах» docs/protocol.md: 400 bad_json, 413 too_large и 400 invalid приходят и на запрос, который отвергли бы и по правам.
  • Повтор ника в keys[].to400 keys_mismatch, как и любое другое несовпадение с итоговым составом.
  • Ник неверной формы в add400 invalid с полем add, как и в remove. Несуществующий ник верной формы остаётся 404 unknown_user.

Следствия

  • Перечень кодов остаётся исчерпывающим, а порядок их выдачи — записанным, а не выведенным из чтения кода.
  • Снаружи по ответу видно, что запрос разобран, но не видно ничего о комнате: 403 not_owner одинаков и для чужой комнаты, и для несуществующей.
  • Клиенту разница не важна: свои ники он приводит к форме ADR-019 до запроса.