From b8f2a91411bbf8f2b8311a95ad2ed287df9d6d56 Mon Sep 17 00:00:00 2001 From: Andrea Date: Tue, 22 Apr 2025 23:04:35 +0200 Subject: [PATCH] Add kick and file management features - Implement kick functionality with add, remove, and check operations - Add file management with create, request, and verify operations - Update database schema with new tables and columns - Enhance command handling in client module for new features - Fix argument order in verify_password function --- db.sqlite | Bin 28672 -> 49152 bytes migrations/004_create_kick_table.sql | 7 + migrations/005_create_files_table.sql | 8 + .../006_add_admin_verified_to_files.sql | 2 + src/client/mod.rs | 278 +++++++++++++++++- src/db/mod.rs | 151 +++++++++- 6 files changed, 436 insertions(+), 10 deletions(-) create mode 100644 migrations/004_create_kick_table.sql create mode 100644 migrations/005_create_files_table.sql create mode 100644 migrations/006_add_admin_verified_to_files.sql diff --git a/db.sqlite b/db.sqlite index 316f5189c81d754ecb903fc2b1b2066c0fbacd67..59a630a0cabb79c5afe34eea4261322f9620b8a5 100644 GIT binary patch delta 2505 zcmd5+e`p)`75|=2mUWU(U%QD>vZV7RHED!@NV>CZOC%|0S+0M`l68_C%h0H&yOVU5 zbf=t7mgBKGZpSt{Xq%ZWqipH+#|k5Vv_&i1C>y0^bTKrHu#G_7u#UiBC0$aMHf$~I zzUM@V8us^gho0Vj-+Q0;eV_Nf-?M+g&Ys0zJkWCj0MKRp?mfvNbcBJE z*aKYOg}-zzxt=Dwx_&{v;>3tOWR3Xj_04nkjZL2Dbi3Wyvxjs(SC;Z}S*n@B5#}On zDng~$iD-l}m#Cu-%6MdvN=CRDHcBmUGxIE$q2?l)LDQu06^UA8x$rd09i;;S4;722 zs8~80-L+SFz1U`d>`M-UCh@u?W_g`T&CExVDRzEg>p1+%hGUT8#fq$CS0zo(%aTY< z#N*Kj8>1#8Q*1h#qNdnrGUAza+T0#DCdi5?t;ynA*4%j(ZOqiV?v^zZn~W?Or99|B z#bfshMgAW06!XI5IquN0{gMOzk7!mj9vQko$j|oAIFazek-xl`E2p>wq0C#_1hn0 z2ezNc{LIVvjy(F_-hDrK9{irZ!*_IIew3?Yw{ZsehH<(mlaKZs{*lng*!VX)#5Z62 z_;LEkkMhL}H;EVCA3Qwxtq%`=efzn< zEA+wdZ2#luuf4M~eewCf-MAh7hWIa|jQ5*6Up;Ej39kY8Is6pfgnx(E;k9NDwGFp! zdaWUwtpmrfjt(TPRwQv8Ns9%?34(O~5Llfa;ChO@?DUY!PS5?Dt^GQA+c(^^_1?YvjtATuobN`|Gt-FqurAmgRD>Qi@EhgrDRVGFq;B za$cbIh^kKqeB*I({q$PGbpw05Z!>PUg9@+7YFSkzgv{k?i;?w+u&iY=iwPmPoXG`7 z#X^5tE00gF7MMm*IHPfqbeXBr3>TP9j?bmjji{JQ`sN4X#W9UdBjo+N$g)zBP1vb< zzAAH?yuPwj36@83Q)4wRx;;b=?3TZrwUI!)*3IJ3uuMb0uCmBQ>O&-p^|VRiS0EfAzR_*K7wZ zGb)i?os{_oyH;J-1&!whmJ{lPcYy7mI?1iGlB}n=N=D-|QX;JS!i5EGRhw0LZ7y7j zB>aKok}q0lzIXgEX2JGs-8%6yG){8WZFa}Fd`o7vr}Tw{D9`)X618xCJXo(WtJ5dz zg?>qt%ZW86&&6ZaaFrhLaVoQz5cvu{#TAvPKU$f}2bOiA;7d5*PkkoY@uq1({)+V`3KN9z6`IbrJDDlh~cW3Z_hvnLNqgmb$~--Ohwj& ztZ^2E(mmuxqcMzDhXu9bZ4dMZLcSoP3O7iF9iPL`;3pzRRJ$=90MD71Os0;|90*Oz64$#o-Lad z1s-s1j^Jiv;^P0o$RsW<&e$9~If?uG=6JrPj9koocNqBR@!Rm-*{rB=j&JiFISrs7 z{|5&CKm6bLKWr8(c*w8Lz{ z7lTYc@5p>k4hG3ae+~|Yg3P@1(&EVi_VSJ;ZUN<96@fvarFoUboYSJFaxh-11?}Ju=DR^;NQvrmH##W4gSOY zXZari!*VfyASbgpV{&F1UrR=gIN?Ln9l(gN-WCE uX9uZE2kOnsWa9-|!N~uWf&VKwHYNseDRMFb^_CW=78P^jaezCO8~^}!1g#|i 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) + } }