/account/privacy — это один экран, потому что вопросы, на которые он отвечает,
задают за один присест: в каком состоянии мой аккаунт, как со мной можно
связаться, на что я согласился, что вы на самом деле храните и как мне уйти.
Разнесение их по разным маршрутам сделало бы последний — тот, за которым люди
приходят рассерженными, — самым труднонаходимым. Переход сюда — из центра на
/account/profile, раздел Персональные данные.
У двух из этих вопросов ответ не «всё, о чём вы просили». Запрос на удаление —
это запрос, а не удаление, а экспорт намеренно оставляет за скобками несколько
категорий. Обе оговорки сказаны на экране, в документе и здесь, потому что
приватностный интерфейс, преувеличивающий сделанное, хуже того, который делает
меньше.
Состояние аккаунта и сроки хранения
Первая карточка — собственный статус аккаунта: Активен, Приостановлен или Удалён — с описанием этого состояния, а следом те из этих моментов, которые заданы: когда приостановлен, когда удалён, когда обезличен и до какого момента хранятся записи. Датированные моменты — и есть смысл карточки. Экран, показывающий только статус, не дал бы человеку возможности понять, означает ли «удалён», что данных больше нет. Каждый необязательный момент — это JSONnull, когда события не было, а не
нулевое время, так что «никогда не приостанавливался» и «приостановлен в начале
времён» невозможно перепутать.
Удаление, записанное сегодня из панели оператора, задаёт статус, но не пишет
окно хранения, поэтому у клиента в таком состоянии строка Записи хранятся
до остаётся пустой. Строка появляется, когда окно задано.
Контактные каналы
В разделе Как мы с вами связываемся — по одной строке на адрес: вид, сам адрес, значок «подтверждён» или «не подтверждён» и два переключателя, проговаривающие, что туда можно отправлять: служебные сообщения и предложения с новостями. Под списком — форма добавления: выберите Email, телефон или Telegram, введите значение, задайте два переключателя, нажмите Добавить адрес. Удалить открывает диалог подтверждения с названным значением. Добавление адреса фиксирует намерение и ничего больше. Панель не может доказать, что адрес принадлежит тому, кто его печатает, поэтому флаги — это разрешение, которое читает конвейер доставки, а не утверждение, что адрес подтверждён, и форма об этом говорит. Как хранится значение. Адрес запечатывается AES-GCM на ключе данных установки, а ограничение уникальности стоит на HMAC нормализованного значения с ключом, а не на обычном хеше, — поэтому украденную базу нельзя проверить на известный адрес, перебирая хеши кандидатов. Вид канала входит в дополнительные аутентифицируемые данные, так что значение, запечатанное как почта, нельзя воспроизвести как номер телефона. Значение, которое не удаётся открыть — обычно после ротации ключа, — возвращается пустым, а не роняет весь список. Нормализация тут не косметика. Почта приводится к нижнему регистру; номер телефона теряет пробелы, дефисы и скобки; имя пользователя Telegram приводится к нижнему регистру с удалением@. Без неё +7 900 000 00 00 и +79000000000
были бы двумя каналами для одного телефона, потому что ограничение стоит на MAC
значения.
Отказы.
Ответ о конфликте говорит, что адрес недоступен, и ничего больше. Сообщать
вызывающему, что адрес принадлежит другому аккаунту, значит превратить панель в
оракул, отвечающий, является ли конкретный человек клиентом здесь, поэтому
средство решения — обращение в поддержку. Единственное исключение не раскрывает
ничего: строка, которой этот клиент уже владеет, вместо этого снимается с
отзыва и переразмечается флагами.
Ограничение частоты запросов отказывает при сбое. Если до ограничителя не
достучаться, запрос отклоняется, чтобы авария не могла тихо снять защиту.
Удаление отзывает, а не стирает, поэтому адрес нельзя тихо перенести между
аккаунтами, удалив его из одного, а история согласий продолжает указывать на
что-то существующее. Добавление канала с включёнными предложениями добавляет
запись о согласии; удаление последнего канала, у которого предложения были
включены, добавляет запись об отзыве — записать отзыв, пока остаётся другой
канал с согласием, значило бы исказить выбор клиента. Каждое добавление и
удаление пишет строку аудита с видом и маркетинговым флагом, но никогда со
значением.
Экран настроек показывает те же каналы с их
флагами, но никогда адреса: тот маршрут вообще не выбирает шифротекст.
След согласий
«Подписан ли я на предложения?» — первый вопрос. «Когда я на это согласился и на каких условиях?» — второй, и установка, умеющая отвечать только на первый, не может показать клиенту, почему старый выбор продолжает действовать после изменения условий.consent_records работает только на добавление. Каждая строка несёт цель,
признак того, дано согласие или отозвано, версию условий, поверхность, откуда
оно пришло, и идентификатор запроса, который его записал. Текущий ответ по цели —
это просто самая новая строка по ней; ничто не редактируется на месте, потому
что история, которую можно переписать, ничего не доказывает.
Экран перечисляет по одной строке на цель со значком Дано или Отозвано и
строкой происхождения, а также раскрывающуюся Полную историю с датированным
следом за ней, ограниченным 25 последними решениями.
Нераспознанная цель отображается со своим исходным именем и общей подсказкой, а
не прячется.
След здесь доступен только для чтения. Маркетинговое согласие меняется
переключателями на контактных каналах выше и маркетинговым переключателем на
/account/preferences; эти два пути проставляют разное происхождение —
account-web-v0.10 / account_web из формы контактов, web-v0.10 /
customer_web с экрана настроек и bot-v0.4 / telegram_bot с экрана настроек
в Telegram. Поэтому клиент, читающий собственный след, увидит по одной и той же
цели больше одной версии условий. Каждая строка при этом остаётся точной записью
того решения, которое её породило.
Экспорт ваших данных
Карточка Скачать ваши данные перечисляет, что войдёт в экспорт и что он намеренно оставляет за скобками, до нажатия кнопки, а затем синхронно формирует весь документ и сохраняет его на устройство. Он синхронный, а не поставленный в очередь, намеренно. Экспорту в очереди нужен канал доставки, окно хранения и ссылка, аутентифицирующая сама себя, — три дополнительных решения о раскрытии ради ответа на вопрос, который умещается в один ответ HTTP. Ничего не пишется на диск, ничего не отправляется почтой, и документ существует в этом единственном ответе и больше нигде.Что в нём есть
Документ объявляет
version: "omniflow.account.export/1", поэтому клиент,
сохранивший экспорт годичной давности, — и оператор, читающий его вместе с ним —
могут понять, значения полей какой версии применимы.
Что не вошло и почему
Список изъятий поставляется внутри документа, а не только на экране, потому что экспорт, который тихо что-то опускает, читается как полный, а клиент не может спросить об отсутствии, которого не видит.
Внутренние заметки оператора по обращению не входят в список изъятий, потому что
их вообще нет на пути экспорта: они живут в собственной таблице, и ни один
клиентский запрос её не касается. Личность оператора опущена в строках
жизненного цикла — называть человека, приостановившего аккаунт, в документе,
который получает владелец этого аккаунта, — это вопрос безопасности, а не
прозрачности.
Ограничения и отказы
- Три экспорта в час, с ключом по идентификатору клиента, потому что
ограничивается именно то, как часто данные одного аккаунта можно вытащить из
базы. Превышение отвечает 429
rate_limited. Ограничитель отказывает при сбое. - Каждый список ограничен 1000 строк, и любой раздел, упёршийся в ограничение, назван в документе и показан на экране. Клиент, которому сказали, что его данные полны, когда это не так, введён в заблуждение ровно по тому единственному вопросу, ради ответа на который документ и существует.
- Если
APP_DATA_ENCRYPTION_KEYне настроен, контактные каналы всё равно перечисляются — сам факт, что канал такого-то вида существует, клиент вправе знать, — но без адресов, и предупреждающая карточка сообщает об этом до скачивания. - Если объявленные документом изъятия отличаются от того, что показал предпросмотр на экране, панель сообщает об этом постфактум. Это означало бы, что сервер передумал между двумя вызовами, и это ровно тот случай, который клиент не должен обнаруживать, сравнивая файл со страницей.
omniflow-personal-data-YYYY-MM-DD.json, и объектный URL
немедленно отзывается.
Строка аудита account.data.exported пишется после сборки и до передачи, так что
зафиксированный экспорт — это всегда тот, который действительно удался, а его
метаданные несут только количество строк: строка аудита, описывающая, что было
раскрыто, не должна становиться второй копией этого.
Экспорт — это
POST, хотя он не меняет ничего, что клиент может увидеть.
GET кэшировался бы, предзагружался бы, попадал бы в строку доступа прокси и
был бы достижим по навигации, а это самый чувствительный документ, который
производит API.Запрос на удаление
1
Напишите причину
Обязательна, не более 400 символов, со счётчиком в реальном времени. Это
свободный текст, а не меню, потому что закрытый список вложил бы категории
оператора в уста клиента. Счётчик соблюдает ограничение и в браузере, так
что никого не остановит отклонённый запрос после написанного абзаца.
2
Подтвердите
За кнопкой следует диалог. Только после него отправляется
{"confirm": true, "reason": "…"}; без этого флага маршрут отвечает 422
confirmation_required.3
Прочитайте панель ожидания
Раздел превращается в предупреждающую панель с датой запроса, фразой,
называющей того, кто его исполняет, собственной причиной клиента и кнопкой
Отменить запрос за собственным диалогом.
executedBy: "operator_retention_workflow". Разница между «запрошено» и
«сделано» — это всё свойство безопасности данного маршрута, и экран, который об
этом молчит, склоняет клиента поверить, что его данных уже нет.
Запрос на удаление требует входа не старше 15 минут; устаревшая сессия отвечает
401 reauthentication_required, и панель показывает предупреждение со ссылкой
обратно на вход до нажатия кнопки, а не после. Свежая аутентификация — это то,
что не даёт сделать запрос с одолженного ноутбука; она не является разрешением
его исполнить, поэтому запрос по-прежнему остаётся только запросом. Отмена не
требует повторной аутентификации, потому что отмена всегда только
предотвращает необратимое.
Повторение запроса ничего не меняет и лишь добавило бы шума в запись, которую
оператор читает перед тем, как действовать, поэтому второй запрос отклоняется, а
не принимается молча.
Отмена не стирает запрос. Событие жизненного цикла остаётся, потому что
записи об идентичности работают только на добавление: «об этом попросили, а
потом отозвали» — другой факт, нежели «об этом никогда не просили», и оператор,
рассматривающий более поздний запрос, заслуживает видеть оба. Признак
«в ожидании» выводится сравнением трёх моментов — самого нового запроса на
удаление, самой новой отмены и самого нового удаления или обезличивания, — и
запрос находится в ожидании только тогда, когда он самый свежий из трёх.
Отозванный или уже исполненный запрос сохраняет свои отметки времени, но теряет
причину в ответе: те слова были написаны для решения, которое больше не открыто,
а их повторение на экране подразумевает, что оно всё ещё открыто.
Что удаление не убирает
Удаление выполняет оператор через элементы управления жизненным циклом клиента, и это не та же операция, что обезличивание. Обезличивание необратимо, выполняется только над уже удалённым аккаунтом и именно оно на самом деле убирает идентифицирующие данные:- каждая идентичность отзывается, а её субъект у провайдера заменяется на
anonymized:<id>; - шифротекст каждого контактного канала обнуляется, а его отпечаток заново выводится из идентификатора строки, оба флага доставки снимаются;
- сопоставление с Remnawave теряет идентификатор Telegram и наблюдаемое состояние;
- тело каждого сообщения поддержки в обращениях клиента превращается в
[anonymized]; - реферальный код удаляется.
Чего Omniflow никогда не показывает и не хранит
Это не упущения. Каждое из них — решение, видимое в коде, и каждое чего-то стоит, поэтому о них стоит сказать. Никаких аппаратных идентификаторов в списке устройств./account/devices
показывает иконку платформы, модель, которую клиент сообщил о себе, и дату.
Дескриптор для удаления устройства — это 128-битный хеш аппаратного
идентификатора, и он разрешается повторным хешированием текущего списка
устройств, а не по сохранённому сопоставлению, поэтому вызывающий никогда не
передаёт тот идентификатор, над которым выполняется действие. На этом экране нет
ни HWID, ни IP-адреса: достаточно, чтобы клиент узнал собственный ноутбук, и
недостаточно, чтобы построить журнал перемещений.
Никаких IP-адресов за пределами собственного экрана безопасности клиента.
Список сессий и журнал событий аккаунта на /account/security намеренно несут
IP — адрес, который клиент не узнаёт, и есть вся причина существования этого
экрана. Ни то ни другое не является разделом экспорта, а сетевые идентификаторы —
объявленное изъятие, поэтому в экспорт не попадает ни один IP.
Никакой ссылки на подписку там, где она задержится. Ответ дашборда её не
несёт; её запрашивает по требованию только экран подключения, потому что
полезная нагрузка дашборда, всегда несущая её, положила бы учётные данные в
каждый кэш, лог и запись истории, которые касаются страницы. QR-код формируется
в браузере и рисуется из data-URL, а не проходит через эндпоинт изображений,
который отправил бы полезную нагрузку с учётными данными на сервер для
кэширования. Ротация ссылки записывает событие безопасности с меткой подписки и
ничем больше — поместить ссылку в собственный журнал безопасности клиента значило
бы обессмыслить ту самую ротацию, которую эта запись фиксирует. См.
подключение устройства.
Никакого свободного текста в журнале безопасности клиента. Словарь событий
закрыт и обеспечивается базой данных: signed_in, signed_out,
signed_out_all, session_revoked, magic_link_requested, identity_linked,
identity_unlinked, subscription_key_rotated, device_removed,
devices_removed_all. Здесь нет столбца ни для суммы, ни для ссылки, ни для
идентификатора другой стороны. Это отдельная таблица от журнала аудита
операторов, потому что аудитория другая, а решение по каждой строке, какие поля
клиенту можно видеть, поместило бы решение о раскрытии в путь чтения.
Никаких пересылаемых сообщений Telegram. Каждое сообщение бота отправляется с
включённой защитой контента Telegram и отключёнными предпросмотрами ссылок,
поэтому снимок экрана со ссылкой на подписку, заказом или перепиской поддержки
нельзя переслать или сохранить из чата. Доставляемая ботом ссылка для входа
защищена тем же правилом, и её текст говорит об этом прямо: ссылка работает один
раз, истекает через десять минут, и кто её откроет, войдёт как клиент.
Никакого клиентского содержимого в операторской группе. Уведомления,
публикуемые в операторскую группу Telegram, называют событие, класс суммы и
идентификаторы, которые оператор может посмотреть. Они никогда не несут текст
клиента, ссылку на подписку, токен или платёжную полезную нагрузку; нагрузки
редактируются при постановке в очередь и ещё раз при отрисовке по белому списку
полей. См. операторские уведомления.
Никакой индексации, никакого кэширования. Панель аккаунта задаёт
noindex, nofollow на уровне макета, а каждый ответ /v1/account несёт
Cache-Control: no-store, Referrer-Policy: no-referrer,
X-Content-Type-Options: nosniff, политику безопасности контента
default-src 'none' и политику разрешений, запрещающую камеру, микрофон,
геолокацию и тематические когорты интересов. Причина, названная в коде, та же,
что и выше: ответ клиенту может содержать ссылку на подписку, а это учётные
данные.
Справочник
Смежные страницы
Уведомления
Согласие, тихие часы, ограничение частоты и лента объявлений.
Клиентская веб-панель
Вход, сессии и остальная часть интерфейса аккаунта.
Подключение устройства
Где живёт ссылка на подписку и как она ротируется.
Безопасность
Взгляд оператора: учётные данные, сессии, секреты и транспорт.