> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Персональные данные и приватность

> Что клиент может увидеть, скачать и попросить удалить, что сохраняется в любом случае и чего Omniflow намеренно никогда не хранит

`/account/privacy` — это один экран, потому что вопросы, на которые он отвечает,
задают за один присест: в каком состоянии мой аккаунт, как со мной можно
связаться, на что я согласился, что вы на самом деле храните и как мне уйти.
Разнесение их по разным маршрутам сделало бы последний — тот, за которым люди
приходят рассерженными, — самым труднонаходимым. Переход сюда — из центра на
`/account/profile`, раздел **Персональные данные**.

У двух из этих вопросов ответ не «всё, о чём вы просили». Запрос на удаление —
это запрос, а не удаление, а экспорт намеренно оставляет за скобками несколько
категорий. Обе оговорки сказаны на экране, в документе и здесь, потому что
приватностный интерфейс, преувеличивающий сделанное, хуже того, который делает
меньше.

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

Первая карточка — собственный статус аккаунта: **Активен**, **Приостановлен**
или **Удалён** — с описанием этого состояния, а следом те из этих моментов,
которые заданы: когда приостановлен, когда удалён, когда обезличен и до какого
момента хранятся записи.

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

> [!NOTE]
>
> Удаление, записанное сегодня из панели оператора, задаёт статус, но не пишет
> окно хранения, поэтому у клиента в таком состоянии строка **Записи хранятся
> до** остаётся пустой. Строка появляется, когда окно задано.

Статус «приостановлен» или «удалён» также завершает все живые сессии: клиентские
сессии разрешаются через статус аккаунта, поэтому изменение вступает в силу на
следующем запросе, а не по истечении срока.

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

В разделе **Как мы с вами связываемся** — по одной строке на адрес: вид, сам
адрес, значок «подтверждён» или «не подтверждён» и два переключателя,
проговаривающие, что туда можно отправлять: служебные сообщения и предложения с
новостями. Под списком — форма добавления: выберите Email, телефон или Telegram,
введите значение, задайте два переключателя, нажмите **Добавить адрес**.
**Удалить** открывает диалог подтверждения с названным значением.

Добавление адреса фиксирует намерение и ничего больше. Панель не может доказать,
что адрес принадлежит тому, кто его печатает, поэтому флаги — это разрешение,
которое читает конвейер доставки, а не утверждение, что адрес подтверждён, и
форма об этом говорит.

**Как хранится значение.** Адрес запечатывается AES-GCM на ключе данных
установки, а ограничение уникальности стоит на HMAC нормализованного значения с
ключом, а не на обычном хеше, — поэтому украденную базу нельзя проверить на
известный адрес, перебирая хеши кандидатов. Вид канала входит в
дополнительные аутентифицируемые данные, так что значение, запечатанное как
почта, нельзя воспроизвести как номер телефона. Значение, которое не удаётся
открыть — обычно после ротации ключа, — возвращается пустым, а не роняет весь
список.

**Нормализация тут не косметика.** Почта приводится к нижнему регистру; номер
телефона теряет пробелы, дефисы и скобки; имя пользователя Telegram приводится к
нижнему регистру с удалением `@`. Без неё `+7 900 000 00 00` и `+79000000000`
были бы двумя каналами для одного телефона, потому что ограничение стоит на MAC
значения.

**Отказы.**

| Условие | Ответ |
| - | - |
| Ни один переключатель не включён | 422 — адрес, сохранённый без причины |
| Адрес уже зарегистрирован где-либо | 409 `contact_unavailable`, без указания аккаунта |
| Более десяти изменений контактов за час | 429 `rate_limited` |
| `APP_DATA_ENCRYPTION_KEY` не настроен | 503 `contacts_unavailable`, и раздел читается как «здесь не предлагается» |

Ответ о конфликте говорит, что адрес недоступен, и ничего больше. Сообщать
вызывающему, что адрес принадлежит другому аккаунту, значит превратить панель в
оракул, отвечающий, является ли конкретный человек клиентом здесь, поэтому
средство решения — обращение в поддержку. Единственное исключение не раскрывает
ничего: строка, которой **этот** клиент уже владеет, вместо этого снимается с
отзыва и переразмечается флагами.

