diff --git a/README b/README index ea8b1d8..01e4398 100644 --- a/README +++ b/README @@ -16,10 +16,10 @@ Installation: USE messenger; # For new installation: - source sql/create.sql + source main/create.sql # For updating existing installation: - source sql/migrate.sql + source main/migrate.sql # Create user with password CREATE USER 'messenger'@'localhost' IDENTIFIED BY 'your_secure_password'; diff --git a/main/create.sql b/main/create.sql new file mode 100644 index 0000000..9ac08fb --- /dev/null +++ b/main/create.sql @@ -0,0 +1,29 @@ +CREATE TABLE messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + signature TEXT, + is_encrypted BOOLEAN DEFAULT FALSE, + INDEX idx_created_at (created_at) +); + +CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + pgp_key TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_moderator TINYINT(1) NOT NULL DEFAULT 0, + login_attempts INT NOT NULL DEFAULT 0, + last_attempt TIMESTAMP NULL, + is_blocked TINYINT(1) NOT NULL DEFAULT 0, + block_reason TEXT, + INDEX idx_username (username) +); + +CREATE TABLE registrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_created_at (created_at) +); \ No newline at end of file diff --git a/main/db.sql b/main/db.sql new file mode 100644 index 0000000..9a12755 --- /dev/null +++ b/main/db.sql @@ -0,0 +1,81 @@ +CREATE TABLE messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + signature TEXT, + is_encrypted BOOLEAN DEFAULT FALSE, + INDEX idx_created_at (created_at) +); + +CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + pgp_key TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_moderator TINYINT(1) NOT NULL DEFAULT 0, + login_attempts INT NOT NULL DEFAULT 0, + last_attempt TIMESTAMP NULL, + is_blocked TINYINT(1) NOT NULL DEFAULT 0, + block_reason TEXT, + INDEX idx_username (username) +); + +CREATE TABLE registrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_created_at (created_at) +); + +DELIMITER // +CREATE OR REPLACE PROCEDURE migrate_if_needed() +BEGIN + IF NOT EXISTS (SELECT * FROM information_schema.tables WHERE table_name = 'messages') THEN + CREATE TABLE messages ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + message TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + signature TEXT, + is_encrypted BOOLEAN DEFAULT FALSE, + INDEX idx_created_at (created_at) + ); + END IF; + + IF NOT EXISTS (SELECT * FROM information_schema.tables WHERE table_name = 'users') THEN + CREATE TABLE users ( + id INT AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + pgp_key TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + is_moderator TINYINT(1) NOT NULL DEFAULT 0, + login_attempts INT NOT NULL DEFAULT 0, + last_attempt TIMESTAMP NULL, + is_blocked TINYINT(1) NOT NULL DEFAULT 0, + block_reason TEXT, + INDEX idx_username (username) + ); + END IF; + + IF NOT EXISTS (SELECT * FROM information_schema.tables WHERE table_name = 'registrations') THEN + CREATE TABLE registrations ( + id INT AUTO_INCREMENT PRIMARY KEY, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_created_at (created_at) + ); + END IF; + + IF EXISTS ( + SELECT * FROM information_schema.columns + WHERE table_name = 'registrations' AND column_name = 'ip' + ) THEN + DROP INDEX IF EXISTS idx_ip_created ON registrations; + ALTER TABLE registrations DROP COLUMN ip; + END IF; +END // +DELIMITER ; + +CALL migrate_if_needed(); +DROP PROCEDURE IF EXISTS migrate_if_needed; \ No newline at end of file diff --git a/main/login.php b/main/login.php index ea7d549..f74ec5a 100644 --- a/main/login.php +++ b/main/login.php @@ -1,88 +1,124 @@ PDO::ERRMODE_EXCEPTION] - ); - - $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); - $stmt->execute([$username]); - $user = $stmt->fetch(); - - if ($user && password_verify($password, $user['password'])) { - if ($user['is_blocked']) { - $error = "Аккаунт заблокирован: " . htmlspecialchars($user['block_reason']); - } else { - $_SESSION['user_id'] = $user['id']; - $_SESSION['username'] = $user['username']; - $_SESSION['is_moderator'] = $user['is_moderator']; - - $stmt = $pdo->prepare("UPDATE users SET login_attempts = 0, last_attempt = NULL WHERE id = ?"); - $stmt->execute([$user['id']]); - - header("Location: index.php"); - exit; - } - } else { + $db = new PDO( + "mysql:host={$config['db']['host']};dbname={$config['db']['name']}", + $config['db']['user'], + $config['db']['pass'] + ); + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $debug[] = "Database connected"; +} catch (PDOException $e) { + $debug[] = "Database error: " . $e->getMessage(); + die("Database connection error: " . $e->getMessage()); +} + +$error = ''; +$success = ''; + +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING); + $password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING); + + $debug[] = "Login attempt for: " . $username; + + if ($username && $password) { + try { + $stmt = $db->prepare('SELECT id, password, is_blocked, login_attempts, last_attempt FROM users WHERE username = ?'); + $stmt->execute([$username]); + $user = $stmt->fetch(PDO::FETCH_ASSOC); + + $debug[] = "User found: " . ($user ? 'yes' : 'no'); + if ($user) { - $stmt = $pdo->prepare("UPDATE users SET login_attempts = login_attempts + 1, last_attempt = CURRENT_TIMESTAMP WHERE id = ?"); - $stmt->execute([$user['id']]); - - if ($user['login_attempts'] >= 4) { - $stmt = $pdo->prepare("UPDATE users SET is_blocked = 1, block_reason = 'Превышено количество попыток входа' WHERE id = ?"); + if ($user['is_blocked']) { + $error = 'Account is blocked'; + $debug[] = "Account blocked"; + } else if ($user['login_attempts'] >= 5 && strtotime($user['last_attempt']) > strtotime('-15 minutes')) { + $error = 'Too many login attempts'; + $debug[] = "Too many attempts"; + } else if (password_verify($password, $user['password'])) { + $stmt = $db->prepare('UPDATE users SET login_attempts = 0, last_attempt = NOW() WHERE id = ?'); $stmt->execute([$user['id']]); - $error = "Аккаунт заблокирован из-за превышения количества попыток входа"; + $_SESSION['user_id'] = $user['id']; + $_SESSION['username'] = $username; + $debug[] = "Login successful"; + header('Location: index.php'); + exit; } else { - $error = "Неверный пароль"; + $stmt = $db->prepare('UPDATE users SET login_attempts = login_attempts + 1, last_attempt = NOW() WHERE id = ?'); + $stmt->execute([$user['id']]); + $error = 'Invalid password'; + $debug[] = "Invalid password"; } } else { - $error = "Пользователь не найден"; + $error = 'User not found'; + $debug[] = "User not found"; } + } catch (PDOException $e) { + $error = 'Server error'; + $debug[] = "SQL Error: " . $e->getMessage(); + $debug[] = "SQL State: " . $e->getCode(); } - } catch (PDOException $e) { - $error = "Ошибка сервера"; } } ?> - + - - - Вход - + + Text0Nly - Login + -
-

