fix
This commit is contained in:
+15
-12
@@ -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:
|
||||
|
||||
+9
-22
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user