From 8c9a33cb81e55f656e9e0358a2ed01ce481b7cd9 Mon Sep 17 00:00:00 2001 From: Romain Mallard Date: Mon, 24 Nov 2025 17:02:12 +0100 Subject: [PATCH] register refresh token and login refresh token for multiple users --- db/1.sqlite-shm | Bin 0 -> 32768 bytes db/1.sqlite-wal | 0 db/2.sqlite | 0 db/auth.sqlite-shm | Bin 32768 -> 32768 bytes db/auth.sqlite-wal | Bin 0 -> 300792 bytes src/main.rs | 2 +- src/utils/auth.rs | 142 ++++++++++++++++++++++++++++++++------------- utils command.txt | 18 +++++- 8 files changed, 119 insertions(+), 43 deletions(-) create mode 100644 db/1.sqlite-shm create mode 100644 db/1.sqlite-wal create mode 100644 db/2.sqlite diff --git a/db/1.sqlite-shm b/db/1.sqlite-shm new file mode 100644 index 0000000000000000000000000000000000000000..fe9ac2845eca6fe6da8a63cd096d9cf9e24ece10 GIT binary patch literal 32768 zcmeIuAr62r372N3Foonr=lqnY$ zDcK}>|EiCKRMOS`9H8^hMLDP65|J|Jc1>I2*AKbK{q4=;RV7h=y?n~1E=q;Fm$$sf z_pQ|Daq8|^-oMY+)1TiayG>bYy(V1my6>Iuf5%nPDU}?0(EOCodBWY>2v{)R2 z>N79@tNpuss^z}=-CtL0iGu(F2q1s}0tg_000IagfB*srAb|Eq DRL&St diff --git a/db/auth.sqlite-wal b/db/auth.sqlite-wal index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..52711c3ad16bdc93acae89828847f80c5146acfd 100644 GIT binary patch literal 300792 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, +} + 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 + ], + + + + +