Skip to main content
/account/privacy — это один экран, потому что вопросы, на которые он отвечает, задают за один присест: в каком состоянии мой аккаунт, как со мной можно связаться, на что я согласился, что вы на самом деле храните и как мне уйти. Разнесение их по разным маршрутам сделало бы последний — тот, за которым люди приходят рассерженными, — самым труднонаходимым. Переход сюда — из центра на /account/profile, раздел Персональные данные. У двух из этих вопросов ответ не «всё, о чём вы просили». Запрос на удаление — это запрос, а не удаление, а экспорт намеренно оставляет за скобками несколько категорий. Обе оговорки сказаны на экране, в документе и здесь, потому что приватностный интерфейс, преувеличивающий сделанное, хуже того, который делает меньше.

Состояние аккаунта и сроки хранения

Первая карточка — собственный статус аккаунта: Активен, Приостановлен или Удалён — с описанием этого состояния, а следом те из этих моментов, которые заданы: когда приостановлен, когда удалён, когда обезличен и до какого момента хранятся записи. Датированные моменты — и есть смысл карточки. Экран, показывающий только статус, не дал бы человеку возможности понять, означает ли «удалён», что данных больше нет. Каждый необязательный момент — это JSON null, когда события не было, а не нулевое время, так что «никогда не приостанавливался» и «приостановлен в начале времён» невозможно перепутать.
Удаление, записанное сегодня из панели оператора, задаёт статус, но не пишет окно хранения, поэтому у клиента в таком состоянии строка Записи хранятся до остаётся пустой. Строка появляется, когда окно задано.
Статус «приостановлен» или «удалён» также завершает все живые сессии: клиентские сессии разрешаются через статус аккаунта, поэтому изменение вступает в силу на следующем запросе, а не по истечении срока.

Контактные каналы

В разделе Как мы с вами связываемся — по одной строке на адрес: вид, сам адрес, значок «подтверждён» или «не подтверждён» и два переключателя, проговаривающие, что туда можно отправлять: служебные сообщения и предложения с новостями. Под списком — форма добавления: выберите 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 не настроен, контактные каналы всё равно перечисляются — сам факт, что канал такого-то вида существует, клиент вправе знать, — но без адресов, и предупреждающая карточка сообщает об этом до скачивания.
  • Если объявленные документом изъятия отличаются от того, что показал предпросмотр на экране, панель сообщает об этом постфактум. Это означало бы, что сервер передумал между двумя вызовами, и это ровно тот случай, который клиент не должен обнаруживать, сравнивая файл со страницей.
Панель никогда не отрисовывает документ. Отрисовка оставила бы его в положении прокрутки, на снимке экрана и в кэше «назад-вперёд» браузера. Он собирается в blob, сохраняется как omniflow-personal-data-YYYY-MM-DD.json, и объектный URL немедленно отзывается. Строка аудита account.data.exported пишется после сборки и до передачи, так что зафиксированный экспорт — это всегда тот, который действительно удался, а его метаданные несут только количество строк: строка аудита, описывающая, что было раскрыто, не должна становиться второй копией этого.
Экспорт — это POST, хотя он не меняет ничего, что клиент может увидеть. GET кэшировался бы, предзагружался бы, попадал бы в строку доступа прокси и был бы достижим по навигации, а это самый чувствительный документ, который производит API.

Запрос на удаление

1

Напишите причину

Обязательна, не более 400 символов, со счётчиком в реальном времени. Это свободный текст, а не меню, потому что закрытый список вложил бы категории оператора в уста клиента. Счётчик соблюдает ограничение и в браузере, так что никого не остановит отклонённый запрос после написанного абзаца.
2

Подтвердите

За кнопкой следует диалог. Только после него отправляется {"confirm": true, "reason": "…"}; без этого флага маршрут отвечает 422 confirmation_required.
3

Прочитайте панель ожидания

