diff --git a/db/1.sqlite b/db/1.sqlite index 5366c39..3b25750 100644 Binary files a/db/1.sqlite and b/db/1.sqlite differ diff --git a/db/auth_copy_2.sqlite-shm b/db/auth_copy_2.sqlite-shm index 2b84086..15fdc1f 100644 Binary files a/db/auth_copy_2.sqlite-shm and b/db/auth_copy_2.sqlite-shm differ diff --git a/db/auth_copy_2.sqlite-wal b/db/auth_copy_2.sqlite-wal index 18c009b..5a966e3 100644 Binary files a/db/auth_copy_2.sqlite-wal and b/db/auth_copy_2.sqlite-wal differ diff --git a/src/main.rs b/src/main.rs index e9a4c70..5fa9674 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ use axum::extract::{ws::{Message, WebSocket, WebSocketUpgrade}, State}; use jsonwebtoken::{DecodingKey, EncodingKey}; use reqwest::header::AUTHORIZATION; use reqwest::header::CONTENT_TYPE; +use reqwest::header::USER_AGENT; use tokio::net::TcpListener; use tokio::sync::mpsc; @@ -100,7 +101,7 @@ async fn main() -> std::io::Result<()> { let cors = CorsLayer::very_permissive() .allow_credentials(true) .allow_methods([Method::GET, Method::POST, Method::PUT, Method::OPTIONS]) - .allow_headers([CONTENT_TYPE, AUTHORIZATION]); + .allow_headers([CONTENT_TYPE, AUTHORIZATION, USER_AGENT]); let app = create_router(state) .layer(Extension(jwt_keys)) .layer(cors); diff --git a/src/utils/auth.rs b/src/utils/auth.rs index a1473d4..34f62b4 100644 --- a/src/utils/auth.rs +++ b/src/utils/auth.rs @@ -406,7 +406,7 @@ pub async fn clean_auth_loging( }; if !verify_password(&payload.password, &stored_hash) { - return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response(); + return (StatusCode::UNAUTHORIZED, "Invalid credentials").into_response(); } @@ -472,10 +472,14 @@ pub async fn create_refresh_token( OsRng.fill_bytes(&mut bytes); let raw_token = Uuid::new_v4().to_string(); + + let hashed_token = &raw_token; + /* let hashed_token = argon2 .hash_password(raw_token.as_bytes(), &salt) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? .to_string(); + */ // let mut stmt = conn.prepare( // "SELECT id, password FROM users WHERE username = ?1" @@ -534,17 +538,19 @@ pub async fn create_refresh_token( /*.map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Error mapping hotel_ids".to_string())); */ //FIXME: might not need the hotel list on tconflict ? + + //TODO: remove user agent entirely from auth ,it is mutable and not stable + //TODO: make the token refresh on login conn.execute( r#" INSERT INTO refresh_token ( user_id, token_hash, device_id, - user_agent, hotel_id_list ) - VALUES (?1, ?2, ?3, ?4, ?5) - ON CONFLICT(user_id, device_id, user_agent) + VALUES (?1, ?2, ?3, ?4) + ON CONFLICT(user_id, device_id) DO UPDATE SET token_hash = excluded.token_hash, hotel_id_list = excluded.hotel_id_list @@ -553,7 +559,6 @@ pub async fn create_refresh_token( user_id, hashed_token, device_id_str, - user_agent_str, hotel_ids_json ], ).map_err(|e| { @@ -562,6 +567,10 @@ pub async fn create_refresh_token( //TODO: add a map/tupple of of the allowed hotels and their id+name, maybe update the token ? + println!("RAW write refresh_token bytes: {:?}", &raw_token.as_bytes()); + println!("RAW refresh_token : {}", &raw_token.to_string()); + println!("RAW write refresh_token len: {}", &raw_token.len()); + let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=60480000000;Path=/", raw_token); let mut response = (StatusCode::CREATED, "Refresh token created successfully").into_response(); @@ -602,6 +611,10 @@ pub async fn login_refresh_token ( None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(), }; + println!("RAW refresh_token bytes: {:?}", refresh_token.as_bytes()); + println!("RAW refresh_token : {}", refresh_token.to_string()); + println!("RAW refresh_token len: {}", refresh_token.len()); + println!("Cookies: {:?}", &refresh_token); let conn = match state.logs_pool.get() { @@ -624,22 +637,22 @@ pub async fn login_refresh_token ( //deserializing the list : //let hotel_ids: Vec = serde_json::from_str(&stored_value)?; let mut stmt = match conn.prepare( - "SELECT user_id, token_hash, hotel_id_list + "SELECT user_id, hotel_id_list FROM refresh_token - WHERE device_id = ?1 AND user_agent = ?2 + WHERE device_id = ?1 AND token_hash = ?2 LIMIT 1;" ) { Ok(s) => s, Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Error prepatring hotel_id_list stmt").into_response(), }; - let rows = match stmt.query_one(params![&device_id_str, &user_agent_str], |row| { + let rows = match stmt.query_one(params![&device_id_str, &refresh_token], |row| { Ok(( row.get::<_, i32>(0)?, // user_id row.get::<_, String>(1)?, // token_hash - row.get::<_, String>(2)?, // hotel_id //FIXME: this is supposed to be vectore maybe ? + //row.get::<_, String>(2)?, // hotel_id //FIXME: this is supposed to be vectore maybe ? )) - }) { + }).optional() { Ok(r) => r, Err(e) => { eprintln!("DB ERROR: {:?}", e); @@ -648,8 +661,16 @@ pub async fn login_refresh_token ( }; //TODO: extraction of the blob //let json_hotel_ids = rows.2; - let (user_id, saved_hash,json_hotel_ids) = rows; - + let (user_id, json_hotel_ids) = match rows { + Some(r) => r, + None => { + return ( + StatusCode::UNAUTHORIZED, + "No refresh token found for this device", + ) + .into_response(); + } +}; let hotel_ids: Vec = match serde_json::from_str(&json_hotel_ids) { Ok(ids) => ids, Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response(), @@ -661,11 +682,18 @@ pub async fn login_refresh_token ( return (StatusCode::UNAUTHORIZED, "No matching device").into_response(); } - if !verify_password(&refresh_token, &saved_hash) { +/* + + eprintln!("DB ERROR: {:?}", &refresh_token); + eprintln!("DB ERROR: {:?}", &token); + + + //still not auto adding hotel user link when creating account + if (&refresh_token != &token) { // skip rows with wrong hash return (StatusCode::UNAUTHORIZED, "Invelid credentials").into_response(); } - + */ let expiration = match chrono::Utc::now().checked_add_signed(chrono::Duration::hours(15)) { Some(time) => time.timestamp() as usize, None => { @@ -705,12 +733,24 @@ pub async fn logout_from_single_device ( State(state): State, Extension(keys): Extension, user_agent: Option>, + cookie_header: Option>, Json(payload): Json ) -> impl IntoResponse { - let user_agent_str = user_agent - .map(|ua| ua.to_string()) - .unwrap_or_else(|| "Unknown".to_string()); + let user_agent_str = user_agent + .map(|TypedHeader(ua)| ua.as_str().to_owned()) + .unwrap_or_else(|| "Unknown".to_string()); + + let cookies = match cookie_header { + Some(token) => token, + None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(), + }; + + let refresh_token = match cookies.get("refresh_token") { + Some(token) => token.to_string(), + None => return (StatusCode::UNAUTHORIZED, "Missing refresh token cookie").into_response(), + }; + let device_id_str = payload.device_id.to_string(); @@ -719,25 +759,25 @@ pub async fn logout_from_single_device ( Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, "DB connection error").into_response(), }; + let device_row = match conn.query_row( - "SELECT user_id, token_hash, hotel_id_list, id FROM refresh_token WHERE device_id = ?1 AND user_agent = ?2 AND revoked = 0 ", - params![&device_id_str, &user_agent_str], + "SELECT user_id, hotel_id_list, id + FROM refresh_token + WHERE token_hash = ?1 AND revoked = 0 ", + params![&refresh_token], |row| { let user_id: i32 = row.get(0)?; - let token_hash: String = row.get(1)?; - let json_hotel_id_list: String = row.get(2)?; - let id:i32 = row.get(3)?; + let json_hotel_id_list: String = row.get(1)?; + let id:i32 = row.get(2)?; //let displayname: String = row.get(3)?; - Ok((user_id, token_hash, json_hotel_id_list ,id)) + Ok((user_id,json_hotel_id_list ,id)) }, ).optional() { Ok(opt) => opt, Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, format!("DB query error : {}", e )).into_response(), }; - - - let (user_id, token_hash, json_hotel_id_list, token_id) = match device_row { + let (user_id, json_hotel_id_list, token_id) = match device_row { Some(tuple) => tuple, None => return (StatusCode::UNAUTHORIZED, "No matching device").into_response(), }; @@ -755,18 +795,23 @@ pub async fn logout_from_single_device ( } */ -/* + let revoked: Result = conn.query_row( - "UPDATE refresh_token SET revoked = 1 WHERE id = ?1 RETURNING device_id", + "DELETE FROM refresh_token + WHERE id = ?1 + RETURNING device_id", params![&token_id], |row| row.get(0), ); -*/ + let revoked_id = match (revoked) { + Ok(r) => r, + Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, "Hotel ids are not deserializable to Vec").into_response() + }; let cookie_value = format!("refresh_token={}; HttpOnly; Secure; Max-Age=0;Path=/", "loggedout"); - let mut response = (StatusCode::CREATED, format!("Token deleted for device id {}", &device_id_str)) + let mut response = (StatusCode::CREATED, format!("Token deleted for device id {}", &revoked_id)) .into_response(); response.headers_mut().insert(