Перейти к основному содержимому

API пользователей бота: доступ и защита ресурсов

Этот контракт относится к GET /api/bot/tokens/:tokenId/users и GET /api/bot/tokens/:tokenId/users/:userId и GET /api/bot/tokens/:tokenId/users/:userId/photo. Маршруты возвращают данные пользователей выбранного токена; доступ к проекту проверяется до чтения пользователей и получения аватара. Параметр telegram_id сам по себе не предоставляет доступ: актор определяется существующей проверкой авторизации.

Ограничение частоты запросов​

Список, карточка и фотография расходуют общие квоты, поэтому переключение между ними или между токенами не создаёт новый бюджет запросов.

ПроверкаКвота за 60 секундКлюч
Перед проверкой актора600 запросовIP, полученный Express; IPv6 объединяется по /56
После проверки актора120 запросовАвторизованный владелец, включая запросы с impersonation

Квота владельца не зависит от переданного telegram_id. Учитываются успешные и ошибочные запросы, дошедшие до соответствующего ограничителя. IP определяется настройкой Express trust proxy = 1, а не прямым чтением заголовка X-Forwarded-For. При развёртывании нужно обеспечить ожидаемый путь через доверенный прокси: доступ клиента напрямую к API может позволить подменить IP через этот заголовок. Квота авторизованного владельца от такой подмены не зависит. Превышение возвращает HTTP 429 с JSON:

{
"error": "RATE_LIMIT_EXCEEDED",
"code": "BOT_USERS_RATE_LIMITED",
"message": "Слишком много запросов к пользователям бота. Повторите позже."
}

Retry-After задаёт задержку перед повтором в секундах. Клиент должен соблюдать её, а не немедленно повторять запрос. Заголовки RateLimit и RateLimit-Policy передают сведения о квоте в формате draft-7; старые X-RateLimit-* не используются.

При доступном Redis счётчик и срок действия обновляются атомарно и действуют на все экземпляры API. При недоступном Redis продолжает работать локальная квота процесса с сохранённым расходом. В этом режиме общий предел между репликами не гарантируется. Ожидание Redis ограничено 150 мс; после ошибки повторная попытка откладывается на 5 секунд. Число незавершённых команд ограничено. Локальное хранилище содержит до 10 000 ключей на квоту: при заполнении новые ключи получают 429, действующие счётчики не вытесняются.

Параметры и пагинация​

tokenId — положительное целое в диапазоне int32, допускается префикс token_. userId — ненулевое целое в безопасном диапазоне JavaScript, допускается префикс user_. Отрицательные идентификаторы сохраняют совместимость с группами. Значения должны быть записаны десятичными цифрами: дроби, экспонента, частично числовые строки и массивы параметров отклоняются с HTTP 400.

Параметр спискаПо умолчаниюДопустимый диапазон
limit101–50
offset00–100 000

Выход за диапазон возвращает 400; сервер не обрезает значение до границы. Ответ сохраняет поля items, count, nextOffset, prevOffset, fromItem, toItem. Для пустой страницы fromItem и toItem равны нулю. Порядок стабилен при одинаковой активности: last_interaction DESC, user_id DESC. Поскольку используется offset-пагинация, изменение активности между запросами может сместить пользователей между страницами.

Используются существующие способы определения актора: сессия, PAT и контекст bot_manager. Без актора возвращается 401, при отсутствии токена — 404, при отсутствии доступа к проекту — 403, при отсутствии пользователя — 404.

Аватары​

Карточка возвращает photoFileId (Telegram file_id), photoUrl (адрес защищённого прокси проекта) и photoDownloadUrl (загрузка через API бота). Отсутствие фотографии, сбой Telegram или перегрузка возвращают три поля со значением null, сохраняя данные пользователя. Авторизация и проверка доступа выполняются до обращения к кэшу.

Получение карточки запрашивает только метаданные через getUserProfilePhotos. Байты изображения не скачиваются и файлы в uploads не создаются. photoFileId относится к боту, который получил фотографию: другой бот не должен использовать его в sendPhoto. Для интеграций нужен доступ к файлу через соответствующий токен, а не передача чужого file_id.

Кэш метаданных действует внутри одного процесса API и изолирован по проекту, записи токена, пользователю и хешу текущего секрета. Параллельные одинаковые запросы объединяются. Смена секрета создаёт новый ключ.

РезультатСрок хранения
Полученный file_id1 час
Telegram подтвердил отсутствие фотографии5 минут
Ошибка получения30 секунд

Кэш содержит до 1000 записей и вытесняет давно не использованные. Одновременно разрешены четыре запроса метаданных и до 32 незавершённых обращений; перегрузка возвращает пустые поля без очереди и без сохранения отрицательного результата. У реплик API независимые кэши и ограничения. Запрос метаданных ограничен 10 секундами; JSON — 256 КиБ. Перенаправления запрещены.

Просмотр изображения​

photoUrl имеет вид /api/projects/9/telegram-file?fileId=...&tokenId=7. Прокси проверяет авторизацию и доступ к проекту при каждом запросе. Ссылка не является публичной: обычный запрос Telegram по URL без сессии или PAT её не откроет. Доступ определяется настоящим владельцем сессии/PAT; подстановка telegram_id через bot_manager сама по себе доступа к этому маршруту не даёт.

Параметр tokenId привязывает запрос к конкретному токену проекта. Некорректный формат возвращает 400, отсутствующий или чужой токен — 404 без переключения на токен по умолчанию. Если параметр отсутствует, используется токен проекта по умолчанию. Знание file_id не заменяет проверку доступа.

Прокси передаёт файл потоком без записи на диск и использует приватное кэширование. Поддерживаются диапазоны с ответами 206/416. Для getFile действует ограничение JSON 256 КиБ; перенаправления и небезопасные пути файлов отклоняются. Для общего прокси срок запроса — 120 секунд; отключение клиента отменяет обращения к Telegram. Приватное кэширование разделяет ответы по Cookie и Authorization.

Файлы, созданные прежней реализацией в uploads, автоматически не удаляются. Их удаление требует отдельного решения с учётом действующих ссылок; переход на прокси не закрывает доступ к старым публичным URL.

Загрузка другим ботом​

photoDownloadUrl имеет вид /api/bot/tokens/7/users/161/photo?telegram_id=123. Запрос проходит общие ограничители, определение актора и проверку доступа к токену. Затем проверяется наличие пользователя и фотография передаётся тем же потоковым прокси, с ограничением изображения 5 МиБ и потоковой загрузки 10 секундами. Этот маршрут поддерживает PAT со scope bot_manager и действующим telegram_id. Отсутствие пользователя или фотографии возвращает 404.

URL не содержит секретов и сам по себе не предоставляет доступ. Интеграция скачивает его с Authorization: Bearer …, затем отправляет байты через своего бота. Bot Manager использует ограниченную загрузку в память и BufferedInputFile; сервер не сохраняет фотографию на диск или как base64 в данных пользователя. Передача защищённого URL напрямую в sendPhoto не заменяет эту загрузку: Telegram не добавляет авторизационный заголовок.

Проверки​

Целевые регрессионные проверки выполняются командой npx vitest run --config server/routes/userProjectsTokens/vitest.config.ts. Она подключена к заданию security процесса Security regressions в CI. Проверки общего потокового прокси также запускаются в CI командой npx vitest run --config server/routes/botIntegration/handlers/botData/telegram-file.vitest.config.ts.

См. также справочник API и вкладку пользователей.