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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Описание
Требования A-1…A-8 из SPEC §3.1, устройство — ARCHITECTURE §2.2, решение — ADR-011. Без этого бот открыт кому угодно — главный риск спама.
Желаемый результат
t.me/<bot>?start=<token>; одноразовая, 24 ч, в БД — хэш./start <token>→ участник + главное меню; повторный/истёкший/неизвестный токен → отказ.messageиcallback_query: неавторизованным — одна фраза в сутки, остальное игнор.Подзадачи
app/access/invites.py— create/redeem (транзакция),secrets.token_urlsafe(16), sha256segnoв памятьapp/bot/middlewares/access.py+ bootstrap админов изADMIN_IDSapp/bot/routers/members.py— раздел «Участники»access_attempts)Анализ
Затрагиваемые файлы
app/access/app/bot/middlewares/access.pyapp/bot/routers/members.pytests/access/Ограничения и риски
Deep link payload ≤ 64 символов
[A-Za-z0-9_-]. Токен не логировать. Middleware должен покрывать и нажатия кнопок — старые инлайн-кнопки у отозванного пользователя не должны работать.Как проверить
Выпустить QR, войти со второго аккаунта; повторно отсканировать — отказ; написать боту с третьего аккаунта — одна фраза и тишина; отозвать — кнопки перестают работать.
Лог выполнения
2026-10-13
2026-10-15
app/access/service.py:create_invite(token_urlsafe(16)→ 22 символа, в БД только sha256, TTLACCESS-настройки),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, ровно один пользователь.AccessMiddlewareтеперь берёт участника из БД и кладётmemberв данные хэндлера; новичку пропускается только/start <токен>. Права админа — по роли в БД, а не поADMIN_IDS.yes/noчитаются какTrue/False→confirm_yes/confirm_no(валидация текстов поймала на старте).Смержено в
devчерез PR #38 (219bebd) — закрываю.