feat(bot): доступ по QR-приглашениям, раздел «Участники», отзыв доступа
- AccessService: одноразовые приглашения (128 бит, в БД только sha256, TTL 24 ч), атомарный захват приглашения (условный UPDATE — из двух сканов одного QR проходит один), антиперебор (> 5 неудач в час — молчание), отзыв (черновик и FSM удаляются), повторное приглашение - ADMIN_IDS заводятся в БД при старте; дальше — только по приглашению - AccessMiddleware: участники из БД (+ last_seen раз в 5 мин), новичкам — только /start <токен> - «👥 Участники» для админов: список, QR (segno) + ссылка, отзыв с подтверждением - один ответ на неверный/истёкший/использованный токен — без подсказок Refs #25 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
from sqlalchemy.ext.asyncio import AsyncEngine
|
||||
|
||||
from app.access.service import AccessService
|
||||
from app.config import AccessSettings, RuntimeSettings, Settings, TelegramSettings
|
||||
from app.db.migrate import upgrade
|
||||
from app.db.session import SessionMaker, make_engine, make_sessionmaker
|
||||
@@ -40,3 +41,10 @@ async def engine() -> AsyncIterator[AsyncEngine]:
|
||||
@pytest.fixture
|
||||
def sessions(engine: AsyncEngine) -> SessionMaker:
|
||||
return make_sessionmaker(engine)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def access(sessions: SessionMaker, settings: Settings) -> AccessService:
|
||||
service = AccessService(sessions, settings.access)
|
||||
await service.bootstrap_admins()
|
||||
return service
|
||||
|
||||
Reference in New Issue
Block a user