Закрыть открытые вопросы: ADR-015…022, бриф айдентики #1
@@ -12,6 +12,7 @@ Bare — маленький независимый инструмент, а не
|
||||
- [Архитектура](docs/architecture.md) — обзор системы
|
||||
- [Модель угроз](docs/threat-model.md) — от чего защищаемся и от чего нет
|
||||
- [Решения](docs/decisions/) — ADR по ключевым решениям
|
||||
- [Айдентика](docs/identity/brief.md) — знак, цвет, типографика, экран
|
||||
- [Открытые вопросы](docs/open-questions.md)
|
||||
|
||||
## Статус
|
||||
|
||||
+33
-3
@@ -10,9 +10,9 @@ Bare — это PWA-клиент на ванильных веб-технолог
|
||||
|
||||
## Аккаунты
|
||||
|
||||
Регистрация — ник и пароль. Ник уникален и является идентификатором пользователя. Без email, телефона, OAuth и интеграций. Восстановления пароля нет.
|
||||
Регистрация открытая — ник и пароль, инвайтов нет. Ник уникален и является идентификатором пользователя: 3–32 символа, латиница в нижнем регистре, цифры, `_`, `-`. Без email, телефона, OAuth и интеграций. Восстановления пароля нет. Смены пароля в v1 тоже нет — появится позже: формат блоба это уже позволяет.
|
||||
|
||||
Серверная аутентификация: Argon2id, сессия в httpOnly cookie.
|
||||
Серверная аутентификация: Argon2id, сессия в httpOnly cookie, привязанная к устройству.
|
||||
|
||||
Пароль — не короче 12 символов; правил про регистры и спецсимволы нет: длина важнее состава. UI рекомендует парольную фразу из нескольких слов и при регистрации прямо говорит: пароль — это ключ шифрования, а не запись в базе; восстановления нет.
|
||||
|
||||
@@ -28,9 +28,27 @@ Bare — это PWA-клиент на ванильных веб-технолог
|
||||
|
||||
Forward secrecy — осознанный non-goal v1.
|
||||
|
||||
## Контакты и комнаты
|
||||
|
||||
Контакт добавляется по точному нику: сервер отдаёт публичный ключ и создаёт запрос, адресат принимает или отклоняет. Сообщения 1:1 ходят только между взаимными контактами. Поиска и каталога нет; ссылок и QR — тоже.
|
||||
|
||||
Публичный ключ контакта закрепляется на устройстве при первом получении. Если сервер вернёт для ника другой ключ — предупреждение, шифрование на новый ключ только после явного подтверждения. Отпечаток виден в карточке контакта.
|
||||
|
||||
В комнату приглашают только своих контактов, добавлением в состав без отдельного согласия. Создатель комнаты исключает участников; выйти может любой. Каждое изменение состава — rekey.
|
||||
|
||||
Отклонить запрос контакта можно с блокировкой: сервер не доставляет от заблокированного ни сообщений, ни новых запросов.
|
||||
|
||||
## Устройства
|
||||
|
||||
Устройство — 128-битный случайный `deviceId`, сгенерированный клиентом при первом запуске и хранящийся в IndexedDB рядом с историей. Стёрли данные сайта — новое устройство. `deviceId` — адрес очереди, не секрет; аутентифицирует сессия.
|
||||
|
||||
Доставка — серверный fan-out: сообщение раскладывается в очереди всех устройств всех участников, кроме отправившего. Все устройства аккаунта владеют одним приватным ключом, шифротекст один для всех. Отправитель устройств адресата не знает.
|
||||
|
||||
Устройство без связи 30 дней удаляется вместе с очередью и push-подпиской. Список устройств с метками из user-agent — в настройках; любое можно удалить.
|
||||
|
||||
## Хранение
|
||||
|
||||
Сервер хранит только три вещи: аккаунты (ник, argon2-хеш, зашифрованный ключевой блоб), метаданные комнат и контактов, транзитную очередь зашифрованных недоставленных сообщений. Очередь per-device: доставлено и подтверждено ACK — удалено с сервера; не забрано за 30 дней — удалено.
|
||||
Сервер хранит только три вещи: аккаунты (ник, argon2-хеш, зашифрованный ключевой блоб, устройства), метаданные комнат, контактов и блокировок, транзитную очередь зашифрованных недоставленных сообщений. Очередь per-device: доставлено и подтверждено ACK — удалено с сервера; не забрано за 30 дней — удалено; больше 10 000 сообщений — старейшие удаляются. Серверного «перца» для ключевого блоба нет: развёртывание — бинарь плюс один файл базы, ничего, что нельзя потерять, кроме самой базы.
|
||||
|
||||
Клиент хранит историю в IndexedDB. messageId — ULID/UUIDv7: хронологическая сортировка и идемпотентный merge. Составной индекс (chatId, messageId). Пагинация курсором по ~50 сообщений, виртуализация списка в DOM.
|
||||
|
||||
@@ -54,6 +72,18 @@ iOS: пуши работают только у PWA, установленного
|
||||
|
||||
Сервер обрабатывает 404/410 от push-сервисов и чистит мёртвые подписки.
|
||||
|
||||
## Лимиты
|
||||
|
||||
Сообщение — до 4 096 символов плейнтекста, проверяет клиент. Тело запроса — до 32 КиБ, проверяет сервер. Частота — token bucket в памяти сервера, превышение — `429` с `Retry-After`: 60 сообщений в минуту, 20 запросов контакта в сутки, 5 регистраций в час с IP, 10 попыток входа за 15 минут на ник. Контактов — до 500, участников комнаты — до 100, устройств — до 10. Все числа — константы в одном файле сервера, конфигурации нет.
|
||||
|
||||
Антиспам структурный: писать можно только взаимным контактам, блокировка ника, лимиты. Модерации нет — серверу нечего модерировать.
|
||||
|
||||
## Интерфейс
|
||||
|
||||
Два языка, русский и английский: два плоских словаря в одном ES-модуле, выбор по `navigator.language` с переключателем в настройках. Склонения — `Intl.PluralRules`, даты — `Intl.DateTimeFormat`.
|
||||
|
||||
Айдентика — «Скобы»: знак из четырёх углов, моноширинная гарнитура для всего, сообщения строками без баблов, палитра bone/ink/stone с одним красным акцентом. Бриф — `docs/identity/brief.md`. Шрифт не грузится с внешних хостов.
|
||||
|
||||
## Scope v1
|
||||
|
||||
Чаты 1:1 и комнаты. Только текст и эмодзи (эмодзи — юникод, отдельной фичи нет). Экспорт/импорт истории. Пуши на всех платформах. Всё остальное — за пределами v1.
|
||||
|
||||
@@ -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). Бриф — источник истины для клиента; канвас — история исследования.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Знак — четыре угла рамки, внутри пусто. Без градиентов, теней, маскотов и пузырей.
|
||||
- Цвет акцента — только для скоб в акцентных случаях и статусов. Не для кнопок и заливок.
|
||||
- Сообщения — строки: автор, время, текст. Эстетика лога, а не мессенджера.
|
||||
- Шрифт в продукте не загружается с внешних хостов: либо файл в репозитории, либо системный моно-стек. Внешний запрос за шрифтом — утечка метаданных и зависимость, несовместимые с философией.
|
||||
- Выбор конкретной моногарнитуры для кириллицы — открытый вопрос (см. бриф).
|
||||
@@ -0,0 +1,65 @@
|
||||
# Айдентика Bare — бриф
|
||||
|
||||
Зафиксировано в [ADR-022](../decisions/022-visual-identity.md). Источник — turn 2 [канваса](https://claude.ai/design/p/0126e4da-40e4-449c-bfbb-7aefc7b64659?file=Bare+Identity.dc.html), артборды 2a и 2b. Этот документ — источник истины для клиента.
|
||||
|
||||
## Идея
|
||||
|
||||
Bare не добавляет — он кадрирует. Четыре угла держат внимание на содержимом, которого сам бренд не касается. Система живёт на разметочной эстетике: тонкие линии, моно-подписи, много воздуха.
|
||||
|
||||
## Знак
|
||||
|
||||
Четыре уголка рамки, из которой вынули содержимое. Знак — пустота внутри.
|
||||
|
||||
- Внутрь рамки ничего не помещать.
|
||||
- Знак всегда одноцветный: ink, bone или mark. Без градиентов, теней, обводок.
|
||||
- Скобы можно раздвигать, «кадрируя» заголовок — пропорции углов неизменны.
|
||||
- Охранное поле — длина одного уголка.
|
||||
- Favicon и иконка приложения: на 16 px штрих — 9 единиц сетки, зазоры между углами в пропорции 3:2. Углы масштабируются без потерь. На тёмном и акцентном фоне знак всегда светлый.
|
||||
- Минимальный размер знака — 12 px.
|
||||
|
||||
Wordmark — `bare`, строчными, той же моногарнитурой, что и интерфейс.
|
||||
|
||||
## Цвет
|
||||
|
||||
| Токен | Значение | Роль |
|
||||
|---|---|---|
|
||||
| bone | `#F7F5F0` | фон |
|
||||
| ink | `#1B1917` | текст, знак |
|
||||
| stone | `#A9A59D` | вторичный текст, метаданные |
|
||||
| mark | `oklch(55% 0.19 20)` | акцент: скобы в акцентных случаях, статусы, непрочитанное |
|
||||
|
||||
Служебные серые из мока: `#3C3B38`, `#6E6D68` — промежуточные тона текста; `#DEDCD6`, `#E7E3DA` — линии и разделители.
|
||||
|
||||
Акцент — красный регистрационных меток. Не для кнопок и заливок. Интерактивное обозначается не цветом, а положением и подчёркиванием.
|
||||
|
||||
Тёмной темы в моке нет. Появится — отдельным артбордом и правкой этого брифа.
|
||||
|
||||
## Типографика
|
||||
|
||||
Одна гарнитура — моноширинная, для всего: wordmark, интерфейс, сообщения, метаданные. Вторая гарнитура (Familjen Grotesk из мини-айдентики 1h) в turn 2 снята.
|
||||
|
||||
Размеры из мока: 10, 11, 12, 13, 14, 15 px для интерфейса; 32 px — wordmark. Текст сообщений — 14–15 px.
|
||||
|
||||
Регистр — строчный везде, включая заголовки и кнопки. Имена людей — строчными, `@ник`; комнаты — `#имя`.
|
||||
|
||||
**Открытый вопрос — гарнитура.** В моке — Fragment Mono, но она не содержит базовой кириллицы (U+0400–045F): русский текст в моке рендерится системным fallback. Для ru-интерфейса (ADR-021) нужен выбор:
|
||||
|
||||
- моногарнитура с кириллицей под OFL, один файл `.woff2` в репозитории — JetBrains Mono или IBM Plex Mono;
|
||||
- или системный стек `ui-monospace, monospace` — ноль загрузок, вид зависит от ОС.
|
||||
|
||||
В любом случае шрифт не грузится с внешних хостов (ADR-022).
|
||||
|
||||
## Экран
|
||||
|
||||
Сообщения без баблов. Каждое — строка: автор, время, текст. Последовательные сообщения одного автора группируются под одним именем.
|
||||
|
||||
**Десктоп (2a, 1120).** Две колонки.
|
||||
|
||||
- Слева — сайдбар: wordmark; раздел «каналы» со списком `#комнат`, счётчик непрочитанных у имени; раздел «личные» со списком `@контактов`; внизу — «ты: @ник».
|
||||
- Справа — чат: шапка с именем комнаты и строкой «N онлайн · тема: …»; лента с разделителями дат и разделителем «новые» перед непрочитанным; внизу поле ввода с префиксом `>` и плейсхолдером «сообщение в #имя», под ним подсказка «enter — отправить».
|
||||
|
||||
**Мобильный (2b, 390).** Одна колонка: шапка с именем и «N онлайн», лента, поле ввода. Список чатов — отдельный экран.
|
||||
|
||||
## Голос
|
||||
|
||||
Короткие фразы, никакого маркетингового глянца. Строчные буквы. Пример из мока: «браузер и есть приложение».
|
||||
@@ -2,11 +2,5 @@
|
||||
|
||||
Решения по этим пунктам ещё не приняты. Каждое принятое решение уходит в ADR и вычёркивается отсюда.
|
||||
|
||||
- Регистрация: открытая или по инвайтам?
|
||||
- Механика добавления контакта и приглашения в комнату: по нику? по ссылке?
|
||||
- Лимиты: длина сообщения, rate limiting, антиспам.
|
||||
- Смена пароля (= перешифровка ключевого блоба): в v1 или позже?
|
||||
- Идентификация устройства для per-device очередей.
|
||||
- Серверный «перец» для ключевого блоба: дополнительное шифрование блоба серверным ключом, хранящимся вне базы. Плюс: дамп базы сам по себе перестаёт быть материалом для оффлайн-перебора. Минус: не защищает от оператора; потеря серверного ключа — невозможность входа с новых устройств для всех. Решение отложено.
|
||||
- Язык интерфейса (ru/en); нужна ли i18n.
|
||||
- Визуальная айдентика: отдельный бриф будет добавлен в `docs/identity/`.
|
||||
- Моногарнитура для кириллицы. Fragment Mono из мока не содержит базовой кириллицы (U+0400–045F): русский интерфейс в моке рендерится системным fallback. Варианты: JetBrains Mono или IBM Plex Mono одним `.woff2` в репозитории; системный стек `ui-monospace, monospace`. Внешних загрузок шрифта не будет в любом случае (ADR-022). Подробнее — `docs/identity/brief.md`.
|
||||
- Удаление аккаунта. ADR-014 упоминает перерегистрацию под тем же ником — она невозможна без удаления. Решить: есть ли удаление в v1, что происходит с комнатами (rekey без участника) и очередями.
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
|
||||
**Сетевой наблюдатель.** HTTPS обязателен. Наблюдатель видит факт и объём трафика к серверу, не содержимое.
|
||||
|
||||
**Кража базы или бэкапа.** В базе нет ничего сверх того, что видит оператор: те же шифротексты и метаданные.
|
||||
**Кража базы или бэкапа.** В базе нет ничего сверх того, что видит оператор: те же шифротексты и метаданные. Ключевые блобы в дампе — материал для оффлайн-перебора паролей; цена перебора задана ADR-013. Серверного «перца» нет (ADR-019): второй критичный артефакт рядом с базой опаснее для маленькой инсталляции, чем сценарий, который он закрывает.
|
||||
|
||||
**Спам и незнакомцы.** Регистрация открытая. Написать можно только взаимному контакту: запрос надо принять. Отклонение с блокировкой — и сервер больше не доставляет от этого ника ничего. Частота запросов и сообщений ограничена (ADR-017).
|
||||
|
||||
**Push-инфраструктура.** Пуш не несёт содержимого: сервер не знает плейнтекста, поэтому его нет и в пуше.
|
||||
|
||||
@@ -20,10 +22,14 @@
|
||||
|
||||
**Активно-злонамеренный оператор.** Оператор, способный подменить клиентский код, может украсть ключи и плейнтекст. Это фундаментальный предел web-E2EE: клиент каждый раз загружается с сервера. Смягчение — открытый код и клиент из нескольких читаемых файлов без сборки: подмену можно заметить глазами. Гарантии нет.
|
||||
|
||||
Второй путь того же оператора — подмена публичного ключа при добавлении контакта. Первый контакт — доверие на слово (TOFU). Дальше ключ закреплён на устройстве: подмена после первого контакта видна как предупреждение (ADR-016). Отпечаток ключа доступен для сверки вне канала; автоматической защиты первого контакта нет.
|
||||
|
||||
**Метаданные.** Кто, с кем, когда и сообщениями какого размера обменивается — серверу видно. Скрытие метаданных — не задача Bare.
|
||||
|
||||
**Компрометация устройства.** История лежит на устройстве в открытом виде (IndexedDB). Доступ к устройству — доступ к истории. Защита устройства — зона ответственности пользователя и ОС.
|
||||
|
||||
**Утечка пароля.** В v1 пароль сменить нельзя (ADR-020). Узнавший пароль владеет аккаунтом — ключами, контактами, входящими сообщениями — до тех пор, пока владелец не заведёт новый аккаунт.
|
||||
|
||||
**Слабый пароль.** Пароль — материал ключа. Ключевой блоб хранится на сервере, и его стойкость к оффлайн-перебору равна стойкости пароля. Гарантия «оператор не читает сообщения» действует в пределах стойкости пароля пользователя: слабый пароль — слабое E2EE. Это осознанная цена парольного мультидевайса. Контрмеры (ADR-013): PBKDF2-HMAC-SHA256 с не менее чем 600 000 итераций, пароль от 12 символов, рекомендация парольной фразы в UI.
|
||||
|
||||
**Собеседник.** E2EE не защищает от участника чата: получатель может сохранить, переслать, сфотографировать. «Удалить у всех» после доставки не существует.
|
||||
|
||||
Reference in New Issue
Block a user