diff --git a/README.md b/README.md index 3783925..06d7405 100644 Binary files a/README.md and b/README.md differ diff --git a/config.py b/config.py new file mode 100644 index 0000000..701ed9a --- /dev/null +++ b/config.py @@ -0,0 +1,28 @@ +# config.py +""" +Конфигурационный файл приложения +""" + +# Режим отладки +DEBUG_MODE = True + +# Настройки базы данных +DATABASE_PATH = 'users.db' + +# Правила валидации +MIN_USERNAME_LENGTH = 4 +MIN_PASSWORD_LENGTH = 8 + +# Сообщения для пользователя +MESSAGES = { + 'success_register': 'Регистрация успешна', + 'success_login': 'Вход выполнен успешно', + 'error_empty_username': 'Логин не может быть пустым', + 'error_short_username': f'Логин должен содержать минимум {MIN_USERNAME_LENGTH} символа', + 'error_username_exists': 'Пользователь с таким логином уже существует', + 'error_invalid_password_format': 'Пароль должен содержать минимум 8 символов, латинские буквы и цифры', + 'error_passwords_mismatch': 'Пароли не совпадают', + 'error_invalid_credentials': 'Неверный логин или пароль', + 'error_database': 'Ошибка базы данных' +} + diff --git a/database.py b/database.py new file mode 100644 index 0000000..447f2a5 --- /dev/null +++ b/database.py @@ -0,0 +1,162 @@ +# database.py +""" +Модуль для работы с базой данных SQLite3 +""" +import sqlite3 +import bcrypt +from typing import Optional, Tuple +from config import DATABASE_PATH, MESSAGES, DEBUG_MODE + + +def debug_log(message: str) -> None: + """Вывод отладочных сообщений""" + if DEBUG_MODE: + print(f"[DEBUG] {message}") + + +def init_database() -> None: + """Инициализация базы данных и создание таблицы пользователей""" + debug_log(f"Инициализация базы данных: {DATABASE_PATH}") + + try: + conn = sqlite3.connect(DATABASE_PATH) + cursor = conn.cursor() + + # Создание таблицы пользователей + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + conn.commit() + conn.close() + debug_log("База данных инициализирована успешно") + + except sqlite3.Error as e: + debug_log(f"Ошибка при инициализации базы данных: {e}") + raise + + +def user_exists(username: str) -> bool: + """ + Проверка существования пользователя в базе данных + + Args: + username: Логин пользователя + + Returns: + bool: True если пользователь существует, False в противном случае + """ + debug_log(f"Проверка существования пользователя: {username}") + + try: + conn = sqlite3.connect(DATABASE_PATH) + cursor = conn.cursor() + + cursor.execute('SELECT id FROM users WHERE username = ?', (username,)) + result = cursor.fetchone() + + conn.close() + + exists = result is not None + debug_log(f"Пользователь {'существует' if exists else 'не существует'}") + return exists + + except sqlite3.Error as e: + debug_log(f"Ошибка при проверке существования пользователя: {e}") + return False + + +def create_user(username: str, password: str) -> Tuple[bool, str]: + """ + Создание нового пользователя в базе данных + + Args: + username: Логин пользователя + password: Пароль пользователя (будет захеширован) + + Returns: + Tuple[bool, str]: (успех, сообщение) + """ + debug_log(f"Создание пользователя: {username}") + + try: + # Проверка на существование пользователя + if user_exists(username): + debug_log("Ошибка: пользователь уже существует") + return False, MESSAGES['error_username_exists'] + + # Хеширование пароля + password_bytes = password.encode('utf-8') + salt = bcrypt.gensalt() + password_hash = bcrypt.hashpw(password_bytes, salt) + + debug_log("Пароль захеширован") + + # Сохранение в базу данных + conn = sqlite3.connect(DATABASE_PATH) + cursor = conn.cursor() + + cursor.execute( + 'INSERT INTO users (username, password_hash) VALUES (?, ?)', + (username, password_hash) + ) + + conn.commit() + conn.close() + + debug_log("Пользователь успешно создан в базе данных") + return True, MESSAGES['success_register'] + + except sqlite3.Error as e: + debug_log(f"Ошибка базы данных: {e}") + return False, MESSAGES['error_database'] + + +def verify_user(username: str, password: str) -> Tuple[bool, str]: + """ + Проверка учетных данных пользователя + + Args: + username: Логин пользователя + password: Пароль пользователя + + Returns: + Tuple[bool, str]: (успех, сообщение) + """ + debug_log(f"Проверка учетных данных для: {username}") + + try: + conn = sqlite3.connect(DATABASE_PATH) + cursor = conn.cursor() + + cursor.execute( + 'SELECT password_hash FROM users WHERE username = ?', + (username,) + ) + result = cursor.fetchone() + conn.close() + + if result is None: + debug_log("Ошибка: пользователь не найден") + return False, MESSAGES['error_invalid_credentials'] + + stored_hash = result[0] + password_bytes = password.encode('utf-8') + + # Проверка пароля + if bcrypt.checkpw(password_bytes, stored_hash): + debug_log("Аутентификация успешна") + return True, MESSAGES['success_login'] + else: + debug_log("Ошибка: неверный пароль") + return False, MESSAGES['error_invalid_credentials'] + + except sqlite3.Error as e: + debug_log(f"Ошибка базы данных: {e}") + return False, MESSAGES['error_database'] + diff --git a/structure.txt b/structure.txt new file mode 100644 index 0000000..46d416e --- /dev/null +++ b/structure.txt @@ -0,0 +1,9 @@ +project/ +├── backend/ +│ ├── auth.py # Основной модуль авторизации +│ ├── database.py # Работа с базой данных +│ ├── validators.py # Валидаторы +│ └── config.py # Конфигурация +├── main.py # Основное окно приложения (шаблон) +└── users.db # База данных (создается автоматически) + diff --git a/validators.py b/validators.py new file mode 100644 index 0000000..89b8211 --- /dev/null +++ b/validators.py @@ -0,0 +1,96 @@ +# validators.py +""" +Модуль валидации данных пользователя +""" +import re +from typing import Tuple +from config import MIN_USERNAME_LENGTH, MIN_PASSWORD_LENGTH, MESSAGES, DEBUG_MODE + + +def debug_log(message: str) -> None: + """Вывод отладочных сообщений""" + if DEBUG_MODE: + print(f"[DEBUG] {message}") + + +def validate_username(username: str) -> Tuple[bool, str]: + """ + Проверка логина на корректность + + Args: + username: Логин пользователя + + Returns: + Tuple[bool, str]: (валидность, сообщение об ошибке) + """ + debug_log(f"Проверка логина: '{username}'") + + # Проверка на пустой логин + if not username or username.strip() == '': + debug_log("Ошибка: пустой логин") + return False, MESSAGES['error_empty_username'] + + # Проверка на минимальную длину + if len(username) < MIN_USERNAME_LENGTH: + debug_log(f"Ошибка: логин слишком короткий ({len(username)} символов)") + return False, MESSAGES['error_short_username'] + + debug_log("Логин прошел валидацию") + return True, '' + + +def validate_password(password: str) -> Tuple[bool, str]: + """ + Проверка пароля на соответствие требованиям: + - Минимум 8 символов + - Хотя бы одна латинская буква (a-z или A-Z) + - Хотя бы одна цифра (0-9) + - Любые другие символы разрешены + + Args: + password: Пароль пользователя + + Returns: + Tuple[bool, str]: (валидность, сообщение об ошибке) + """ + debug_log(f"Проверка пароля (длина: {len(password)})") + + # Проверка минимальной длины + if len(password) < MIN_PASSWORD_LENGTH: + debug_log(f"Ошибка: пароль слишком короткий ({len(password)} символов)") + return False, MESSAGES['error_invalid_password_format'] + + # Проверка наличия латинских букв + if not re.search(r'[a-zA-Z]', password): + debug_log("Ошибка: пароль не содержит латинских букв") + return False, MESSAGES['error_invalid_password_format'] + + # Проверка наличия цифр + if not re.search(r'\d', password): + debug_log("Ошибка: пароль не содержит цифр") + return False, MESSAGES['error_invalid_password_format'] + + debug_log("Пароль прошел валидацию") + return True, '' + + +def validate_password_confirmation(password: str, password_confirm: str) -> Tuple[bool, str]: + """ + Проверка совпадения пароля и его подтверждения + + Args: + password: Пароль + password_confirm: Подтверждение пароля + + Returns: + Tuple[bool, str]: (валидность, сообщение об ошибке) + """ + debug_log("Проверка совпадения паролей") + + if password != password_confirm: + debug_log("Ошибка: пароли не совпадают") + return False, MESSAGES['error_passwords_mismatch'] + + debug_log("Пароли совпадают") + return True, '' +