Ограничение частоты запросов отказывает при сбое. Если до ограничителя не
достучаться, запрос отклоняется, чтобы авария не могла тихо снять защиту.

**Удаление отзывает, а не стирает**, поэтому адрес нельзя тихо перенести между
аккаунтами, удалив его из одного, а история согласий продолжает указывать на
что-то существующее. Добавление канала с включёнными предложениями добавляет
запись о согласии; удаление последнего канала, у которого предложения были
включены, добавляет запись об отзыве — записать отзыв, пока остаётся другой
канал с согласием, значило бы исказить выбор клиента. Каждое добавление и
удаление пишет строку аудита с видом и маркетинговым флагом, но никогда со
значением.

[Экран настроек](https://omniflow-vpn.mintlify.site/ru/customer/notifications.md) показывает те же каналы с их
флагами, но никогда адреса: тот маршрут вообще не выбирает шифротекст.

## След согласий

«Подписан ли я на предложения?» — первый вопрос. «Когда я на это согласился и на
каких условиях?» — второй, и установка, умеющая отвечать только на первый, не
может показать клиенту, почему старый выбор продолжает действовать после
изменения условий.

`consent_records` работает только на добавление. Каждая строка несёт цель,
признак того, дано согласие или отозвано, версию условий, поверхность, откуда
оно пришло, и идентификатор запроса, который его записал. Текущий ответ по цели —
это просто самая новая строка по ней; ничто не редактируется на месте, потому
что история, которую можно переписать, ничего не доказывает.

Экран перечисляет по одной строке на цель со значком **Дано** или **Отозвано** и
строкой происхождения, а также раскрывающуюся **Полную историю** с датированным
следом за ней, ограниченным 25 последними решениями.

| Цель | Значение |
| - | - |
| `terms` | Правила, принятые при регистрации |
| `privacy` | Как обрабатываются данные клиента |
| `marketing` | Предложения и новости |
| `profiling` | Использование истории для выбора показываемых предложений |

Нераспознанная цель отображается со своим исходным именем и общей подсказкой, а
не прячется.

След здесь доступен только для чтения. Маркетинговое согласие меняется
переключателями на контактных каналах выше и маркетинговым переключателем на
`/account/preferences`; эти два пути проставляют разное происхождение —
`account-web-v0.10` / `account_web` из формы контактов, `web-v0.10` /
`customer_web` с экрана настроек и `bot-v0.4` / `telegram_bot` с экрана настроек
в Telegram. Поэтому клиент, читающий собственный след, увидит по одной и той же
цели больше одной версии условий. Каждая строка при этом остаётся точной записью
того решения, которое её породило.

## Экспорт ваших данных

Карточка **Скачать ваши данные** перечисляет, что войдёт в экспорт и что он
намеренно оставляет за скобками, **до** нажатия кнопки, а затем синхронно
формирует весь документ и сохраняет его на устройство.

Он синхронный, а не поставленный в очередь, намеренно. Экспорту в очереди нужен
канал доставки, окно хранения и ссылка, аутентифицирующая сама себя, — три
дополнительных решения о раскрытии ради ответа на вопрос, который умещается в
один ответ HTTP. Ничего не пишется на диск, ничего не отправляется почтой, и
документ существует в этом единственном ответе и больше нигде.

### Что в нём есть

| Раздел | Содержимое |
| - | - |
| `profile` | Запись аккаунта |
| `identities` | Привязанные способы входа |
| `contacts` | Контактные каналы, с адресами, когда ключ настроен |
| `subscriptions` | Подписки и их метки |
| `entitlements` | Имеющиеся тарифы с их окнами |
| `orders` | История заказов |
| `payments` | Попытки оплаты, без учётных данных провайдера |
| `wallet` | Балансы кошелька и проводки |
| `support` | Переписки поддержки, сообщения клиента и оператора |
| `referral` | Приглашения и вознаграждения |
| `loyalty` | Текущее положение и история |
| `consents` | След согласий |
| `lifecycle` | История аккаунта: приостановки, восстановления, запросы на удаление |

Документ объявляет `version: "omniflow.account.export/1"`, поэтому клиент,
сохранивший экспорт годичной давности, — и оператор, читающий его вместе с ним —
могут понять, значения полей какой версии применимы.

### Что не вошло и почему

Список изъятий поставляется **внутри** документа, а не только на экране, потому
что экспорт, который тихо что-то опускает, читается как полный, а клиент не
может спросить об отсутствии, которого не видит.

| Изъятие | Причина |
| - | - |
| `other_customer_identifiers` | Пригласивший вправе знать, что приглашение принесло выплату, но не получать список тех, кто им воспользовался |
| `payment_credentials` | Ссылки провайдера, URL оформления и метаданные чеков — это дескрипторы провайдера, а не данные клиента |
| `provider_secrets` | Сырые наборы утверждений, которые вернул провайдер входа, эта установка не вправе распространять |
| `subscription_links` | Ссылка на подписку — это учётные данные, дающие доступ; она остаётся за экраном подключения, который никогда не кэшируется |
| `device_and_network_identifiers` | В том числе любые, принадлежащие кому-то ещё, кто делит жильё или соединение |

Внутренние заметки оператора по обращению не входят в список изъятий, потому что
их вообще нет на пути экспорта: они живут в собственной таблице, и ни один
клиентский запрос её не касается. Личность оператора опущена в строках
жизненного цикла — называть человека, приостановившего аккаунт, в документе,
который получает владелец этого аккаунта, — это вопрос безопасности, а не
прозрачности.

### Ограничения и отказы

* **Три экспорта в час**, с ключом по идентификатору клиента, потому что
  ограничивается именно то, как часто данные одного аккаунта можно вытащить из
  базы. Превышение отвечает 429 `rate_limited`. Ограничитель отказывает при
  сбое.
* **Каждый список ограничен 1000 строк**, и любой раздел, упёршийся в
  ограничение, **назван** в документе и показан на экране. Клиент, которому
  сказали, что его данные полны, когда это не так, введён в заблуждение ровно по
  тому единственному вопросу, ради ответа на который документ и существует.
* Если `APP_DATA_ENCRYPTION_KEY` не настроен, контактные каналы всё равно
  перечисляются — сам факт, что канал такого-то вида существует, клиент вправе
  знать, — но без адресов, и предупреждающая карточка сообщает об этом до
  скачивания.
* Если объявленные документом изъятия отличаются от того, что показал
  предпросмотр на экране, панель сообщает об этом постфактум. Это означало бы,
  что сервер передумал между двумя вызовами, и это ровно тот случай, который
  клиент не должен обнаруживать, сравнивая файл со страницей.

Панель никогда не отрисовывает документ. Отрисовка оставила бы его в положении
прокрутки, на снимке экрана и в кэше «назад-вперёд» браузера. Он собирается в
blob, сохраняется как `omniflow-personal-data-YYYY-MM-DD.json`, и объектный URL
немедленно отзывается.

Строка аудита `account.data.exported` пишется после сборки и до передачи, так что
зафиксированный экспорт — это всегда тот, который действительно удался, а его
метаданные несут только количество строк: строка аудита, описывающая, что было
раскрыто, не должна становиться второй копией этого.

> [!NOTE]
>
> Экспорт — это `POST`, хотя он не меняет ничего, что клиент может увидеть.
> `GET` кэшировался бы, предзагружался бы, попадал бы в строку доступа прокси и
> был бы достижим по навигации, а это самый чувствительный документ, который
> производит API.

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

**1. Напишите причину**

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

**2. Подтвердите**

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

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

Раздел превращается в предупреждающую панель с датой запроса, фразой,
называющей того, кто его исполняет, собственной причиной клиента и кнопкой
**Отменить запрос** за собственным диалогом.

Маршрут отвечает **202 Accepted**, а не 200, и полезная нагрузка явно несёт
`executedBy: "operator_retention_workflow"`. Разница между «запрошено» и
«сделано» — это всё свойство безопасности данного маршрута, и экран, который об
этом молчит, склоняет клиента поверить, что его данных уже нет.

Запрос на удаление требует входа не старше 15 минут; устаревшая сессия отвечает
401 `reauthentication_required`, и панель показывает предупреждение со ссылкой
обратно на вход до нажатия кнопки, а не после. Свежая аутентификация — это то,
что не даёт сделать запрос с одолженного ноутбука; она не является разрешением
его исполнить, поэтому запрос по-прежнему остаётся только запросом. **Отмена не
требует повторной аутентификации**, потому что отмена всегда только
предотвращает необратимое.

| Условие | Ответ |
| - | - |
| `confirm` отсутствует или равен false | 422 `confirmation_required` |
| Пустая причина или более 400 символов | 422 `invalid_input` |
| Запрос уже открыт | 409 `deletion_pending` |
| Отмена, когда ничего не открыто | 409 `no_deletion_pending` |
| Сессия старше 15 минут (только для запроса) | 401 `reauthentication_required` |

Повторение запроса ничего не меняет и лишь добавило бы шума в запись, которую
оператор читает перед тем, как действовать, поэтому второй запрос отклоняется, а
не принимается молча.

**Отмена не стирает запрос.** Событие жизненного цикла остаётся, потому что
записи об идентичности работают только на добавление: «об этом попросили, а
потом отозвали» — другой факт, нежели «об этом никогда не просили», и оператор,
рассматривающий более поздний запрос, заслуживает видеть оба. Признак
«в ожидании» выводится сравнением трёх моментов — самого нового запроса на
удаление, самой новой отмены и самого нового удаления или обезличивания, — и
запрос находится в ожидании только тогда, когда он самый свежий из трёх.
Отозванный или уже исполненный запрос сохраняет свои отметки времени, но теряет
причину в ответе: те слова были написаны для решения, которое больше не открыто,
а их повторение на экране подразумевает, что оно всё ещё открыто.

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

Удаление выполняет оператор через элементы управления жизненным циклом клиента,
и это не та же операция, что обезличивание. Обезличивание необратимо,
выполняется только над уже удалённым аккаунтом и именно оно на самом деле
убирает идентифицирующие данные:

* каждая идентичность отзывается, а её субъект у провайдера заменяется на
  `anonymized:<id>`;
* шифротекст каждого контактного канала обнуляется, а его отпечаток заново
  выводится из идентификатора строки, оба флага доставки снимаются;
* сопоставление с Remnawave теряет идентификатор Telegram и наблюдаемое
  состояние;
* тело каждого сообщения поддержки в обращениях клиента превращается в
  `[anonymized]`;
* реферальный код удаляется.

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

Вложения поддержки — единственное видимое клиенту с собственными часами: строка
вложения истекает через 90 дней после создания, и затем очистка освобождает
сохранённый файл, когда на него больше не ссылается ни одна уцелевшая строка.

> [!WARNING]
>
> Клиентские сессии, магические ссылки и видимый клиенту журнал безопасности
> объявляют сроки хранения в коде — 30 дней для мёртвой строки сессии, 180 дней
> для события безопасности, — но сегодня ни одно запланированное задание их не
> применяет, поэтому по этому расписанию строки не удаляются. Всё, что их
> касается, по-прежнему видно клиенту на `/account/security` и отзывается
> оттуда.

## Чего Omniflow никогда не показывает и не хранит

Это не упущения. Каждое из них — решение, видимое в коде, и каждое чего-то
стоит, поэтому о них стоит сказать.

**Никаких аппаратных идентификаторов в списке устройств.** `/account/devices`
показывает иконку платформы, модель, которую клиент сообщил о себе, и дату.
Дескриптор для удаления устройства — это 128-битный хеш аппаратного
идентификатора, и он разрешается повторным хешированием текущего списка
устройств, а не по сохранённому сопоставлению, поэтому вызывающий никогда не
передаёт тот идентификатор, над которым выполняется действие. На этом экране нет
ни HWID, ни IP-адреса: достаточно, чтобы клиент узнал собственный ноутбук, и
недостаточно, чтобы построить журнал перемещений.

**Никаких IP-адресов за пределами собственного экрана безопасности клиента.**
Список сессий и журнал событий аккаунта на `/account/security` намеренно несут
IP — адрес, который клиент не узнаёт, и есть вся причина существования этого
экрана. Ни то ни другое не является разделом экспорта, а сетевые идентификаторы —
объявленное изъятие, поэтому в экспорт не попадает ни один IP.

**Никакой ссылки на подписку там, где она задержится.** Ответ дашборда её не
несёт; её запрашивает по требованию только экран подключения, потому что
полезная нагрузка дашборда, всегда несущая её, положила бы учётные данные в
каждый кэш, лог и запись истории, которые касаются страницы. QR-код формируется
в браузере и рисуется из data-URL, а не проходит через эндпоинт изображений,
который отправил бы полезную нагрузку с учётными данными на сервер для
кэширования. Ротация ссылки записывает событие безопасности с меткой подписки и
ничем больше — поместить ссылку в собственный журнал безопасности клиента значило
бы обессмыслить ту самую ротацию, которую эта запись фиксирует. См.
[подключение устройства](https://omniflow-vpn.mintlify.site/ru/customer/connection.md).

**Никакого свободного текста в журнале безопасности клиента.** Словарь событий
закрыт и обеспечивается базой данных: `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, называют событие, класс суммы и
идентификаторы, которые оператор может посмотреть. Они никогда не несут текст
клиента, ссылку на подписку, токен или платёжную полезную нагрузку; нагрузки
редактируются при постановке в очередь и ещё раз при отрисовке по белому списку
полей. См. [операторские уведомления](https://omniflow-vpn.mintlify.site/ru/operations/operator-notifications.md).

**Никакой индексации, никакого кэширования.** Панель аккаунта задаёт
`noindex, nofollow` на уровне макета, а каждый ответ `/v1/account` несёт
`Cache-Control: no-store`, `Referrer-Policy: no-referrer`,
`X-Content-Type-Options: nosniff`, политику безопасности контента
`default-src 'none'` и политику разрешений, запрещающую камеру, микрофон,
геолокацию и тематические когорты интересов. Причина, названная в коде, та же,
что и выше: ответ клиенту может содержать ссылку на подписку, а это учётные
данные.

## Справочник

| Маршрут | Метод | Примечания |
| - | - | - |
| `/v1/account/privacy` | `GET` | Состояние хранения, состояние удаления, согласия с историей и предпросмотр экспорта |
| `/v1/account/privacy/export` | `POST` | Документ; `Content-Disposition: attachment`; 3 в час |
| `/v1/account/privacy/deletion` | `POST` | `{reason, confirm}` → 202; требует входа не старше 15 минут |
| `/v1/account/privacy/deletion` | `DELETE` | Отозвать запрос → 200 с новым состоянием |
| `/v1/account/contacts` | `GET` | Возвращает адреса расшифрованными, потому что они принадлежат клиенту |
| `/v1/account/contacts` | `POST` | `{kind, value, transactional, marketing}` → 201; 10 в час |
| `/v1/account/contacts/{contactID}` | `DELETE` | 204; отзывает, а не удаляет |

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

> **[Уведомления](https://omniflow-vpn.mintlify.site/ru/customer/notifications.md)**
>
> Согласие, тихие часы, ограничение частоты и лента объявлений.

> **[Клиентская веб-панель](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md)**
>
> Вход, сессии и остальная часть интерфейса аккаунта.

> **[Подключение устройства](https://omniflow-vpn.mintlify.site/ru/customer/connection.md)**
>
> Где живёт ссылка на подписку и как она ротируется.

> **[Безопасность](https://omniflow-vpn.mintlify.site/ru/operations/security.md)**
>
> Взгляд оператора: учётные данные, сессии, секреты и транспорт.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.