diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..88381fe --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,14 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "command": "build", + "problemMatcher": [ + "$rustc" + ], + "group": "build", + "label": "rust: cargo build" + } + ] +} \ No newline at end of file diff --git a/db/1.sqlite b/db/1.sqlite index e27249d..00c4e40 100644 Binary files a/db/1.sqlite and b/db/1.sqlite differ diff --git a/db/auth.sqlite b/db/auth.sqlite index 555f62a..e189df3 100644 Binary files a/db/auth.sqlite and b/db/auth.sqlite differ diff --git a/db/auth.sqlite-shm b/db/auth.sqlite-shm new file mode 100644 index 0000000..3a6ecd0 Binary files /dev/null and b/db/auth.sqlite-shm differ diff --git a/db/auth.sqlite-wal b/db/auth.sqlite-wal new file mode 100644 index 0000000..30285a4 Binary files /dev/null and b/db/auth.sqlite-wal differ diff --git a/db/hotel_1.sqlite b/db/hotel_1.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/db/hotel_schema.sql b/db/hotel_schema.sql new file mode 100644 index 0000000..be20ae9 --- /dev/null +++ b/db/hotel_schema.sql @@ -0,0 +1,51 @@ +CREATE TABLE rooms ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + number TEXT NOT NULL UNIQUE, + status TEXT NOT NULL DEFAULT 'clean' +); + +CREATE TABLE IF NOT EXISTS "conversation" ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, + `creator_id` INTEGER, + `title` TEXT, + `created_at` TEXT DEFAULT (CURRENT_TIMESTAMP) +); + +CREATE TABLE IF NOT EXISTS "message" ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `conversation_id` INTEGER REFERENCES `conversation`(`id`), + `sender_id` INTEGER NOT NULL, `content` TEXT NOT NULL, + `sent_at` TEXT DEFAULT (CURRENT_TIMESTAMP) +); + +CREATE TABLE "conversation_participants" ( + `conversation_id` INTEGER NOT NULL REFERENCES `conversation`(`id`), + `user_id` INTEGER NOT NULL, + `joined_at` TEXT DEFAULT (CURRENT_TIMESTAMP) + PRIMARY KEY (conversation_id, user_id) +); + +CREATE TABLE IF NOT EXISTS "inventory" ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `amount` INTEGER, + `item_name` TEXT, + `user_id` INT, + `updated_at` TEXT DEFAULT (CURRENT_TIMESTAMP) + ); + +CREATE TABLE IF NOT EXISTS "room_history" ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT, + `room_id` INTEGER NOT NULL REFERENCES `rooms`(`id`), + `room_number` TEXT NOT NULL, + `status` TEXT, + `updated_at` TEXT DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS "inventory_history" ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL, + `item_id` INTEGER NOT NULL REFERENCES `inventory`(`id`), + `amount` INTEGER NOT NULL, + `item_name` TEXT, + `user_id` INT, + `updated_at` TEXT DEFAULT (CURRENT_TIMESTAMP)); + diff --git a/hotel_1.sqlite b/hotel_1.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/hotel_schema.sql b/hotel_schema.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/chat/handlers.rs b/src/chat/handlers.rs index 7ae0b0e..30b0d0e 100644 --- a/src/chat/handlers.rs +++ b/src/chat/handlers.rs @@ -66,13 +66,19 @@ pub async fn add_user_to_conv( Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "prepare failed".to_string()) }; - if !statement.exists(params![user_id, payload.conv_id]) - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string())).unwrap() - { - // Early exit if not creator - return ((StatusCode::FORBIDDEN, "Not the creator".to_string())); - } + match statement.exists(params![user_id, payload.conv_id]) { + Ok(true) => { + //user is creator + } + Ok(false) => { + //user is not the creator + return (StatusCode::FORBIDDEN, "Not the creato of the conversation".to_string()) + } + Err(_) => { + return(StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()) + } + } for target_id in &payload.users { let rows_inserted = match conn.execute( @@ -114,13 +120,19 @@ pub async fn send_message( Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "prepare failed".to_string()) }; - if !statement.exists(params![user_id, payload.conv_id]) - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string())) - .unwrap() - { - // Early exit if not creator - return ((StatusCode::FORBIDDEN, "Not part of the conversation".to_string())); + match statement.exists(params![user_id, payload.conv_id]) { + Ok(true) => { + // user is part of the conversation — continue } + Ok(false) => { + // early exit: not part of the conversation + return (StatusCode::FORBIDDEN, "Not part of the conversation".to_string()); + } + Err(_) => { + // query failed + return (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()); + } +} let result = conn.execute( "INSERT INTO message (sender_id, content, conversation_id) VALUES (?1, ?2, ?3)", @@ -209,11 +221,15 @@ pub async fn get_message( sent_at: row.get(3)?, }) } - ).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))? - .collect::, _>>() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Collect failed".to_string()))?; + ) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))? + .collect::, _>>() + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Collect failed".to_string()))?; - Ok((StatusCode::OK, serde_json::to_string(&messages).unwrap())) + let response = serde_json::to_string(&messages) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Serialisation failed")); + + Ok((StatusCode::OK, response )) } diff --git a/src/main.rs b/src/main.rs index f6a0036..24e29d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,7 +23,7 @@ use crate::utils::auth::JwtKeys; -#[tokio::main] +#[tokio::main(flavor = "multi_thread", worker_threads = 8)] async fn main() -> std::io::Result<()> { diff --git a/src/rooms/handler.rs b/src/rooms/handler.rs index 5d73b68..ea01c5b 100644 --- a/src/rooms/handler.rs +++ b/src/rooms/handler.rs @@ -77,13 +77,12 @@ pub async fn clean_db_update( match result { Ok(room_number) => { // --- broadcast to all WS clients in the hotel --- - if let Err(err) = conn.execute( - "INSERT INTO room_history (room_id, room_number, status) VALUES (?1, ?2, ?3)", - params![&room_id, &room_number, &payload.status], - ) { - return (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to insert history: {err}")); - } + "INSERT INTO room_history (room_id, room_number, status) VALUES (?1, ?2, ?3)", + params![&room_id, &room_number, &payload.status], + ) { + return (StatusCode::INTERNAL_SERVER_ERROR, format!("Failed to insert history: {err}")); + } if let Some(hotel_users) = state.ws_map.get(&hotel_id) { let update_msg = json!({ "room_id": room_id, diff --git a/src/utils/auth.rs b/src/utils/auth.rs index d8af5ce..d5b7cae 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -106,10 +106,15 @@ fn hash_password(password: &str) -> anyhow::Result { // Verify an incoming password against stored hash fn verify_password(password: &str, stored_hash: &str) -> bool { - let parsed_hash = PasswordHash::new(stored_hash).unwrap(); + + let parsed_hash = match PasswordHash::new(&stored_hash) { + Ok(hash) => hash, + Err(_) => return false, + }; + Argon2::default() - .verify_password(password.as_bytes(), &parsed_hash) - .is_ok() + .verify_password(password.as_bytes(), &parsed_hash).is_ok() + } #[derive(Deserialize, Debug)] @@ -155,6 +160,133 @@ pub async fn register_user ( Ok((StatusCode::CREATED, "User registered successfully")) } +#[derive(Serialize, Deserialize, Debug)] +pub struct ForceUpdatePasswordValues{ + username: String, + newpassword: String, + hotel_id: i32, + admin_pass: String, +} + +//pub struct ForceUpdatePasswordPayload (pub ForceUpdatePasswordValues); + +pub async fn ForceUpdatePassword( + State(state): State, + Json(payload): Json, +) -> impl IntoResponse { + + let conn = match state.logs_pool.get() { + Ok(c) => c, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB, conn failed").into_response() + }; + + let user_row = match conn.query_row( + "SELECT id FROM users WHERE username = ?1 AND hotel_id = ?2", + params![&payload.username, &payload.hotel_id], + |row|{ + let user_id: i32 = row.get(0)?; + //let hotel_id: i32 = row.get(1)?; + Ok((user_id)) + }, + ).optional() { + Ok(opt) => opt, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error") + .into_response(), + }; + + let (user_id) = match user_row { + Some(u) => u, + None => return (StatusCode::UNAUTHORIZED, "Not correct user") + .into_response(), + }; + + let admin_check: String = "my_admin_password".to_string(); + + if &payload.admin_pass != &admin_check { + return (StatusCode::UNAUTHORIZED, "Invalid Amin Password").into_response() + }; + + + let hashed_password = match hash_password(&payload.newpassword) { + Ok(h) => h, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed").into_response(), + }; + + let result = conn.execute( + "UPDATE users SET password = ?1 WHERE id = ?2", + params![&hashed_password, &user_id], + ); + + match result { + Ok(rows) if rows > 0 => (StatusCode::OK, "Password updated").into_response(), + Ok(_) => (StatusCode::NOT_FOUND, "User not found").into_response(), + Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to update password").into_response(), + } + +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct UpdatePasswordValues{ + username: String, + current_password: String, + newpassword: String, + hotel_id: i32, + +} + +pub async fn UpdatePassword( + State(state): State, + Json(payload): Json, +) -> impl IntoResponse { + + let conn = match state.logs_pool.get() { + Ok(c) => c, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB, conn failed").into_response() + }; + + let user_row = match conn.query_row( + "SELECT password, id FROM users WHERE username = ?1 AND current_password = ?2", + params![&payload.username, &payload.current_password], + |row|{ + let password: String = row.get(0)?; + let id: i32 = row.get(1)?; + Ok((password, id)) + }, + ).optional() { + Ok(opt) => opt, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error") + .into_response(), + }; + + let (password, user_id) = match user_row { + Some(u) => u, + None => return (StatusCode::UNAUTHORIZED, "Not correct user") + .into_response(), + }; + + if verify_password( &payload.current_password, &password ) { + return (StatusCode::UNAUTHORIZED, "Invalid Password").into_response() + }; + + + let hashed_password = match hash_password(&payload.newpassword) { + Ok(h) => h, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Password hashing failed").into_response(), + }; + + let result = conn.execute( + "UPDATE users SET password = ?1 WHERE id = ?2", + params![&hashed_password, &user_id], + ); + + match result { + Ok(rows) if rows > 0 => (StatusCode::OK, "Password updated").into_response(), + Ok(_) => (StatusCode::NOT_FOUND, "User not found").into_response(), + Err(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Failed to update password").into_response(), + } + +} + #[derive(Deserialize, Debug)] pub struct LoginValues { username : String, @@ -227,11 +359,14 @@ pub async fn clean_auth_loging( return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response(); } - let expiration = chrono::Utc::now() - .checked_add_signed(chrono::Duration::hours(15)) - .unwrap() - .timestamp() as usize; + let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) { + Some(time) => time.timestamp() as usize, + None => { + // Handle overflow — probably a 500, since this should never happen + return (StatusCode::INTERNAL_SERVER_ERROR, "Time overflow".to_string()).into_response(); + } +}; let claims = serde_json::json!({ "id": user_id, @@ -251,6 +386,11 @@ pub async fn clean_auth_loging( Json(LoginResponse { token }).into_response() } + + + + + fn internal_error(err: E) -> (StatusCode, String) { (StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err)) } \ No newline at end of file diff --git a/src/utils/routes.rs b/src/utils/routes.rs index 8077a62..c451268 100644 --- a/src/utils/routes.rs +++ b/src/utils/routes.rs @@ -15,6 +15,8 @@ pub fn utils_routes() -> Router { .route("/register", put(register_user)) .route("/ws/", get(ws_handler)) .route("/tokentest", put(token_tester)) + .route("/force_update_password", put(ForceUpdatePassword)) + .route("/update_password", put(UpdatePassword)) //.with_state(state) } \ No newline at end of file diff --git a/utils command.txt b/utils command.txt new file mode 100644 index 0000000..223d11e --- /dev/null +++ b/utils command.txt @@ -0,0 +1 @@ +cross build --release --target aarch64-unknown-linux-gnu \ No newline at end of file