Добавление всех наработок за период практики.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# 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`.
|
||||
Reference in New Issue
Block a user