bot: доступ по QR-приглашениям, участники, отзыв доступа #25

Closed
opened 2026-10-13 22:51:49 +03:00 by Eleboba · 1 comment
Owner

Описание

Требования A-1…A-8 из SPEC §3.1, устройство — ARCHITECTURE §2.2, решение — ADR-011. Без этого бот открыт кому угодно — главный риск спама.

Желаемый результат

  • Админ: «👥 Участники» → «Пригласить» → QR (PNG) + ссылка t.me/<bot>?start=<token>; одноразовая, 24 ч, в БД — хэш.
  • /start <token> → участник + главное меню; повторный/истёкший/неизвестный токен → отказ.
  • Middleware на message и callback_query: неавторизованным — одна фраза в сутки, остальное игнор.
  • Антиперебор: > 5 неудачных попыток/час → игнор.
  • Список участников (кто пригласил, когда, последняя активность) и «отозвать»: апдейты игнорируются, черновик удаляется.

Подзадачи

  • app/access/invites.py — create/redeem (транзакция), secrets.token_urlsafe(16), sha256
  • QR через segno в память
  • app/bot/middlewares/access.py + bootstrap админов из ADMIN_IDS
  • app/bot/routers/members.py — раздел «Участники»
  • Антиперебор (access_attempts)
  • Тесты: redeem гонка двух пользователей одного токена, истечение, отзыв, middleware на каждом роутере

Анализ

Затрагиваемые файлы

Файл Что меняется
app/access/ новый пакет
app/bot/middlewares/access.py новый
app/bot/routers/members.py новый
tests/access/ новый

Ограничения и риски

Deep link payload ≤ 64 символов [A-Za-z0-9_-]. Токен не логировать. Middleware должен покрывать и нажатия кнопок — старые инлайн-кнопки у отозванного пользователя не должны работать.

Как проверить

Выпустить QR, войти со второго аккаунта; повторно отсканировать — отказ; написать боту с третьего аккаунта — одна фраза и тишина; отозвать — кнопки перестают работать.


Лог выполнения

2026-10-13

  • Задача переписана под SPEC v2 (QR-доступ, меню из 4 разделов, командная модель — ADR-011…013).

2026-10-15

  • app/access/service.py: create_invite (token_urlsafe(16) → 22 символа, в БД только sha256, TTL ACCESS-настройки), redeem, list_members, revoke, bootstrap_admins, touch (last_seen не чаще раза в 5 мин). app/access/qr.py — QR через segno.
  • Гонка: сначала создаётся пользователь, затем UPDATE invites SET used_by=… WHERE id=… AND used_by IS NULL; проигравший откатывает транзакцию целиком. Первая версия ставила used_by=NULL (no-op) — обе транзакции «выигрывали»; поймано на самопроверке. Проверено на Postgres: 20 одновременных redeem одного токена → 1 ok, 19 rejected, ровно один пользователь.
  • Неверный, истёкший и использованный токен — один ответ (не подсказываем, какие токены существовали). > 5 неудач в час с одного ID — молчание даже на валидный токен.
  • AccessMiddleware теперь берёт участника из БД и кладёт member в данные хэндлера; новичку пропускается только /start <токен>. Права админа — по роли в БД, а не по ADMIN_IDS.
  • «👥 Участники»: список (👑/👤, с какого числа, последняя активность), «➕ Пригласить» → фото QR + ссылка в подписи, «🚫 имя» → подтверждение → отзыв. Отзыв удаляет черновик и FSM-состояние, старые кнопки перестают работать.
  • Грабли YAML 1.1: ключи yes/no читаются как True/False → confirm_yes/confirm_no (валидация текстов поймала на старте).
  • Тесты: 16 новых (сервис + полный сценарий через Dispatcher: QR → вход по ссылке из подписи → повторный скан отклонён → отзыв → тишина). Всего 73, покрытие 91 %.
  • Не проверено вживую — нет токена бота.
