diff --git a/db.sqlite b/db.sqlite index 316f518..59a630a 100644 Binary files a/db.sqlite and b/db.sqlite differ diff --git a/migrations/004_create_kick_table.sql b/migrations/004_create_kick_table.sql new file mode 100644 index 0000000..09693ef --- /dev/null +++ b/migrations/004_create_kick_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS kick ( + id INTEGER PRIMARY KEY AUTOINCREMENT, -- Unique ID for each kick + user_name VARCHAR(255) NOT NULL -- ID of the user who made the kick +); + +-- -- Create an index on the user_name column for faster lookups +CREATE INDEX IF NOT EXISTS idx_kick_user_name ON kick (user_name); diff --git a/migrations/005_create_files_table.sql b/migrations/005_create_files_table.sql new file mode 100644 index 0000000..8dea5ac --- /dev/null +++ b/migrations/005_create_files_table.sql @@ -0,0 +1,8 @@ +CREATE TABLE IF NOT EXISTS files ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + path VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_files_name ON files (name); diff --git a/migrations/006_add_admin_verified_to_files.sql b/migrations/006_add_admin_verified_to_files.sql new file mode 100644 index 0000000..e5ad31e --- /dev/null +++ b/migrations/006_add_admin_verified_to_files.sql @@ -0,0 +1,2 @@ +ALTER TABLE files +ADD COLUMN admin_verified BOOLEAN DEFAULT FALSE; diff --git a/src/client/mod.rs b/src/client/mod.rs index 7c3ce69..cf663aa 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -5,8 +5,9 @@ pub(crate) mod handlers { }; use crate::db::users::{ - ban_user, check_ban, check_for_account, create_user, get_ban_reason, hash_password, - unban_user, verify_admin, verify_password, + add_kick, add_new_file, add_verified_flag_to_file, ban_user, change_password, check_ban, + check_file_verified, check_for_account, check_kick, create_user, get_ban_reason, + hash_password, remove_kick, request_file, unban_user, verify_admin, verify_password, }; use base64::{engine::general_purpose::STANDARD as BASE64, Engine as _}; use log::{debug, error, info}; @@ -147,7 +148,7 @@ pub(crate) mod handlers { .unwrap(); // verifiy password let password = String::from_utf8(decrypted)?; - if verify_password(&password, &username).await.is_ok() { + if verify_password(&username, &password).await? == true { info!("Password verified successfully"); } else { info!("Password verification failed"); @@ -245,6 +246,22 @@ pub(crate) mod handlers { info!("Parsing message"); let parsed_message = parse_message(message.as_str()); + + if check_kick(&username).await.unwrap() == true { + info!("User {} is kicked", username); + let message = format!("User {} is kicked", username); + let _ = tx.send(message); + remove_kick(&username).await.unwrap(); + break; + } + + if check_ban(&username).await.unwrap() == true { + info!("User {} is banned", username); + let message = format!("User {} is banned", username); + let _ = tx.send(message); + break; + } + // Handle commands if !parsed_message.command.is_empty() { match parsed_message.command[0].as_str() { @@ -284,6 +301,189 @@ pub(crate) mod handlers { break; } + "/kick" => { + if parsed_message.argument.is_empty() { + error!("Invalid /kick format. Usage: /kick username"); + match tx.send( + format!("Error! Invalid /kick format").to_string(), + ) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + continue; + } + + match verify_admin(&username_read).await { + Ok(true) => { + info!("User {} is admin", username); + let target_user = &parsed_message.argument[0]; + info!("Kicking user: {}", target_user); + add_kick(&target_user).await.unwrap(); + match tx.send(format!( + "User {} has been kicked", + target_user + )) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + } + Ok(false) => { + error!("User {} is not admin", username); + continue; + } + Err(e) => { + error!("Error verifying admin: {:?}", e); + continue; + } + } + } + + "/addfile" => { + if parsed_message.argument.is_empty() { + error!("Invalid /addfile format. Usage: /addfile filename link"); + match tx.send( + format!("Invalid /addfile format. Usage: /addfile filename link").to_string(), + ) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + continue; + } + + let file_name = &parsed_message.argument[0]; + let file_link = &parsed_message.argument[1]; + info!("Adding file: {}", file_name); + info!("File link: {}", file_link); + + add_new_file(&file_name, &file_link).await.unwrap(); + + match tx.send(format!("File {} has been added", file_name)) { + Ok(_) => { + info!("Error message sent to client {}", username_write) + } + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + } + + "/verifylink" => { + if parsed_message.argument.is_empty() { + error!("Invalid /verifylink format. Usage: /verifylink filename"); + match tx.send( + format!("Invalid /verifylink format. Usage: /verifylink filename").to_string(), + ) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + continue; + } + + let file_name = &parsed_message.argument[0]; + info!("Verifying link for file: {}", file_name); + + match verify_admin(&username).await { + Ok(true) => { + info!("User {} is admin", username); + add_verified_flag_to_file(file_name).await.unwrap(); + match tx.send(format!( + "File {} has been verified", + file_name + )) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + } + Ok(false) => { + error!("User {} is not admin", username); + continue; + } + Err(e) => { + error!("Error verifying admin: {:?}", e); + continue; + } + } + } + + "/requestfile" => { + if parsed_message.argument.is_empty() { + error!("Invalid /requestfile format. Usage: /requestfile filename"); + match tx.send( + format!("Invalid /requestfile format. Usage: /requestfile filename").to_string(), + ) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + continue; + } + + let file_name = &parsed_message.argument[0]; + info!("Requesting file: {}", file_name); + + let file_link = request_file(file_name).await.unwrap(); + + match tx.send(format!("Link for {}: {}", file_name, file_link)) + { + Ok(_) => { + info!("message sent to client {}", username_write) + } + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + + if check_file_verified(file_name).await.unwrap() == true { + match tx.send(format!("dl! {}", file_link)) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + } + } + "/ban" => { if parsed_message.argument.is_empty() { error!("Invalid /ban format. Usage: /ban username"); @@ -430,6 +630,78 @@ pub(crate) mod handlers { } } + "/changepassword" => { + if parsed_message.argument.len() < 2 { + error!("Invalid /changepassword format. Usage: /changepassword old_password new_password"); + match tx + .send(format!("Invalid /changepassword format. Usage: /changepassword old_password new_password")) + { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + continue; + } + + let old_password = &parsed_message.argument[0]; + let new_password = &parsed_message.argument[1]; + + info!("Changing password for user {}", username); + info!("new password: {}", new_password); + info!("old password: {}", old_password); + + if verify_password(old_password, &username).await.is_ok() { + match change_password(&username, new_password).await { + Ok(_) => { + info!("Password changed successfully"); + let _ = tx.send( + "Password changed successfully".to_string(), + ); + } + Err(e) => { + error!("Error changing password: {:?}", e); + match tx.send(format!( + "Error changing password: {:?}", + e + )) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!( + "Failed to send error message: {:?}", + e + ); + break; + } + } + continue; + } + } + } else { + info!("Old password verification failed"); + match tx.send(format!( + "Invalid old password for user {}", + username + )) { + Ok(_) => info!( + "Error message sent to client {}", + username_write + ), + Err(e) => { + error!("Failed to send error message: {:?}", e); + break; + } + } + } + } + _ => { error!("Unknown command: {}", parsed_message.command[0]); match tx.send("Error! Unknown command".to_string()) { diff --git a/src/db/mod.rs b/src/db/mod.rs index 0bef885..79f4646 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -179,11 +179,29 @@ pub(crate) mod users { Ok(()) } - pub async fn verify_password( - // Use clearer argument names - username: &str, - provided_password: &str, - ) -> Result { + pub async fn change_password(username: &str, new_password: &str) -> Result<(), sqlx::Error> { + let pool = create_db_pool().await?; + + // Hash the new password + let new_password_hash = hash_password(new_password).await; + + // Update the password in the database + sqlx::query( + r#" + UPDATE users + SET password_hash = ? + WHERE username = ? + "#, + ) + .bind(new_password_hash) + .bind(username) + .execute(&pool) + .await?; + + Ok(()) + } + + pub async fn verify_password(username: &str, provided_password: &str) -> Result { let pool = create_db_pool().await?; // Propagates sqlx::Error // Fetch the stored hash for the user @@ -205,8 +223,7 @@ pub(crate) mod users { }; // Parse the stored hash - let parsed_hash = PasswordHash::new(&stored_hash_str).map_err(DbError::Hashing)?; // Manually map the error - + let parsed_hash = PasswordHash::new(&stored_hash_str).map_err(DbError::Hashing)?; let argon2 = Argon2::default(); let verification_result = @@ -252,4 +269,124 @@ pub(crate) mod users { Ok(is_admin == 1) } + + pub async fn add_kick(username: &str) -> Result<(), sqlx::Error> { + let pool = create_db_pool().await?; + + sqlx::query( + r#" + INSERT INTO kick (user_name) + VALUES (?) + "#, + ) + .bind(username) + .execute(&pool) + .await?; + Ok(()) + } + + pub async fn remove_kick(username: &str) -> Result<(), sqlx::Error> { + let pool = create_db_pool().await?; + + sqlx::query( + r#" + DELETE FROM kick + WHERE user_name = ? + "#, + ) + .bind(username) + .execute(&pool) + .await?; + Ok(()) + } + + pub async fn check_kick(username: &str) -> Result { + let pool = create_db_pool().await?; + + let exists = sqlx::query( + r#" + SELECT EXISTS( + SELECT 1 + FROM kick + WHERE user_name = ? + ) + "#, + ) + .bind(username) + .fetch_one(&pool) + .await? + .get::(0); + + Ok(exists == 1) + } + + pub async fn add_new_file(name: &str, link: &str) -> Result<(), sqlx::Error> { + let pool = create_db_pool().await?; + + sqlx::query( + r#" + INSERT INTO files (name, path) + VALUES (?, ?) + "#, + ) + .bind(name) + .bind(link) + .execute(&pool) + .await?; + Ok(()) + } + + pub async fn request_file(name: &str) -> Result { + let pool = create_db_pool().await?; + + let file_path = sqlx::query( + r#" + SELECT path + FROM files + WHERE name = ? + "#, + ) + .bind(name) + .fetch_one(&pool) + .await? + .get::(0); + + Ok(file_path) + } + + pub async fn add_verified_flag_to_file(name: &str) -> Result<(), sqlx::Error> { + let pool = create_db_pool().await?; + + sqlx::query( + r#" + UPDATE files + SET admin_verified = 1 + WHERE name = ? + "#, + ) + .bind(name) + .execute(&pool) + .await?; + Ok(()) + } + + pub async fn check_file_verified(name: &str) -> Result { + let pool = create_db_pool().await?; + + let is_verified = sqlx::query( + r#" + SELECT EXISTS( + SELECT 1 + FROM files + WHERE name = ? AND admin_verified = 1 + ) + "#, + ) + .bind(name) + .fetch_one(&pool) + .await? + .get::(0); + + Ok(is_verified == 1) + } }