From f1938813baebdd520521b98a418d359b13b87e63 Mon Sep 17 00:00:00 2001 From: QueryPlanner Date: Thu, 20 Aug 2026 18:20:42 +0530 Subject: [PATCH] fix: record Telegram identities in open mode - Record private-chat identities when access control is unset - Keep open-mode updates flowing when local label storage fails - Cover identity writes and storage error paths --- src/blacki/telegram/bot.py | 37 ++++++++++++++++++++--- tests/test_telegram_access.py | 55 +++++++++++++++++++++++++++++++++-- 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/blacki/telegram/bot.py b/src/blacki/telegram/bot.py index 3171f79..bdf4586 100644 --- a/src/blacki/telegram/bot.py +++ b/src/blacki/telegram/bot.py @@ -340,13 +340,20 @@ def _get_access_storage(self) -> TelegramAccessStorage: async def _authorize_update(self, update: Update) -> bool: """Allow only authenticated private Telegram traffic into the bot.""" - if not self.config.access_control_enabled: - return True - message = update.message callback = update.callback_query if message is None and callback is not None: message = callback.message + + sender = ( + callback.from_user + if callback is not None + else (message.from_user if message is not None else None) + ) + if not self.config.access_control_enabled: + await self._record_identity_if_private(message, sender) + return True + if message is None or message.chat.type != ChatType.PRIVATE: if callback is not None: await self.api.answer_callback_query( @@ -354,7 +361,6 @@ async def _authorize_update(self, update: Update) -> bool: ) return False - sender = callback.from_user if callback is not None else message.from_user if sender is None or sender.id != message.chat.id: return False @@ -380,6 +386,29 @@ async def _authorize_update(self, update: Update) -> bool: logger.exception("Telegram access control failed closed") return False + async def _record_identity_if_private( + self, + message: Message | None, + sender: User | None, + ) -> None: + """Persist a direct-chat sender label when local storage is available.""" + if ( + message is None + or message.chat.type != ChatType.PRIVATE + or sender is None + or sender.id != message.chat.id + ): + return + + try: + await self._get_access_storage().record_identity( + self._identity_from_user(sender) + ) + except RuntimeError: + logger.debug("Telegram identity storage is unavailable; skipping label") + except Exception: + logger.exception("Telegram identity recording failed; continuing update") + async def _grant_legacy_access_if_applicable( self, message: Message, diff --git a/tests/test_telegram_access.py b/tests/test_telegram_access.py index ed49601..0a78e95 100644 --- a/tests/test_telegram_access.py +++ b/tests/test_telegram_access.py @@ -140,12 +140,17 @@ def _callback_update(*, message: dict[str, object] | None) -> Update: ) -def _bot(storage: TelegramAccessStorage, *, has_history: bool) -> TelegramBot: +def _bot( + storage: TelegramAccessStorage, + *, + has_history: bool, + access_code: str | None = "test-access-code", +) -> TelegramBot: config = TelegramConfig.model_validate( { "TELEGRAM_ENABLED": True, "TELEGRAM_BOT_TOKEN": "test-token-123", - "TELEGRAM_ACCESS_CODE": "test-access-code", + "TELEGRAM_ACCESS_CODE": access_code, } ) bot = TelegramBot(config, _Runtime(has_history), access_storage=storage) # type: ignore[arg-type] @@ -181,6 +186,52 @@ async def test_historical_private_chat_is_grandfathered( assert await storage.is_authorized(42, "rotated-code") is True +@pytest.mark.asyncio +async def test_unconfigured_access_control_still_records_identity( + storage: TelegramAccessStorage, +) -> None: + """Legacy open mode must still populate dashboard identity labels.""" + bot = _bot(storage, has_history=False, access_code=None) + + assert await bot._authorize_update(_update("normal message")) is True + + row = await storage._fetch_one( + """ + SELECT display_name, username + FROM telegram_identities + WHERE telegram_user_id = ? + """, + (42,), + ) + assert row == {"display_name": "Ada", "username": "ada"} + + +@pytest.mark.asyncio +async def test_open_mode_continues_when_identity_storage_is_unavailable( + storage: TelegramAccessStorage, +) -> None: + """An unavailable optional label store must not block open-mode traffic.""" + bot = _bot(storage, has_history=False, access_code=None) + bot.access_storage = None + + with patch( + "blacki.telegram.bot.get_telegram_access_storage", + side_effect=RuntimeError("storage is not initialized"), + ): + assert await bot._authorize_update(_update("normal message")) is True + + +@pytest.mark.asyncio +async def test_open_mode_continues_when_identity_write_fails( + storage: TelegramAccessStorage, +) -> None: + """A local identity write failure must not block the Telegram update.""" + await storage._conn.close() + bot = _bot(storage, has_history=False, access_code=None) + + assert await bot._authorize_update(_update("normal message")) is True + + @pytest.mark.asyncio async def test_rotated_passphrase_user_is_not_regrandfathered( storage: TelegramAccessStorage,