diff --git a/db/1.sqlite-shm b/db/1.sqlite-shm new file mode 100644 index 0000000..fe9ac28 Binary files /dev/null and b/db/1.sqlite-shm differ diff --git a/db/1.sqlite-wal b/db/1.sqlite-wal new file mode 100644 index 0000000..e69de29 diff --git a/db/2.sqlite b/db/2.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/db/auth.sqlite-shm b/db/auth.sqlite-shm index fe9ac28..c1b1e8d 100644 Binary files a/db/auth.sqlite-shm and b/db/auth.sqlite-shm differ diff --git a/db/auth.sqlite-wal b/db/auth.sqlite-wal index e69de29..52711c3 100644 Binary files a/db/auth.sqlite-wal and b/db/auth.sqlite-wal differ diff --git a/src/main.rs b/src/main.rs index e367f93..501c9e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,7 +60,7 @@ std::panic::set_hook(Box::new(|info| { .send(); })); -panic!("crash-test"); +//panic!("crash-test"); let hotel_pools = HotelPool::new(); let logs_manager = SqliteConnectionManager::file("db/auth.sqlite"); diff --git a/src/utils/auth.rs b/src/utils/auth.rs index 7e9dea3..b529381 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -329,6 +329,11 @@ struct LoginResponse { token: String, } +#[derive(Serialize)] +struct MultiLoginResponse { + tokens: Vec, +} + pub async fn clean_auth_loging( State(state): State, Extension(keys): Extension, @@ -428,30 +433,54 @@ pub async fn create_refresh_token( let conn = state.logs_pool.get() .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error".to_string()))?; - let user_row = conn.query_row( - "SELECT id, password, hotel_id FROM users WHERE username = ?1", - params![&payload.username], - |row| { + let mut stmt = conn.prepare( + "SELECT id, password, hotel_id FROM users WHERE username = ?1" + ).map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB prepare error".to_string()))?; + + + let user_rows = stmt + .query_map(params![&payload.username], |row| { let user_id: i32 = row.get(0)?; let password: String = row.get(1)?; let hotel_id: i32 = row.get(2)?; Ok((user_id, password, hotel_id)) - }, - ).optional() - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB get user id error".to_string()))?; + }) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB query error".to_string()))?; +/* let (user_id, stored_hash, hotel_id) = user_row .ok_or((StatusCode::NOT_FOUND, "User not found".to_string()))?; +*/ + //let mut tokens = Vec::new(); - if !verify_password(&payload.password, &stored_hash) { - return Err((StatusCode::UNAUTHORIZED, "Invalid credentials".to_string())); + for user_row_result in user_rows { + let (user_id, stored_hash, hotel_id) = user_row_result + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB row error".to_string()))?; + + if !verify_password(&payload.password, &stored_hash) { + continue; // Skip rows with invalid password + } + + /* + let mut bytes = [0u8; 64]; + OsRng.fill_bytes(&mut bytes); + let raw_token = Uuid::new_v4().to_string(); + + let hashed_token = argon2 + .hash_password(raw_token.as_bytes(), &salt) + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .to_string(); + */ + + conn.execute( + "INSERT INTO refresh_token (user_id, token_hash, device_id, user_agent, hotel_id) VALUES (?1, ?2, ?3, ?4, ?5)", + params![user_id, hashed_token, device_id_str, user_agent_str, hotel_id], + ) + .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB insert error".to_string()))?; + + //tokens.push(raw_token); } - conn.execute( - "INSERT INTO refresh_token (user_id, token_hash, device_id, user_agent, hotel_id) VALUES (?1, ?2, ?3, ?4, ?5)", - params![user_id, hashed_token, device_id_str, user_agent_str, hotel_id], - ) - .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "DB insert error".to_string()))?; let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Path=/", raw_token); @@ -482,37 +511,63 @@ pub async fn login_refresh_token ( Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(), }; - let user_agent_str = user_agent + + /*let user_agent_str = user_agent .map(|ua| ua.to_string()) .unwrap_or_else(|| "Unknown".to_string()); let device_id_str = payload.device_id.to_string(); + */ +let user_agent_str = match user_agent { + Some(ua) => ua.to_string(), + None => return (StatusCode::INTERNAL_SERVER_ERROR, "user agent unknown").into_response(), +}; + +let device_id_str = payload.device_id.to_string(); + + //"SELECT user_id, token_hash, hotel_id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2", - let device_row = match conn.query_row( - "SELECT user_id, token_hash, hotel_id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2", - params![&device_id_str, &user_agent_str], - |row| { - let user_id: i32 = row.get(0)?; - let token_hash: String = row.get(1)?; - let hotel_id: i32 = row.get(2)?; - //let displayname: String = row.get(3)?; - Ok((user_id, token_hash, hotel_id)) - }, - ).optional() { - Ok(opt) => opt, + let mut stmt = match conn.prepare( + "SELECT user_id, token_hash, hotel_id + FROM refresh_token + WHERE device_id = ?1 AND user_agent = ?2" + ) { + Ok(s) => s, Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(), }; - - let (user_id, token_hash, hotel_id) = match device_row { - Some(tuple) => tuple, - None => return (StatusCode::UNAUTHORIZED, "No matching device").into_response(), + + let rows = match stmt.query_map(params![&device_id_str, &user_agent_str], |row| { + Ok(( + row.get::<_, i32>(0)?, // user_id + row.get::<_, String>(1)?, // token_hash + row.get::<_, i32>(2)?, // hotel_id + )) + }) { + Ok(r) => r, + Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB query error").into_response(), }; + let mut entries = Vec::new(); + for r in rows { + match r { + Ok(t) => entries.push(t), + Err(_) => continue, // ignore corrupt rows + } + } + + if entries.is_empty() { + return (StatusCode::UNAUTHORIZED, "No matching device").into_response(); + } + + let mut tokens = Vec::new(); + +for (user_id, token_hash, hotel_id) in entries { if !verify_password(&payload.refresh_token, &token_hash) { - return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response(); -} + // skip rows with wrong hash + continue; + } let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) { Some(time) => time.timestamp() as usize, @@ -520,24 +575,29 @@ pub async fn login_refresh_token ( // 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, "hotel_id": hotel_id, - //"username": payload.username, "exp": expiration }); - let token = match encode( - &Header::default(), - &claims, - &keys.encoding - ) { + let token = match encode(&Header::default(), &claims, &keys.encoding) { Ok(t) => t, Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "JWT creation failed").into_response(), }; - Json(LoginResponse { token }).into_response() + + tokens.push(token); +} + + +if tokens.is_empty() { + return (StatusCode::UNAUTHORIZED, "Invalid or mismatched token").into_response(); +} + +//Json(tokens).into_response() + Json(MultiLoginResponse { tokens }).into_response() } pub async fn logout_from_single_device ( diff --git a/utils command.txt b/utils command.txt index 9f5e42f..6600c89 100644 --- a/utils command.txt +++ b/utils command.txt @@ -27,6 +27,17 @@ GOOD -p 8080:8080 ` rust-api:1.0.1 +linux distro + + docker run ` + --name hotel-api ` + -e JWT_SECRET=your_jwt_secret_key ` + -v "/var/lib/hotel-api/db:/app/db" ` + -p 8080:8080 ` + rust-api:1.0.2 + + + @@ -80,4 +91,9 @@ GOOD "RW": true, "Propagation": "rprivate" } - ], \ No newline at end of file + ], + + + + +