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
This commit is contained in:
Andrea Moro 2025-04-22 23:04:35 +02:00
parent 91b6942348
commit b8f2a91411
6 changed files with 436 additions and 10 deletions

BIN
db.sqlite

Binary file not shown.

View file

@ -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);

View file

@ -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);

View file

@ -0,0 +1,2 @@
ALTER TABLE files
ADD COLUMN admin_verified BOOLEAN DEFAULT FALSE;

View file

@ -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()) {

View file

@ -179,11 +179,29 @@ pub(crate) mod users {
Ok(())
}
pub async fn verify_password(
// Use clearer argument names
username: &str,
provided_password: &str,
) -> Result<bool, DbError> {
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<bool, DbError> {
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<bool, sqlx::Error> {
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::<i64, _>(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<String, sqlx::Error> {
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::<String, _>(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<bool, sqlx::Error> {
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::<i64, _>(0);
Ok(is_verified == 1)
}
}