add password authentication for bot users

This commit is contained in:
kandrusyak
2026-07-25 15:57:30 +03:00
parent c04fb9480b
commit 053b124a9a
8 changed files with 210 additions and 0 deletions

75
tests/test_auth.py Normal file
View File

@@ -0,0 +1,75 @@
import tempfile
import unittest
from pathlib import Path
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock
from telegram.constants import ChatType
from telegram.ext import ApplicationHandlerStop
from assistant_bot.auth import authentication_guard
from assistant_bot.storage import AssistantStorage
class AuthenticationGuardTests(unittest.IsolatedAsyncioTestCase):
def setUp(self) -> None:
self.temporary_directory = tempfile.TemporaryDirectory()
self.storage = AssistantStorage(
Path(self.temporary_directory.name) / "assistant.sqlite3"
)
def tearDown(self) -> None:
self.temporary_directory.cleanup()
def make_update_and_context(self, text: str) -> tuple[Any, Any]:
message = SimpleNamespace(text=text, reply_text=AsyncMock())
update = SimpleNamespace(
effective_user=SimpleNamespace(id=42),
effective_chat=SimpleNamespace(type=ChatType.PRIVATE),
effective_message=message,
inline_query=None,
)
context = SimpleNamespace(
application=SimpleNamespace(
bot_data={
"storage": self.storage,
"assistant_password": "секрет",
}
)
)
return update, context
async def test_correct_password_authorizes_user(self) -> None:
update, context = self.make_update_and_context("секрет")
with self.assertRaises(ApplicationHandlerStop):
await authentication_guard(update, context)
self.assertTrue(self.storage.is_user_authorized(42))
update.effective_message.reply_text.assert_awaited_once_with(
"Пароль принят. Доступ открыт — повторно вводить его не нужно."
)
async def test_wrong_password_does_not_authorize_user(self) -> None:
update, context = self.make_update_and_context("неверно")
with self.assertRaises(ApplicationHandlerStop):
await authentication_guard(update, context)
self.assertFalse(self.storage.is_user_authorized(42))
update.effective_message.reply_text.assert_awaited_once_with(
"Неверный пароль. Попробуй еще раз."
)
async def test_authorized_user_passes_guard(self) -> None:
self.storage.authorize_user(42)
update, context = self.make_update_and_context("обычное сообщение")
await authentication_guard(update, context)
update.effective_message.reply_text.assert_not_awaited()
if __name__ == "__main__":
unittest.main()

View File

@@ -67,5 +67,25 @@ class AssistantModeConfigTests(unittest.TestCase):
)
class PasswordConfigTests(unittest.TestCase):
def test_password_is_required(self) -> None:
with patch("assistant_bot.config.load_env_file"), patch.dict(
os.environ,
{},
clear=False,
):
os.environ.pop(config.PASSWORD_ENV_NAME, None)
with self.assertRaises(RuntimeError):
config.get_assistant_password()
def test_password_is_read_from_environment(self) -> None:
with patch("assistant_bot.config.load_env_file"), patch.dict(
os.environ,
{config.PASSWORD_ENV_NAME: " test-password "},
clear=False,
):
self.assertEqual(config.get_assistant_password(), "test-password")
if __name__ == "__main__":
unittest.main()

View File

@@ -53,6 +53,18 @@ class AgentDecisionTests(unittest.TestCase):
class StorageTests(unittest.TestCase):
def test_user_authorization_is_persisted(self) -> None:
with tempfile.TemporaryDirectory() as directory:
database_path = Path(directory) / "assistant.sqlite3"
storage = AssistantStorage(database_path)
self.assertFalse(storage.is_user_authorized(42))
storage.authorize_user(42)
reopened_storage = AssistantStorage(database_path)
self.assertTrue(reopened_storage.is_user_authorized(42))
self.assertFalse(reopened_storage.is_user_authorized(43))
def test_legacy_user_settings_gets_yandex_model_column(self) -> None:
with tempfile.TemporaryDirectory() as directory:
database_path = Path(directory) / "assistant.sqlite3"