Добавление всех наработок за период практики.

This commit is contained in:
SkyForces
2026-07-24 00:46:19 +03:00
parent 24943b8a73
commit ae4141ba28
56 changed files with 3987 additions and 136 deletions
+70
View File
@@ -0,0 +1,70 @@
# 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`.