Files
BitrixDealsBot/docs/adr/008-data-storage-and-access.md
T

71 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ADR-008: хранение интеграционных данных и разграничение доступа
**Статус:** Принято
**Дата:** 2026-07-23
## Контекст
Основное назначение базы данных — хранение авторизационных данных, токенов
привязки, OAuth-токенов и связей между пользователями Битрикс24 и Telegram.
Сделки, контакты, компании, стадии и история остаются в Битрикс24 и в локальной
базе не дублируются.
## Решение
Модель хранения нормализована вокруг портала Битрикс24.
![Схема базы данных](assets/report/database-schema.png)
*Рисунок ADR-008/1. Схема базы данных*
| Таблица | Ключевые данные | Назначение и ограничения |
|------------------------|-------------------------------------------------|-------------------------------------------------------|
| binding.portals | member_id, domain | Справочник порталов; member_id уникален |
| binding.tokens | token_hash, expires_at, consumed_at, revoked_at | Одноразовые ссылки; токен хранится только как хеш |
| binding.user_bindings | portal_id, bitrix_user_id, telegram_user_id | Однозначная привязка пользователей в пределах портала |
| oauth.user_credentials | access_token, refresh_token, version, lock | Зашифрованные OAuth-данные и координация обновления |
*Таблица ADR-008/1. Назначение таблиц базы данных*
Поле `member_id` является устойчивым внешним идентификатором портала, а числовой
`bitrix_user_id` имеет смысл только вместе с `portal_id`. Связи с `portals`
используют `ON DELETE CASCADE`: удаление портала автоматически удаляет его
ссылки, привязки и OAuth-данные. Составной первичный ключ
`oauth.user_credentials(portal_id, bitrix_user_id)` исключает две конкурирующие
записи учётных данных одного пользователя. Поля `consumed_at` и `revoked_at`
разделяют два независимых основания недействительности одноразовой ссылки.
Для работы с данными в PostgreSQL созданы две роли: `site_role` и `bot_role`.
Они имеют разные права доступа к хранимым функциям и не могут редактировать
таблицы напрямую.
| Функция | Вызывающая роль | Назначение |
|-----------------------------|-----------------|-------------------------------------------------------------------------------------|
| binding.issue_v1 | site_role | Сохранить OAuth-данные, отозвать старые ссылки и выпустить новую в одной транзакции |
| binding.consume_v1 | bot_role | Однократно погасить ссылку и создать привязку |
| binding.find_by_telegram_v1 | bot_role | Получить актуальную привязку Telegram |
| oauth.get_credentials_v1 | bot_role | Получить учётные данные связанного пользователя |
| oauth.claim_refresh_v1 | bot_role | Получить короткую аренду на обновление токена |
| oauth.finish_refresh_v1 | bot_role | Атомарно записать новую пару при совпадении версии |
| oauth.release_refresh_v1 | bot_role | Освободить аренду после ошибки |
*Таблица ADR-008/2. Контракт хранимых функций PostgreSQL*
Сайт использует роль `site_role` и может вызывать только функцию привязки.
Telegram-бот использует `bot_role` и может вызывать функции погашения токенов,
получения привязки и работы с OAuth. Роль `site_role` не имеет доступа к схеме
`oauth` и не может читать сохранённые токены; роль `bot_role` не может выпускать
новые ссылки от имени сайта.
Все функции объявлены `SECURITY DEFINER` и фиксируют `search_path` в
`pg_catalog`, что уменьшает риск подмены объектов. Права `PUBLIC` на таблицы и
функции отозваны.
## Последствия
Конкурирующие вызовы `binding.consume_v1` для одного хеша не смогут одновременно
пройти условие `consumed_at IS NULL`: `UPDATE` блокирует строку, а после
завершения первой транзакции второй вызов видит уже установленное время
погашения. Проверка и изменение не разделены между приложением и базой, поэтому
отсутствует окно гонки между `SELECT` и `UPDATE`.