turned on WAL on db, fix force update of passwords

This commit is contained in:
2025-10-11 03:18:12 +02:00
parent 008fc377ec
commit 11c3fa56d2
15 changed files with 253 additions and 30 deletions

14
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "cargo",
"command": "build",
"problemMatcher": [
"$rustc"
],
"group": "build",
"label": "rust: cargo build"
}
]
}

Binary file not shown.

Binary file not shown.

BIN
db/auth.sqlite-shm Normal file

Binary file not shown.

BIN
db/auth.sqlite-wal Normal file

Binary file not shown.

0
db/hotel_1.sqlite Normal file
View File

51
db/hotel_schema.sql Normal file
View File

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

0
hotel_1.sqlite Normal file
View File

0
hotel_schema.sql Normal file
View File

View File

@@ -66,13 +66,19 @@ pub async fn add_user_to_conv(
Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "prepare failed".to_string()) Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "prepare failed".to_string())
}; };
if !statement.exists(params![user_id, payload.conv_id]) match statement.exists(params![user_id, payload.conv_id]) {
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string())).unwrap() Ok(true) => {
{ //user is creator
// Early exit if not creator }
return ((StatusCode::FORBIDDEN, "Not the creator".to_string())); 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 { for target_id in &payload.users {
let rows_inserted = match conn.execute( 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()) Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "prepare failed".to_string())
}; };
if !statement.exists(params![user_id, payload.conv_id]) match statement.exists(params![user_id, payload.conv_id]) {
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string())) Ok(true) => {
.unwrap() // user is part of the conversation — continue
{
// Early exit if not creator
return ((StatusCode::FORBIDDEN, "Not part of the conversation".to_string()));
} }
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( let result = conn.execute(
"INSERT INTO message (sender_id, content, conversation_id) VALUES (?1, ?2, ?3)", "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)?, sent_at: row.get(3)?,
}) })
} }
).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))? )
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Query failed".to_string()))?
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Collect failed".to_string()))?; .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 ))
} }

View File

@@ -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<()> { async fn main() -> std::io::Result<()> {

View File

@@ -77,7 +77,6 @@ pub async fn clean_db_update(
match result { match result {
Ok(room_number) => { Ok(room_number) => {
// --- broadcast to all WS clients in the hotel --- // --- broadcast to all WS clients in the hotel ---
if let Err(err) = conn.execute( if let Err(err) = conn.execute(
"INSERT INTO room_history (room_id, room_number, status) VALUES (?1, ?2, ?3)", "INSERT INTO room_history (room_id, room_number, status) VALUES (?1, ?2, ?3)",
params![&room_id, &room_number, &payload.status], params![&room_id, &room_number, &payload.status],

View File

@@ -106,10 +106,15 @@ fn hash_password(password: &str) -> anyhow::Result<String> {
// Verify an incoming password against stored hash // Verify an incoming password against stored hash
fn verify_password(password: &str, stored_hash: &str) -> bool { 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() Argon2::default()
.verify_password(password.as_bytes(), &parsed_hash) .verify_password(password.as_bytes(), &parsed_hash).is_ok()
.is_ok()
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@@ -155,6 +160,133 @@ pub async fn register_user (
Ok((StatusCode::CREATED, "User registered successfully")) 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<AppState>,
Json(payload): Json<ForceUpdatePasswordValues>,
) -> 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<AppState>,
Json(payload): Json<UpdatePasswordValues>,
) -> 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)] #[derive(Deserialize, Debug)]
pub struct LoginValues { pub struct LoginValues {
username : String, username : String,
@@ -227,11 +359,14 @@ pub async fn clean_auth_loging(
return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response(); 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!({ let claims = serde_json::json!({
"id": user_id, "id": user_id,
@@ -251,6 +386,11 @@ pub async fn clean_auth_loging(
Json(LoginResponse { token }).into_response() Json(LoginResponse { token }).into_response()
} }
fn internal_error<E: std::fmt::Display>(err: E) -> (StatusCode, String) { fn internal_error<E: std::fmt::Display>(err: E) -> (StatusCode, String) {
(StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err)) (StatusCode::INTERNAL_SERVER_ERROR, format!("Internal error: {}", err))
} }

View File

@@ -15,6 +15,8 @@ pub fn utils_routes() -> Router<AppState> {
.route("/register", put(register_user)) .route("/register", put(register_user))
.route("/ws/", get(ws_handler)) .route("/ws/", get(ws_handler))
.route("/tokentest", put(token_tester)) .route("/tokentest", put(token_tester))
.route("/force_update_password", put(ForceUpdatePassword))
.route("/update_password", put(UpdatePassword))
//.with_state(state) //.with_state(state)
} }

1
utils command.txt Normal file
View File

@@ -0,0 +1 @@
cross build --release --target aarch64-unknown-linux-gnu