Раздел превращается в предупреждающую панель с датой запроса, фразой, называющей того, кто его исполняет, собственной причиной клиента и кнопкой Отменить запрос за собственным диалогом.
Маршрут отвечает 202 Accepted, а не 200, и полезная нагрузка явно несёт executedBy: "operator_retention_workflow". Разница между «запрошено» и «сделано» — это всё свойство безопасности данного маршрута, и экран, который об этом молчит, склоняет клиента поверить, что его данных уже нет. Запрос на удаление требует входа не старше 15 минут; устаревшая сессия отвечает 401 reauthentication_required, и панель показывает предупреждение со ссылкой обратно на вход до нажатия кнопки, а не после. Свежая аутентификация — это то, что не даёт сделать запрос с одолженного ноутбука; она не является разрешением его исполнить, поэтому запрос по-прежнему остаётся только запросом. Отмена не требует повторной аутентификации, потому что отмена всегда только предотвращает необратимое. Повторение запроса ничего не меняет и лишь добавило бы шума в запись, которую оператор читает перед тем, как действовать, поэтому второй запрос отклоняется, а не принимается молча. Отмена не стирает запрос. Событие жизненного цикла остаётся, потому что записи об идентичности работают только на добавление: «об этом попросили, а потом отозвали» — другой факт, нежели «об этом никогда не просили», и оператор, рассматривающий более поздний запрос, заслуживает видеть оба. Признак «в ожидании» выводится сравнением трёх моментов — самого нового запроса на удаление, самой новой отмены и самого нового удаления или обезличивания, — и запрос находится в ожидании только тогда, когда он самый свежий из трёх. Отозванный или уже исполненный запрос сохраняет свои отметки времени, но теряет причину в ответе: те слова были написаны для решения, которое больше не открыто, а их повторение на экране подразумевает, что оно всё ещё открыто.

Что удаление не убирает

Удаление выполняет оператор через элементы управления жизненным циклом клиента, и это не та же операция, что обезличивание. Обезличивание необратимо, выполняется только над уже удалённым аккаунтом и именно оно на самом деле убирает идентифицирующие данные:
  • каждая идентичность отзывается, а её субъект у провайдера заменяется на anonymized:<id>;
  • шифротекст каждого контактного канала обнуляется, а его отпечаток заново выводится из идентификатора строки, оба флага доставки снимаются;
  • сопоставление с Remnawave теряет идентификатор Telegram и наблюдаемое состояние;
  • тело каждого сообщения поддержки в обращениях клиента превращается в [anonymized];
  • реферальный код удаляется.
Намеренно уцелевает финансовая и аудиторская запись. Заказ, профинансировавший проводку, нельзя убрать, не разбалансировав книгу проводок, поэтому обезличенный клиент сохраняет свою финансовую историю и теряет всё, что идентифицирует того, кто её создал. То же правило управляет воркером хранения, который удаляет одноразовые операционные данные — истёкшие сессии бота, брошенные оформления заказов, обработанные события вебхуков, истёкшие вложения поддержки, старую телеметрию, разрешённые записи о расхождениях — и никогда не трогает финансовую, аудиторскую, согласительную, идентификационную историю и историю прав на услуги, которая работает только на добавление всё время жизни установки. Вложения поддержки — единственное видимое клиенту с собственными часами: строка вложения истекает через 90 дней после создания, и затем очистка освобождает сохранённый файл, когда на него больше не ссылается ни одна уцелевшая строка.
Клиентские сессии, магические ссылки и видимый клиенту журнал безопасности объявляют сроки хранения в коде — 30 дней для мёртвой строки сессии, 180 дней для события безопасности, — но сегодня ни одно запланированное задание их не применяет, поэтому по этому расписанию строки не удаляются. Всё, что их касается, по-прежнему видно клиенту на /account/security и отзывается оттуда.

Чего 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' и политику разрешений, запрещающую камеру, микрофон, геолокацию и тематические когорты интересов. Причина, названная в коде, та же, что и выше: ответ клиенту может содержать ссылку на подписку, а это учётные данные.

Справочник

Смежные страницы

Уведомления

Согласие, тихие часы, ограничение частоты и лента объявлений.

Клиентская веб-панель

Вход, сессии и остальная часть интерфейса аккаунта.

Подключение устройства

Где живёт ссылка на подписку и как она ротируется.

Безопасность

Взгляд оператора: учётные данные, сессии, секреты и транспорт.