Какие данные хранит сервис

Это техническое описание: что именно лежит в базе и где, написанное по коду и структуре базы данных. Юридические документы — отдельные: Политика обработки персональных данных и Пользовательское соглашение. Если между ними и этой страницей окажется расхождение, верны они — а расхождение будет ошибкой, о которой стоит сообщить.

Данные аккаунта

При регистрации и в процессе использования сервиса сохраняются:

  • email;
  • отображаемое имя (никнейм);
  • bcrypt-хеш пароля — сам пароль нигде не хранится и не может быть восстановлен;
  • выбранный уровень английского (например, B2);
  • часовой пояс устройства (например, Europe/Moscow) — присылается браузером при входе и регистрации. От него зависит, какие сутки считаются у вас «сегодня» для серии дней и дневной цели: без него днём был бы UTC, и занятие в час ночи по Москве попадало бы во вчера;
  • дата регистрации и дата последнего входа;
  • дата согласия с Пользовательским соглашением и Политикой и та их РЕДАКЦИЯ, с которой вы согласились — хранится именно редакция, а не флаг «да», иначе после первой же правки документов было бы не сказать, с чем именно вы соглашались;
  • момент прохождения приветственного экрана (onboarded_at) — чтобы не показывать его повторно; это факт об аккаунте, а не о браузере;
  • дата последней смены пароля (password_changed_at) — по ней сервер отличает токен входа, выданный до смены пароля, от честного, и обесценивает первый сразу, а не когда он истечёт сам по себе (до 7 дней);
  • при запросе сброса пароля — одна строка в отдельной таблице токенов сброса: в ней хранится только хеш токена (сам токен нигде, кроме письма, не сохраняется), срок действия — 60 минут, и после использования (или после смены пароля любым другим способом) токен помечается погашенным и повторно не срабатывает.
  • при смене адреса почты — такая же строка в отдельной таблице: хеш токена подтверждения и НОВЫЙ адрес, который вы указали. Подтверждение уходит на новый адрес, а уведомление о смене — на прежний, чтобы подмену почты было видно тому, кто ей владел.

Данные об учёбе

Чтобы упражнения, повторения и прогресс работали, сервис хранит:

  • прогресс по каждому упражнению: статус, число попыток, лучший результат, дата последней попытки;
  • сводные счётчики выполненных и правильных ответов по каждой категории (для дашборда);
  • состояние алгоритма интервального повторения (FSRS) отдельно для упражнений и для слов из ваших собственных списков: дата следующего показа, показатели сложности и стабильности запоминания, счётчик повторных ошибок;
  • построчный журнал каждого повторения (оценка, время между повторениями) — он копится на будущее, для возможной персональной настройки алгоритма повторения, и сейчас нигде не используется;
  • заработанные достижения и дата их получения;
  • ваши собственные списки слов (слово, перевод, направление перевода);
  • заявки в друзья и список друзей;
  • если вы сообщили об ошибке в задании — причина, ваш комментарий и снимок текста задания на тот момент. Эти обращения читает только владелец сервиса и нигде их не публикует; комментарий пишете вы, поэтому не стоит писать в нём сведения о себе или о других людях;
  • XP за день и за неделю, общий XP, дневная цель по XP;
  • количество выполненных упражнений за каждый день (учитывается любая завершённая попытка, включая неверные ответы) — используется для календаря активности в разделе «Аналитика прогресса»; хранится без ограничения по сроку, пока не удалён аккаунт;
  • текущая и лучшая серия дней подряд, доступные «заморозки» серии;
  • отметки о засчитанных победах в мини-играх (Hangman, Memory Match, Scramble, Category Sort) — они не хранят содержимое игры, только факт «эта раунда уже выдала XP», чтобы нельзя было получить XP повторно за одну и ту же победу.

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

Предложения тем («Идеи»)

В разделе «Идеи» сервис хранит:

  • текст предложенной темы, уровень (если указан) и статус — предложение публичное: его видит любой вошедший пользователь сразу после отправки, в отличие от собственных слов и сообщений об ошибках выше;
  • факт вашего голоса за предложение — без какого-либо содержания сверх этого;
  • жалобу на предложение — причину обращения и то, кто именно её подал: без этого нельзя было бы отличить три жалобы от трёх разных людей от трёх от одного, а автоматическое скрытие предложения требует ровно трёх РАЗНЫХ людей.

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

Что хранится в браузере

  • токен входа (JWT) и кешированная копия профиля — в localStorage, удаляются при выходе из аккаунта;
  • кеш недавно загруженных упражнений и повторений — чтобы приложение работало офлайн; тоже удаляется при выходе;
  • очередь ещё не отправленных ответов, набранных офлайн (IndexedDB) — по замыслу она не удаляется при обычном выходе, чтобы можно было зайти на этом же устройстве позже и досинхронизировать ответы; полностью удаляется только при удалении аккаунта;
  • выбор светлой/тёмной темы — это настройка устройства, не связана с аккаунтом;
  • отметка о том, что уведомление о хранении внизу экрана уже прочитано, — чтобы не показывать его при каждом заходе. Отметка не связана с аккаунтом и ничего о вас не говорит.