Вход

- -
- - -
- -
-
- - -
-
- - -
- -
-

Нет аккаунта? Зарегистрироваться

-
+

Login

+ +
+ + +
+ + +
+
+ +
+
+ +
+ +
+

Register | Back to chat

+ + +
+ Debug info:
+ +
+ +
+ - \ No newline at end of file + + \ No newline at end of file diff --git a/main/migrate.sql b/main/migrate.sql new file mode 100644 index 0000000..4892634 --- /dev/null +++ b/main/migrate.sql @@ -0,0 +1,24 @@ +DELIMITER // +CREATE OR REPLACE PROCEDURE migrate_if_needed() +BEGIN + IF EXISTS ( + SELECT * FROM information_schema.columns + WHERE table_name = 'registrations' AND column_name = 'ip' + ) THEN + DROP INDEX IF EXISTS idx_ip_created ON registrations; + ALTER TABLE registrations DROP COLUMN ip; + END IF; + + IF NOT EXISTS ( + SELECT * FROM information_schema.columns + WHERE table_name = 'users' AND column_name = 'is_blocked' + ) THEN + ALTER TABLE users + ADD COLUMN is_blocked TINYINT(1) NOT NULL DEFAULT 0, + ADD COLUMN block_reason TEXT; + END IF; +END // +DELIMITER ; + +CALL migrate_if_needed(); +DROP PROCEDURE IF EXISTS migrate_if_needed; \ No newline at end of file diff --git a/main/register.php b/main/register.php index 8b22af6..a8e79d2 100644 --- a/main/register.php +++ b/main/register.php @@ -1,83 +1,100 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + +$error = ''; +$success = ''; + if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $username = $_POST['username'] ?? ''; - $password = $_POST['password'] ?? ''; - $pgp_key = $_POST['pgp_key'] ?? ''; - - try { - $pdo = new PDO( - "mysql:host={$config['db_host']};dbname={$config['db_name']};charset=utf8mb4", - $config['db_user'], - $config['db_pass'], - [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION] - ); - - $stmt = $pdo->prepare("SELECT COUNT(*) FROM registrations WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)"); - $stmt->execute(); - $recent_registrations = $stmt->fetchColumn(); - - if ($recent_registrations >= 3) { - $error = "Слишком много регистраций за последний час. Попробуйте позже."; + $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING); + $password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING); + $pgp_key = filter_input(INPUT_POST, 'pgp_key', FILTER_SANITIZE_STRING); + + if ($username && $password) { + if (strlen($username) > 50 || strlen($password) < 8 || !preg_match('/^[a-zA-Z0-9_]+$/', $username)) { + $error = 'Invalid data'; + } else if (strlen($pgp_key) > 4096) { + $error = 'PGP key is too long'; } else { - $stmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE username = ?"); - $stmt->execute([$username]); - if ($stmt->fetchColumn() > 0) { - $error = "Пользователь с таким именем уже существует"; + $stmt = $db->prepare('SELECT COUNT(*) FROM registrations WHERE created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)'); + $stmt->execute(); + $count = $stmt->fetchColumn(); + + if ($count >= 20) { + $error = 'Registration limit exceeded'; } else { - $hashed_password = password_hash($password, PASSWORD_DEFAULT); - - $stmt = $pdo->prepare("INSERT INTO users (username, password, pgp_key) VALUES (?, ?, ?)"); - $stmt->execute([$username, $hashed_password, $pgp_key]); - - $stmt = $pdo->prepare("INSERT INTO registrations (created_at) VALUES (NOW())"); - $stmt->execute(); - - $success = "Регистрация успешна! Теперь вы можете войти."; + try { + $stmt = $db->prepare('INSERT INTO users (username, password, pgp_key, login_attempts, last_attempt) VALUES (?, ?, ?, 0, NOW())'); + $stmt->execute([ + $username, + password_hash($password, PASSWORD_DEFAULT, ['cost' => 12]), + $pgp_key + ]); + + $stmt = $db->prepare('INSERT INTO registrations () VALUES ()'); + $stmt->execute(); + + $success = 'Registration successful'; + } catch (PDOException $e) { + $error = 'Username already exists'; + } } } - } catch (PDOException $e) { - $error = "Ошибка сервера"; } } ?> - + - - - Регистрация - + + Text0Nly - Registration + -
-

Регистрация

- -
- - -
- -
-
- - -
-
- - -
-
- - -
- -
-

Уже есть аккаунт? Войти

-
+

Registration

+ +
+ + +
+ + +
+
+ +
+
+ +
+
+ +
+ +
+

Back to chat

- \ No newline at end of file + + \ No newline at end of file