## Описание Требования A-1…A-8 из [SPEC](https://gitea.24studbank.ru/HSE/tgit/src/branch/dev/docs/SPEC.md) §3.1, устройство — [ARCHITECTURE](https://gitea.24studbank.ru/HSE/tgit/src/branch/dev/docs/ARCHITECTURE.md) §2.2, решение — ADR-011. Без этого бот открыт кому угодно — главный риск спама. ## Желаемый результат - Админ: «👥 Участники» → «Пригласить» → QR (PNG) + ссылка `t.me/<bot>?start=<token>`; одноразовая, 24 ч, в БД — хэш. - `/start <token>` → участник + главное меню; повторный/истёкший/неизвестный токен → отказ. - Middleware на `message` и `callback_query`: неавторизованным — одна фраза в сутки, остальное игнор. - Антиперебор: > 5 неудачных попыток/час → игнор. - Список участников (кто пригласил, когда, последняя активность) и «отозвать»: апдейты игнорируются, черновик удаляется. ## Подзадачи - [x] `app/access/invites.py` — create/redeem (транзакция), `secrets.token_urlsafe(16)`, sha256 - [x] QR через `segno` в память - [x] `app/bot/middlewares/access.py` + bootstrap админов из `ADMIN_IDS` - [x] `app/bot/routers/members.py` — раздел «Участники» - [x] Антиперебор (`access_attempts`) - [x] Тесты: redeem гонка двух пользователей одного токена, истечение, отзыв, middleware на каждом роутере ## Анализ ### Затрагиваемые файлы | Файл | Что меняется | |------|--------------| | `app/access/` | новый пакет | | `app/bot/middlewares/access.py` | новый | | `app/bot/routers/members.py` | новый | | `tests/access/` | новый | ### Ограничения и риски Deep link payload ≤ 64 символов `[A-Za-z0-9_-]`. Токен не логировать. Middleware должен покрывать и нажатия кнопок — старые инлайн-кнопки у отозванного пользователя не должны работать. ## Как проверить Выпустить QR, войти со второго аккаунта; повторно отсканировать — отказ; написать боту с третьего аккаунта — одна фраза и тишина; отозвать — кнопки перестают работать. --- ## Лог выполнения ### 2026-10-13 - Задача переписана под SPEC v2 (QR-доступ, меню из 4 разделов, командная модель — ADR-011…013). ### 2026-10-15 - `app/access/service.py`: `create_invite` (`token_urlsafe(16)` → 22 символа, в БД только sha256, TTL `ACCESS`-настройки), `redeem`, `list_members`, `revoke`, `bootstrap_admins`, `touch` (last_seen не чаще раза в 5 мин). `app/access/qr.py` — QR через `segno`. - **Гонка:** сначала создаётся пользователь, затем `UPDATE invites SET used_by=… WHERE id=… AND used_by IS NULL`; проигравший откатывает транзакцию целиком. Первая версия ставила `used_by=NULL` (no-op) — обе транзакции «выигрывали»; поймано на самопроверке. Проверено на **Postgres**: 20 одновременных redeem одного токена → 1 ok, 19 rejected, ровно один пользователь. - Неверный, истёкший и использованный токен — один ответ (не подсказываем, какие токены существовали). > 5 неудач в час с одного ID — молчание даже на валидный токен. - `AccessMiddleware` теперь берёт участника из БД и кладёт `member` в данные хэндлера; новичку пропускается только `/start <токен>`. Права админа — по роли в БД, а не по `ADMIN_IDS`. - «👥 Участники»: список (👑/👤, с какого числа, последняя активность), «➕ Пригласить» → фото QR + ссылка в подписи, «🚫 имя» → подтверждение → отзыв. Отзыв удаляет черновик и FSM-состояние, старые кнопки перестают работать. - Грабли YAML 1.1: ключи `yes`/`no` читаются как `True`/`False` → `confirm_yes`/`confirm_no` (валидация текстов поймала на старте). - Тесты: 16 новых (сервис + полный сценарий через Dispatcher: QR → вход по ссылке из подписи → повторный скан отклонён → отзыв → тишина). Всего 73, покрытие 91 %. - **Не проверено вживую** — нет токена бота.
Eleboba added this to the M1 · MVP: загрузка → коммит → CI milestone 2026-10-13 22:51:49 +03:00
Eleboba added the featurebotsize/1 labels 2026-10-13 22:51:49 +03:00
Eleboba self-assigned this 2026-10-13 22:51:49 +03:00
Eleboba added a new dependency 2026-10-13 22:55:16 +03:00
Author
Owner

Смержено в dev через PR #38 (219bebd) — закрываю.

Смержено в `dev` через PR #38 (219bebd) — закрываю.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: HSE/tgit#25