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:
parent
91b6942348
commit
b8f2a91411
6 changed files with 436 additions and 10 deletions
BIN
db.sqlite
BIN
db.sqlite
Binary file not shown.
7
migrations/004_create_kick_table.sql
Normal file
7
migrations/004_create_kick_table.sql
Normal 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);
|
8
migrations/005_create_files_table.sql
Normal file
8
migrations/005_create_files_table.sql
Normal 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);
|
2
migrations/006_add_admin_verified_to_files.sql
Normal file
2
migrations/006_add_admin_verified_to_files.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE files
|
||||||
|
ADD COLUMN admin_verified BOOLEAN DEFAULT FALSE;
|
|
@ -5,8 +5,9 @@ pub(crate) mod handlers {
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::db::users::{
|
use crate::db::users::{
|
||||||
ban_user, check_ban, check_for_account, create_user, get_ban_reason, hash_password,
|
add_kick, add_new_file, add_verified_flag_to_file, ban_user, change_password, check_ban,
|
||||||
unban_user, verify_admin, verify_password,
|
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 base64::{engine::general_purpose::STANDARD as BASE64, Engine as _};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
|
@ -147,7 +148,7 @@ pub(crate) mod handlers {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// verifiy password
|
// verifiy password
|
||||||
let password = String::from_utf8(decrypted)?;
|
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");
|
info!("Password verified successfully");
|
||||||
} else {
|
} else {
|
||||||
info!("Password verification failed");
|
info!("Password verification failed");
|
||||||
|
@ -245,6 +246,22 @@ pub(crate) mod handlers {
|
||||||
info!("Parsing message");
|
info!("Parsing message");
|
||||||
|
|
||||||
let parsed_message = parse_message(message.as_str());
|
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
|
// Handle commands
|
||||||
if !parsed_message.command.is_empty() {
|
if !parsed_message.command.is_empty() {
|
||||||
match parsed_message.command[0].as_str() {
|
match parsed_message.command[0].as_str() {
|
||||||
|
@ -284,6 +301,189 @@ pub(crate) mod handlers {
|
||||||
break;
|
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" => {
|
"/ban" => {
|
||||||
if parsed_message.argument.is_empty() {
|
if parsed_message.argument.is_empty() {
|
||||||
error!("Invalid /ban format. Usage: /ban username");
|
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]);
|
error!("Unknown command: {}", parsed_message.command[0]);
|
||||||
match tx.send("Error! Unknown command".to_string()) {
|
match tx.send("Error! Unknown command".to_string()) {
|
||||||
|
|
151
src/db/mod.rs
151
src/db/mod.rs
|
@ -179,11 +179,29 @@ pub(crate) mod users {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn verify_password(
|
pub async fn change_password(username: &str, new_password: &str) -> Result<(), sqlx::Error> {
|
||||||
// Use clearer argument names
|
let pool = create_db_pool().await?;
|
||||||
username: &str,
|
|
||||||
provided_password: &str,
|
// Hash the new password
|
||||||
) -> Result<bool, DbError> {
|
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
|
let pool = create_db_pool().await?; // Propagates sqlx::Error
|
||||||
|
|
||||||
// Fetch the stored hash for the user
|
// Fetch the stored hash for the user
|
||||||
|
@ -205,8 +223,7 @@ pub(crate) mod users {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse the stored hash
|
// 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 argon2 = Argon2::default();
|
||||||
|
|
||||||
let verification_result =
|
let verification_result =
|
||||||
|
@ -252,4 +269,124 @@ pub(crate) mod users {
|
||||||
|
|
||||||
Ok(is_admin == 1)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue