From 8bdd0c784e18938046930d6996e0f26220212763 Mon Sep 17 00:00:00 2001 From: Yuriy Yuriev Date: Sat, 18 Jul 2026 18:33:54 +0700 Subject: [PATCH] fix --- auth/manager.py | 27 +++++---- auth/storage.py | 31 +++------- handlers/commands.py | 138 +++++++------------------------------------ 3 files changed, 44 insertions(+), 152 deletions(-) diff --git a/auth/manager.py b/auth/manager.py index 915e01d..d099cc9 100644 --- a/auth/manager.py +++ b/auth/manager.py @@ -1,8 +1,6 @@ -import asyncio -import hashlib import json -import secrets import logging +import secrets from datetime import datetime, timedelta from pathlib import Path from typing import Dict, Optional @@ -16,7 +14,11 @@ _SESSIONS_FILE = Path("data/sessions.json") class AuthManager: - """Управляет авторизацией: хеширование паролей, сессии, верификация, роли.""" + """Управляет сессиями и ролями. + + Личность пользователя подтверждает Telegram, поэтому пароля для входа нет. + ADMIN_PASSWORD остаётся единственным секретом — им повышают роль до admin. + """ def __init__(self, session_timeout_minutes: int = 120): self._sessions: Dict[int, datetime] = {} @@ -28,15 +30,16 @@ class AuthManager: self._restore_sessions() @staticmethod - def _hash_password(password: str, salt: str = None) -> tuple: - if salt is None: - salt = secrets.token_hex(16) - result = hashlib.sha256((salt + password).encode()).hexdigest() - return result, salt + def verify_admin_password(password: str) -> bool: + """Проверяет пароль администратора. - def verify_password(self, password: str, password_hash: str, salt: str) -> bool: - computed, _ = self._hash_password(password, salt) - return secrets.compare_digest(computed, password_hash) + Пустой ADMIN_PASSWORD означает, что админ-режим отключён — иначе + ненастроенный бот пускал бы в админку по пустой строке. + """ + if not ADMIN_PASSWORD: + logger.warning("ADMIN_PASSWORD not set — admin mode is disabled") + return False + return secrets.compare_digest(password, ADMIN_PASSWORD) def is_authenticated(self, user_id: int) -> bool: if user_id not in self._sessions: diff --git a/auth/storage.py b/auth/storage.py index ada89ae..c135e5c 100644 --- a/auth/storage.py +++ b/auth/storage.py @@ -1,51 +1,38 @@ """ -Хранилище учётных данных пользователей. +Хранилище зарегистрированных пользователей. """ import asyncio import json import logging +from datetime import datetime from pathlib import Path -from typing import Optional, Dict logger = logging.getLogger(__name__) class AuthStorage: - """Хранилище паролей пользователей в JSON-файле.""" + """Реестр пользователей бота в JSON-файле. + + Паролей не хранит: личность подтверждает Telegram, идентификатором + служит user_id. + """ def __init__(self, file_path: str = "data/users.json"): self.file_path = Path(file_path) self.file_path.parent.mkdir(parents=True, exist_ok=True) self._lock = asyncio.Lock() - async def register(self, user_id: int, password_hash: str, salt: str) -> bool: + async def register(self, user_id: int) -> bool: async with self._lock: data = await asyncio.to_thread(self._load_sync) if str(user_id) in data: return False - data[str(user_id)] = {"password_hash": password_hash, "salt": salt} + data[str(user_id)] = {"registered_at": datetime.now().isoformat()} await asyncio.to_thread(self._save_sync, data) logger.info(f"User {user_id} registered") return True - async def get_user(self, user_id: int) -> Optional[Dict]: - async with self._lock: - data = await asyncio.to_thread(self._load_sync) - return data.get(str(user_id)) - - async def change_password(self, user_id: int, new_hash: str, new_salt: str) -> bool: - async with self._lock: - data = await asyncio.to_thread(self._load_sync) - user_str = str(user_id) - if user_str not in data: - return False - data[user_str]["password_hash"] = new_hash - data[user_str]["salt"] = new_salt - await asyncio.to_thread(self._save_sync, data) - logger.info(f"User {user_id} changed password") - return True - async def delete_user(self, user_id: int) -> bool: async with self._lock: data = await asyncio.to_thread(self._load_sync) diff --git a/handlers/commands.py b/handlers/commands.py index b49bcba..b6c5eda 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -33,7 +33,6 @@ from services.payment_service import HelketPayment from services.twitch_api import get_viewer_count from auth.manager import AuthManager from auth.storage import AuthStorage -from auth import ADMIN_PASSWORD logger = logging.getLogger(__name__) @@ -75,7 +74,7 @@ class BotInterface: self._menu_top_msg: Dict[int, int] = {} # user_id -> reply keyboard message_id self._chat_history: Dict[int, list] = {} self._bot_ref = bot_ref - self._waiting_password: Dict[int, str] = {} # user_id -> "register" or "login" + self._waiting_password: Dict[int, str] = {} # user_id -> "admin_upgrade" self._start_time = datetime.now() async def _update_commands(self): @@ -102,74 +101,12 @@ class BotInterface: await interface._show_main_menu(message) return - if interface.auth_manager.is_authenticated(user_id): - await interface._show_user_menu(message) - return - - exists = await interface.auth_storage.user_exists(user_id) - if exists: - sent = await message.answer("С возвращением! Введите пароль:") - interface._waiting_password[user_id] = "login" - else: - sent = await message.answer( - "Добро пожаловать!\n\nПридумайте пароль для входа (мин. 4 символа):" - ) - interface._waiting_password[user_id] = "register" - interface._menu_msg[user_id] = sent.message_id - - @dp.message(Command("register")) - async def cmd_register(message: Message): - """Регистрация: /register <пароль>""" - await interface._safe_delete(message.bot, message.chat.id, message.message_id) - parts = message.text.split(maxsplit=1) - if len(parts) < 2 or len(parts[1]) < 4: - await interface._send_temp(message, "❌ Использование: /register <пароль от 4 символов>") - return - password = parts[1] - user_id = message.from_user.id - exists = await interface.auth_storage.user_exists(user_id) - if exists: - await interface._send_temp(message, "⚠️ Вы уже зарегистрированы. Используйте /login <пароль>") - return - password_hash, salt = interface.auth_manager._hash_password(password) - await interface.auth_storage.register(user_id, password_hash, salt) - is_admin = (password == ADMIN_PASSWORD) - interface.auth_manager.login(user_id, is_admin=is_admin) - if is_admin: - await interface._update_commands() - await interface._show_main_menu(message) - else: - await interface._show_user_menu(message) - - @dp.message(Command("login")) - async def cmd_login(message: Message): - """Вход: /login <пароль>""" - await interface._safe_delete(message.bot, message.chat.id, message.message_id) - parts = message.text.split(maxsplit=1) - if len(parts) < 2: - await interface._send_temp(message, "❌ Использование: /login <пароль>") - return - password = parts[1] - user_id = message.from_user.id - user_data = await interface.auth_storage.get_user(user_id) - if not user_data: - await interface._send_temp(message, "❌ Вы не зарегистрированы.\nИспользуйте /register <пароль>") - return - if interface.auth_manager.is_locked_out(user_id): - await interface._send_temp(message, "🔒 Слишком много попыток. Попробуйте позже.") - return - if password == ADMIN_PASSWORD: - interface.auth_manager.login(user_id, is_admin=True) - await interface._update_commands() - await interface._show_main_menu(message) - elif interface.auth_manager.verify_password( - password, user_data["password_hash"], user_data["salt"] - ): + # Пароля для входа нет — личность подтверждает сам Telegram. + # Первый /start заодно регистрирует пользователя. + await interface.auth_storage.register(user_id) + if not interface.auth_manager.is_authenticated(user_id): interface.auth_manager.login(user_id, is_admin=False) - await interface._show_user_menu(message) - else: - interface.auth_manager.record_failed_attempt(user_id) - await interface._send_temp(message, "❌ Неверный пароль") + await interface._show_user_menu(message) @dp.message(Command("logout")) async def cmd_logout(message: Message): @@ -1345,62 +1282,27 @@ class BotInterface: await message.answer(f"❌ Не удалось отправить: {e}") return - # Ввод пароля (неавторизованный пользователь ждёт пароль) + # Ввод пароля администратора (единственный оставшийся пароль) if user_id in interface._waiting_password: if not text: return # Пароль удаляем немедленно await interface._safe_delete(message.bot, message.chat.id, message.message_id) - mode = interface._waiting_password.pop(user_id) + interface._waiting_password.pop(user_id) - if mode == "register": - if len(text) < 4: - sent = await message.answer("Пароль слишком короткий, мин. 4 символа:") - interface._menu_msg[user_id] = sent.message_id - interface._waiting_password[user_id] = "register" - return - password_hash, salt = interface.auth_manager._hash_password(text) - await interface.auth_storage.register(user_id, password_hash, salt) - is_admin = (text == ADMIN_PASSWORD) - interface.auth_manager.login(user_id, is_admin=is_admin) - if is_admin: - await interface._update_commands() - await interface._show_main_menu(message) - else: - await interface._show_user_menu(message) + if interface.auth_manager.is_locked_out(user_id): + await interface._send_temp(message, "🔒 Слишком много попыток. Попробуйте позже.") + return - elif mode == "login": - user_data = await interface.auth_storage.get_user(user_id) - if not user_data: - await interface._send_temp(message, "Вы не зарегистрированы") - return - if interface.auth_manager.is_locked_out(user_id): - await interface._send_temp(message, "Слишком много попыток, попробуйте позже") - return - if text == ADMIN_PASSWORD: - interface.auth_manager.login(user_id, is_admin=True) - await interface._update_commands() - await interface._show_main_menu(message) - elif interface.auth_manager.verify_password( - text, user_data["password_hash"], user_data["salt"] - ): - interface.auth_manager.login(user_id, is_admin=False) - await interface._show_user_menu(message) - else: - interface.auth_manager.record_failed_attempt(user_id) - sent = await message.answer("Неверный пароль, попробуйте ещё раз:") - interface._menu_msg[user_id] = sent.message_id - interface._waiting_password[user_id] = "login" - - elif mode == "admin_upgrade": - if text == ADMIN_PASSWORD: - interface.auth_manager.login(user_id, is_admin=True) - await interface._update_commands() - await interface._show_main_menu(message) - else: - sent = await message.answer("Неверный пароль администратора:") - interface._menu_msg[user_id] = sent.message_id - interface._waiting_password[user_id] = "admin_upgrade" + if interface.auth_manager.verify_admin_password(text): + interface.auth_manager.login(user_id, is_admin=True) + await interface._update_commands() + await interface._show_main_menu(message) + else: + interface.auth_manager.record_failed_attempt(user_id) + sent = await message.answer("Неверный пароль администратора:") + interface._menu_msg[user_id] = sent.message_id + interface._waiting_password[user_id] = "admin_upgrade" return # Ввод шагов создания задачи (пользователь)