Файлов cookie среди этого нет: сервис не ставит ни одной. Всё перечисленное — localStorage и IndexedDB, то есть хранилища самого браузера, к которым сервер обращаться не может; данные оттуда уходят на сервер только в составе обычных запросов приложения.

Что уходит наружу

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

  • Словарь (api.dictionaryapi.dev) — используется функцией поиска слов. Этот запрос делает сервер, а не браузер: стороннему сервису уходит искомое слово, но не IP-адрес и не что-либо ещё, что могло бы опознать пользователя. Ответ кешируется на сервере в общей таблице на 30 дней — повторный поиск того же слова в этот период вообще не обращается к стороннему сервису. Этот кеш общий для всех пользователей и хранит только само слово и ответ словаря — привязки к конкретному аккаунту в нём нет.
  • Почта — сервис отправляет письмо на адрес, указанный при регистрации, ровно в двух случаях: запрос сброса пароля и уведомление о состоявшейся смене пароля. Рассылок и уведомлений сверх этого нет. Письмо уходит через стороннего поставщика почты (SMTP), которому в процессе становятся видны адрес получателя и текст письма. В письмах нет картинок, шрифтов и трекинг-пикселей — открытие письма само по себе не сообщает сервису ничего. Email — единственный канал восстановления доступа к аккаунту: удаление аккаунта необратимо, и без доступа к почте, на которую он зарегистрирован, вернуть аккаунт нельзя.

Шрифты раньше загружались браузером с fonts.googleapis.com и fonts.gstatic.com, и такой запрос по своей природе раскрывал Google IP-адрес каждого посетителя. Теперь их копии лежат на этом сайте, и этого запроса больше нет.

Отдельная оговорка про озвучку (кнопка 🔊) и проверку произношения (кнопка 🎤): обе используют встроенные в браузер механизмы синтеза и распознавания речи, а не что-либо написанное здесь. Что именно делает браузер, зависит от браузера: многие из них выполняют распознавание не на устройстве, а отправляют записанный фрагмент на серверы своего разработчика (например, Chrome — в Google). Этот обмен идёт мимо нашего сервера, мы не видим ни записи, ни результата, и повлиять на него с нашей стороны нельзя — но и молчать о нём было бы нечестно. Если это нежелательно, просто не пользуйтесь этими двумя кнопками: на остальную работу сайта они не влияют.

IP-адрес: где он оказывается

В двух местах, и второе менее очевидно, чем первое.

В журналах веб-сервера. Оба слоя nginx, через которые проходит любой запрос к сервису (edge-прокси и раздача статики фронтенда), пишут свой формат: время, идентификатор запроса, IP-адрес, метод, запрошенный адрес, код ответа, размер ответа и длительность. Это происходит для каждого запроса, независимо от того, вошли вы в аккаунт или нет. User-agent не записывается — стандартный формат nginx, который его пишет, в обоих конфигах заменён на свой.

Журналы никуда не выгружаются и не архивируются: они живут в логах самих Docker-контейнеров, и объём журнала ограничен — 50 МБ на каждый сервис, после чего старые записи вытесняются новыми. В пересчёте это около 250 тысяч запросов; сколько это дней, зависит от того, сколько людей пользуется сервисом, поэтому фиксированного срока тут нет и назвать его было бы неправдой.

Запрошенный адрес пишется целиком, вместе со строкой запроса. Ссылки из писем это не затрагивает: одноразовый токен в них передаётся во фрагменте адреса (#token=…), а фрагмент браузер на сервер вообще не отправляет — в журнале его нет и быть не может. Так сделано именно для этого.

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

В журналы самого приложения (не веб-сервера) не попадают ни заголовки, ни тела запросов вовсе: только метод, путь без строки запроса, код ответа, длительность, идентификатор запроса и, если вы вошли, идентификатор аккаунта. Это не мелочь: без такого ограничения оборванный JSON на форме входа записал бы в журнал пароль открытым текстом.

Резервные копии базы

Раз в сутки на сервере снимается резервная копия всей базы данных — на случай сбоя, повреждения данных или ошибки при обновлении сервиса, а не для чего-либо ещё. Хранится не больше 14 последних копий: при появлении новой копии самая старая из лишних удаляется. Срок «примерно две недели» — не гарантия, а следствие того, что копии продолжают сниматься по расписанию: если снятие копий по какой-то причине прервётся (например, кончится место на диске или сервер будет выключен), уже существующие копии никуда не удаляются и могут пролежать дольше. Копии лежат на том же сервере, что и сам сервис, без шифрования, и наружу никуда не отправляются.

Как забрать своё

В Профиле, в разделе «Мои данные», есть выгрузка: один файл JSON со всем, что перечислено выше, — прогресс, повторения, «Мои слова», XP, достижения, история занятий, дуэлей, а также ваши предложения тем и голоса за них. Это данные, а не отчёт: имена полей повторяют внутренние, ничего не склеено в красивые строки и ничего не отброшено. Из чужого в файле только отображаемые имена друзей и соперников по дуэлям — то, что и так видно на экране; ни их почты, ни их прогресса там нет. Пароль не выгружается ни в каком виде: он хранится только хешем и не восстанавливается даже нами.

Как удалить всё

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

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

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