Перейти к содержанию

Права комнаты и доверие к участникам

Сервер «Черновика» теперь сохраняет принятые изменения, поэтому ошибка допуска стала бы долговременной ошибкой документа. В этом уроке добавим роль читателя и рассмотрим, где именно проверяются права. Анна остаётся редактором комнаты draft-42, Борис получает доступ только для чтения. Обмен CRDT-операциями сохраняет прежнюю форму, но сервер разрешает изменяющие сообщения лишь участнику с соответствующей ролью.

Состояние lesson-10 продолжает локальную форму входа и cookie-сессии. Роль определяется на сервере для заранее заданного участника. Браузер получает её для оформления интерфейса, однако запрет поля сам по себе не является защитой. Пользователь способен изменить DOM, заменить клиентский скрипт или отправить собственный пакет по открытому соединению.

Четыре разные проверки

Проверка происхождения, личности, комнаты и роли отвечает на разные вопросы. Origin описывает страницу, которая начала браузерное соединение. Сессия связывает запрос с участником. Имя комнаты выбирает ресурс. Роль определяет допустимые операции с выбранным ресурсом. Ни одна из этих проверок не должна незаметно подменять остальные: знание адреса комнаты не подтверждает аккаунт, а наличие аккаунта не предоставляет все права.

В учебном upgrade-handler эти условия проверяются до принятия WebSocket. Сервер допускает только известный путь и фиксированную комнату, действующую сессию и разрешённое локальное происхождение. После upgrade проверка срока сессии продолжается при обработке сообщений и периодически для существующих соединений. Иначе старое соединение могло бы сохранять доступ неограниченно долго после истечения входа.

Ниже показан точный фрагмент принятия update из server.mjs. Объект member построен сервером из сессии, а не прочитан из браузерного сообщения. doc, persist, broadcast и send определены в полном файле. Разрешение проверяется до применения двоичных данных к общей копии.

   else if(m.type==='update'){
    if(member.role!=='editor'){send(ws,{type:'error',message:'Только чтение'});return;}
    Y.applyUpdate(doc,Buffer.from(m.data,'base64'),ws);
    try{persist();}catch{failed=true;for(const client of clients.keys()) client.close(1011,'storage failure');return;}
    broadcast({type:'update',data:m.data},ws);
    if(LESSON>=9)send(ws,{type:'ack',seq:m.seq});
   }

После самостоятельного входа Анна должна иметь редактируемое поле, а Борис — получать общий текст с запрещённым вводом. Если клиент Бориса всё же отправит пакет update, сервер должен вернуть сообщение «Только чтение» и не применить его. Это ожидаемая ветвь кода; попытки обхода и работа ролей в браузере при подготовке не запускались.

UI и серверная граница

Свойство disabled объясняет человеку, какие действия сейчас доступны, и предотвращает случайный ввод. Это часть удобства интерфейса. Серверный запрет отвечает за целостность общего состояния независимо от выбранной клиентской программы. Хорошая система использует обе части: понятный UI снижает число ошибок, а серверная проверка защищает общий ресурс.

При первом сетевом обмене provider отправляет локальные недостающие updates только редактору. Читатель может получать документ и присутствие, но не должен автоматически загружать на сервер свою старую локальную историю. Это особенно важно после смены роли: IndexedDB может содержать правки, сделанные раньше. Наличие таких байтов в браузере не означает, что текущий доступ разрешает их принятие.

CRDT не умеет сам определить, какие операции «написаны до отзыва роли» и какие «созданы после». Сервер принимает пакет в текущем контексте доступа. Если бизнес-процесс хочет принять часть старых автономных правок, нужен отдельный механизм рассмотрения или импорта. Нельзя просто довериться клиентской отметке времени, которую можно изменить, или внутреннему идентификатору реплики, который не является аккаунтом.

Срок и отзыв роли

В примере сессия содержит срок действия и назначенную роль. Такой снимок роли удобен для локального упражнения, но не демонстрирует мгновенный административный отзыв. Для реального сервиса сервер должен обращаться к актуальной политике доступа или закрывать соединения при её изменении. Один раз проверенный upgrade не защищает документ от пользователя, чьё право было отменено позже.

Представьте, что Анна оставалась офлайн час, а её участие в комнате удалили. Клиент продолжает иметь локальный черновик и может редактировать его в памяти. После возвращения сервер обязан отклонить доступ по актуальным правилам. UI должен сообщить, что локальные данные доступны для экспорта или обращения к владельцу, а не обещать успешное слияние только потому, что updates корректны по формату.

Наличие локальной копии после отзыва также отдельный вопрос конфиденциальности. Запретить серверную загрузку новых правок легче, чем гарантировать исчезновение уже переданного содержимого из устройства пользователя. Если документ секретный, политика должна определить допустимость офлайн-кеша и поведения выхода. Система ролей не может отменить факт, что браузер раньше уже получил байты.

Подтверждённое присутствие

Имя участника в awareness заменяется сервером на значение из сессии. Браузерный user.name не используется для решения о роли. Такая нормализация предотвращает очевидную подмену отображаемого имени, но awareness всё равно остаётся временным каналом интерфейса. Он не заменяет журнал принятых операций с серверным автором и временем.

Сервер также ограничивает идентификатор состояния присутствия текущим соединением. Иначе один клиент мог бы отправить удаление чужого client ID и заставить интерфейс скрыть другого участника. При этом идентификатор самого документа остаётся внутренней деталью Yjs: пользователь не получает дополнительные права из-за совпадения ID. Подтверждённая сессия и допустимый набор сообщений важнее клиентских меток.

Размер области полномочий

Наша учебная политика описывает одну комнату и две роли. В реальном портале список комнат может принадлежать организации, а доступ зависеть от проекта, приглашения и этапа согласования. Такие условия должны вычисляться на сервере для конкретного ресурса. Не передавайте через query параметр role=editor и не принимайте решение только по флагу интерфейса; это превращает политику доступа в пользовательскую настройку.

Локальные пароли из окружения не являются полноценной системой аккаунтов. Сессии здесь хранятся в памяти, отдельный процесс после перезапуска их не знает, а cookie рассчитана на локальный HTTP. Перед публичным выпуском понадобятся HTTPS, защищённые параметры cookie, реальное хранение учётных данных и определённая политика отзыва. Эти ограничения не уменьшают учебный результат: проверка изменяющего сообщения находится в правильном месте.

Для самостоятельного разбора проследите весь путь пакета Бориса от WebSocket до ветви роли. Затем объясните, почему изменение надписи «читатель» на «редактор» в DOM не должно менять итог. В следующем уроке добавим пользовательскую историю ревизий и отмену своих правок. Они будут опираться на доступ, но останутся отдельными механизмами, поскольку ни роль, ни CRDT не создают готовую историю продукта.

Оглавление серии · Исходные состояния проекта.

API сверены с первичной документацией 11 октября 2026 года. Пример имеет статус not_run: фактический запуск и наблюдение результата читателю предстоит выполнить самостоятельно.