71 lines
6.0 KiB
Markdown
71 lines
6.0 KiB
Markdown
# ADR-008: хранение интеграционных данных и разграничение доступа
|
||
|
||
**Статус:** Принято
|
||
**Дата:** 2026-07-23
|
||
|
||
## Контекст
|
||
|
||
Основное назначение базы данных — хранение авторизационных данных, токенов
|
||
привязки, OAuth-токенов и связей между пользователями Битрикс24 и Telegram.
|
||
Сделки, контакты, компании, стадии и история остаются в Битрикс24 и в локальной
|
||
базе не дублируются.
|
||
|
||
## Решение
|
||
|
||
Модель хранения нормализована вокруг портала Битрикс24.
|
||
|
||

|
||
|
||
*Рисунок 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`.
|