Закрыть открытые вопросы: ADR-015…022, бриф айдентики
Регистрация открытая (015), контакты и приглашения по нику с закреплением ключа и блокировкой (016), лимиты (017), идентификация устройства через случайный deviceId в IndexedDB и серверный fan-out (018), отказ от серверного перца (019), смена пароля не в v1 (020), интерфейс ru/en (021), айдентика «Скобы» по turn 2 канваса (022). Архитектура, модель угроз и README обновлены. В открытых вопросах остались два: моногарнитура с кириллицей (Fragment Mono её не содержит) и удаление аккаунта. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R2pCJkWyG2aYu48u1Vp1A1
This commit is contained in:
@@ -13,4 +13,4 @@ Email, телефон и OAuth тянут за собой внешние сер
|
||||
- Ноль внешних сервисов в цикле регистрации и входа.
|
||||
- Сервер не знает о пользователе ничего, кроме ника.
|
||||
- Восстановления доступа нет — пароль ещё и материал ключа (ADR-006).
|
||||
- Открытость регистрации (инвайты или нет) — открытый вопрос.
|
||||
- Регистрация открытая — ADR-015.
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
|
||||
- На сервере нет истории — компрометация сервера не раскрывает переписку.
|
||||
- Устройство, молчавшее больше 30 дней, теряет недоставленные сообщения. Осознанно.
|
||||
- Нужна идентификация устройства для очередей — открытый вопрос.
|
||||
- Идентификация устройства для очередей — ADR-018.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# ADR-015: Регистрация открытая
|
||||
|
||||
Закрывает открытый вопрос из [ADR-005](005-accounts.md).
|
||||
|
||||
## Контекст
|
||||
|
||||
ADR-005 оставил открытым, нужны ли инвайты. Инвайты — это сущность (код, выдача, срок, учёт), отдельный UI и барьер для первого контакта с продуктом. Bare — публичный инструмент, а не закрытый клуб.
|
||||
|
||||
## Решение
|
||||
|
||||
Регистрация открытая: ник и пароль, больше ничего. Инвайтов нет.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Любой может завести аккаунт, значит, любой может злоупотреблять. Защита — не на входе, а в механике общения: сообщения ходят только между взаимными контактами (ADR-016) и в пределах лимитов (ADR-017).
|
||||
- Ники занимаются по принципу «кто первый». Споров о никах сервер не разбирает.
|
||||
- Закрытая регистрация для приватных инсталляций — отдельным ADR, если понадобится. В v1 её нет.
|
||||
@@ -0,0 +1,24 @@
|
||||
# ADR-016: Контакты и приглашения — по нику
|
||||
|
||||
Закрывает открытый вопрос о механике добавления контакта.
|
||||
|
||||
## Контекст
|
||||
|
||||
Ник — единственный идентификатор пользователя (ADR-005). Ссылки-приглашения, QR-коды, поиск и каталог пользователей — отдельные сущности со своим жизненным циклом. При открытой регистрации (ADR-015) нужен ещё и механизм согласия: иначе любой, кто знает ник, может писать кому угодно.
|
||||
|
||||
## Решение
|
||||
|
||||
**Контакт.** Пользователь вводит точный ник. Сервер отдаёт публичный ключ и создаёт запрос контакта. Адресат видит запрос и принимает или отклоняет. Сообщения 1:1 ходят только между взаимными контактами. Поиска по части ника и каталога нет.
|
||||
|
||||
**Ключи.** Публичный ключ контакта сохраняется на устройстве при первом получении и больше с сервера не перечитывается. Если сервер когда-либо вернёт для этого ника другой ключ — клиент показывает предупреждение и не шифрует на новый ключ без явного подтверждения. Отпечаток ключа виден в карточке контакта; сверка вне канала — по желанию пользователя, автоматики нет.
|
||||
|
||||
**Комната.** Участник приглашает в комнату только своих контактов — добавлением в состав, без отдельного согласия: согласие уже дано на уровне контакта. Не хочешь — выходишь. Создатель комнаты может исключать участников. Любое изменение состава — rekey (ADR-007).
|
||||
|
||||
**Блокировка.** Отклонить запрос можно с блокировкой ника: сервер не доставляет от заблокированного ни сообщений, ни новых запросов.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Серверу достаточно таблиц контактов (пара ников, статус) и блокировок. Новых сущностей — инвайтов, ссылок, токенов — нет.
|
||||
- Ник надо знать заранее и передать вне Bare. Это ограничение осознанное: Bare — инструмент для людей, которые уже знакомы.
|
||||
- Закрепление ключа защищает от подмены ключа сервером после первого контакта. Первый контакт — доверие на слово (TOFU): активно-злонамеренный оператор в модели угроз и так за пределами защиты.
|
||||
- Спам ограничен структурно: чужому нельзя написать, пока он не принял запрос; сами запросы — под лимитами (ADR-017).
|
||||
@@ -0,0 +1,34 @@
|
||||
# ADR-017: Лимиты
|
||||
|
||||
Закрывает открытый вопрос о лимитах и антиспаме.
|
||||
|
||||
## Контекст
|
||||
|
||||
Открытая регистрация (ADR-015) и реле с очередями (ADR-008) требуют потолков: на размер сообщения, на частоту действий, на объём хранимого. Сервер не видит содержимого, поэтому антиспам может быть только структурным и количественным — фильтрации по тексту не существует.
|
||||
|
||||
## Решение
|
||||
|
||||
**Размеры.**
|
||||
- Сообщение — до 4 096 символов плейнтекста; проверяет клиент до шифрования.
|
||||
- Тело любого запроса к серверу — до 32 КиБ. Это единственная проверка размера на сервере.
|
||||
- Ник — 3–32 символа: латиница в нижнем регистре, цифры, `_`, `-`. Регистр не различается.
|
||||
|
||||
**Частота.** Token bucket в памяти сервера, без внешних хранилищ. Превышение — `429` с `Retry-After`.
|
||||
- Отправка сообщений: 60 в минуту на аккаунт.
|
||||
- Запросы контакта: 20 в сутки на аккаунт.
|
||||
- Регистрация: 5 в час с одного IP.
|
||||
- Вход: 10 попыток за 15 минут на ник, 30 — с одного IP. Argon2 дорогой; это защита процессора сервера в той же мере, что и аккаунтов.
|
||||
- Прочие запросы: 120 в минуту на аккаунт.
|
||||
|
||||
**Объёмы.**
|
||||
- Очередь устройства — до 10 000 сообщений; при переполнении старейшие удаляются. Плюс 30-дневный TTL из ADR-008.
|
||||
- Контактов у аккаунта — до 500. Участников в комнате — до 100. Устройств у аккаунта — до 10.
|
||||
|
||||
**Антиспам.** Сводится к трём вещам: писать можно только взаимным контактам (ADR-016), блокировка ника (ADR-016), лимиты выше. Жалоб и модерации нет: серверу нечего модерировать.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Все числа — константы в одном файле сервера. Клиент дублирует только длину сообщения и формат ника — для подсказок до отправки. Конфигурации нет: поменять лимит — поменять константу и пересобрать.
|
||||
- Перезапуск сервера сбрасывает счётчики. Для маленького инструмента это приемлемо.
|
||||
- Лимит по IP не различает людей за одним NAT. Осознанная грубость: цифры взяты с запасом.
|
||||
- Переполнение очереди теряет сообщения, как и TTL. Устройство, не выходившее на связь, получает не всё — это уже зафиксировано в ADR-008.
|
||||
@@ -0,0 +1,22 @@
|
||||
# ADR-018: Устройство — случайный идентификатор в IndexedDB
|
||||
|
||||
Закрывает открытый вопрос из [ADR-008](008-server-relay.md).
|
||||
|
||||
## Контекст
|
||||
|
||||
Очередь недоставленных сообщений — per-device (ADR-008), подписка на пуши — тоже per-device (ADR-011). Серверу нужно отличать устройства одного аккаунта. Стабильного идентификатора браузера не существует, и это правильно; его надо завести самим.
|
||||
|
||||
## Решение
|
||||
|
||||
- При первом запуске клиент генерирует 128-битный случайный `deviceId` (`crypto.getRandomValues`) и хранит его в IndexedDB рядом с историей. Одно хранилище — один жизненный цикл: стёрли данные сайта — исчезли и история, и идентичность устройства. Новый запуск — новое устройство.
|
||||
- При входе клиент передаёт `deviceId`. Сервер создаёт запись устройства с ключом (аккаунт, `deviceId`) и очередь к нему. Сессионная cookie привязана к устройству.
|
||||
- `deviceId` — не секрет, а адрес очереди. Аутентифицирует сессия. Коллизии между аккаунтами невозможны: ключ составной.
|
||||
- Доставка — серверный fan-out: сообщение раскладывается в очереди всех устройств всех участников, кроме отправившего. Отправитель не знает и не должен знать устройств адресата: все устройства одного аккаунта владеют одним приватным ключом (ADR-006), шифротекст один для всех.
|
||||
- Устройство без связи 30 дней удаляется вместе с очередью и push-подпиской — тот же срок, что TTL очереди (ADR-008). На следующем запуске клиент регистрируется как новое устройство; локальная история при этом цела.
|
||||
- Клиент при регистрации устройства передаёт короткую метку из user-agent («Firefox · Linux»). Список устройств с метками и датой последней связи виден в настройках; любое можно удалить — очередь и сессия уничтожаются.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Сообщение, отправленное с телефона, появляется и на ноутбуке — как новое, через его очередь. Это не синхронизация истории: то, что было до появления устройства, на него не приедет.
|
||||
- Один браузер в двух профилях или в режиме инкогнито — разные устройства. Инкогнито плодит устройства при каждом запуске; их чистит 30-дневный срок и лимит в 10 устройств (ADR-017).
|
||||
- Потеря `deviceId` (чистка storage) эквивалентна потере устройства: недоставленное в старую очередь пропадает по TTL. `navigator.storage.persist()` (ADR-009) снижает риск.
|
||||
@@ -0,0 +1,23 @@
|
||||
# ADR-019: Серверного «перца» для ключевого блоба нет
|
||||
|
||||
Закрывает открытый вопрос о дополнительном шифровании блоба серверным ключом.
|
||||
|
||||
## Контекст
|
||||
|
||||
Идея: шифровать ключевой блоб ещё раз серверным ключом, хранящимся вне базы. Тогда украденный дамп или бэкап сам по себе не даёт материала для оффлайн-перебора паролей. Против оператора это не помогает — у него есть оба.
|
||||
|
||||
## Решение
|
||||
|
||||
Перца нет. Блоб хранится так, как описано в ADR-006 и ADR-013: один слой шифрования, ключ выведен из пароля.
|
||||
|
||||
Аргументы:
|
||||
|
||||
- Появляется второй критичный артефакт рядом с базой. ADR-003 обещает «бэкап — копия одного файла»; перец это обещание ломает. Потеря файла с перцем — невозможность входа с новых устройств для всех пользователей сразу, а восстановления паролей в Bare нет by design. Для маленького самохостного инструмента риск потерять ключ при переезде выше риска, который он закрывает.
|
||||
- Сценарий «утёк бэкап, но не сервер» уже закрыт ADR-013: PBKDF2 с миллионом итераций и пароль от 12 символов делают перебор дорогим. Словарный пароль перец тоже не спасёт при компрометации сервера.
|
||||
- Каждый дополнительный слой — код, который нужно аудировать, и оперативная процедура, которую нужно помнить. Простота важнее.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Стойкость блоба в любом сценарии равна стойкости пароля, как и записано в модели угроз. Новых оговорок в ней не появляется.
|
||||
- Развёртывание остаётся «бинарь плюс файл базы». Ничего, что нельзя потерять, кроме самой базы, у оператора нет.
|
||||
- Решение обратимо: перец можно добавить позже отдельным ADR — он не меняет формат блоба, только оборачивает его.
|
||||
@@ -0,0 +1,18 @@
|
||||
# ADR-020: Смена пароля — не в v1
|
||||
|
||||
Закрывает открытый вопрос о сроках смены пароля.
|
||||
|
||||
## Контекст
|
||||
|
||||
Пароль — материал ключа, которым зашифрован ключевой блоб (ADR-006). Смена пароля — это расшифровка блоба старым ключом и перешифровка новым, целиком на клиенте; ECDH-пара и секрет аккаунта не меняются. Технически просто, но это отдельный сценарий с гонками между устройствами и отдельный UI.
|
||||
|
||||
## Решение
|
||||
|
||||
В v1 смены пароля нет. Появится позже отдельным ADR.
|
||||
|
||||
Формат блоба уже готов к этому: параметры KDF лежат рядом с блобом (ADR-013), тот же механизм перешифровки нужен для повышения итераций. Ключи и секрет аккаунта при смене пароля сохраняются, поэтому контакты, комнаты и экспортированные архивы останутся в силе.
|
||||
|
||||
## Следствия
|
||||
|
||||
- В v1 скомпрометированный пароль — скомпрометированный аккаунт, и путь один: новый аккаунт. Об этом надо прямо сказать при регистрации, рядом с «восстановления нет».
|
||||
- Серверная часть смены пароля — замена argon2-хеша и блоба одним запросом — тривиальна; откладывается целиком, чтобы не делать половину.
|
||||
@@ -0,0 +1,21 @@
|
||||
# ADR-021: Интерфейс на русском и английском, i18n своими руками
|
||||
|
||||
Закрывает открытый вопрос о языке интерфейса.
|
||||
|
||||
## Контекст
|
||||
|
||||
Аудитория Bare двуязычна. Библиотеки i18n на клиенте невозможны (ADR-001), и они не нужны: интерфейс чата — несколько десятков строк.
|
||||
|
||||
## Решение
|
||||
|
||||
- Два языка: русский и английский. Других в v1 нет.
|
||||
- Все строки интерфейса — в одном ES-модуле, два плоских словаря с одинаковыми ключами. Ничего сверх: никаких форматов файлов переводов, никаких внешних инструментов.
|
||||
- Язык при первом запуске — из `navigator.language` (ru → русский, всё остальное → английский). Переключатель в настройках, выбор хранится в `localStorage`.
|
||||
- Склонения и множественные формы — руками, через `Intl.PluralRules`. Даты и время — через `Intl.DateTimeFormat` с выбранной локалью.
|
||||
- Документация остаётся на русском.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Добавить язык — добавить словарь; удалить — удалить. Ключи проверяются на полноту простым скриптом при желании, не сборкой.
|
||||
- Ники и содержимое сообщений языком интерфейса не затрагиваются.
|
||||
- Текст в UI короткий и строчный — по голосу бренда (ADR-022). Переводить надо в том же регистре и тоне.
|
||||
@@ -0,0 +1,21 @@
|
||||
# ADR-022: Визуальная айдентика — «Скобы», моно-интерфейс без баблов
|
||||
|
||||
Закрывает открытый вопрос о визуальной айдентике.
|
||||
|
||||
## Контекст
|
||||
|
||||
Айдентика исследовалась в Claude Design: шесть направлений знака, два развиты до мини-айдентик, затем интерфейс чата на базе одной из них. Исследование — [канвас «Исследование айдентики Bare»](https://claude.ai/design/p/0126e4da-40e4-449c-bfbb-7aefc7b64659?file=Bare+Identity.dc.html).
|
||||
|
||||
## Решение
|
||||
|
||||
Работаем с turn 2 канваса: интерфейс на базе мини-айдентики 1h «Скобы», одна гарнитура — моноширинная, сообщения без баблов. Артборды 2a (десктоп, 1120) и 2b (мобильный, 390) — референс для вёрстки клиента.
|
||||
|
||||
Подробности — палитра, типографика, правила знака, структура экрана — в [docs/identity/brief.md](../identity/brief.md). Бриф — источник истины для клиента; канвас — история исследования.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Знак — четыре угла рамки, внутри пусто. Без градиентов, теней, маскотов и пузырей.
|
||||
- Цвет акцента — только для скоб в акцентных случаях и статусов. Не для кнопок и заливок.
|
||||
- Сообщения — строки: автор, время, текст. Эстетика лога, а не мессенджера.
|
||||
- Шрифт в продукте не загружается с внешних хостов: либо файл в репозитории, либо системный моно-стек. Внешний запрос за шрифтом — утечка метаданных и зависимость, несовместимые с философией.
|
||||
- Выбор конкретной моногарнитуры для кириллицы — открытый вопрос (см. бриф).
|
||||
Reference in New Issue
Block a user