From 9117c9429b218f9eeae601e09f2efb7f6dc00dad Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 09:24:18 -0400 Subject: [PATCH 1/8] Adding constant for endpoint for refreshing token --- src/callers/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/callers/mod.rs b/src/callers/mod.rs index a8b5883..7a05735 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -13,4 +13,6 @@ pub mod endpoints { pub const REGISTER_SERVICE_USER: &str = "/api/v1/service/register"; /// Endpoint constant for service login user pub const LOGIN_SERVICE_USER: &str = "/api/v1/service/login"; + /// Endpoint constant for refresh token + pub const REFRESH_TOKEN: &str = "/api/v1/token/refresh"; } -- 2.47.3 From 19427114fcdf36589b65e190ddc0918a4fd55c17 Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:16:47 -0400 Subject: [PATCH 2/8] Saving changes --- src/callers/login.rs | 280 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 + src/repo/mod.rs | 30 +++++ src/repo/service.rs | 36 ++++++ 4 files changed, 350 insertions(+) diff --git a/src/callers/login.rs b/src/callers/login.rs index 443c2ae..2b28ac9 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -32,6 +32,11 @@ pub mod request { } } } + + #[derive(Deserialize, utoipa::ToSchema)] + pub struct RefreshTokenRequest { + pub access_token: String, + } } pub mod response { @@ -57,6 +62,12 @@ pub mod response { let _parsed_body: LoginResponse = serde_json::from_slice(&body).unwrap(); todo!("Add code to convert axum::Response to this type"); } + + #[derive(Deserialize, utoipa::ToSchema)] + pub struct RefreshTokenResponse { + pub message: String, + pub data: Vec, + } } /// Endpoint for a user login @@ -337,3 +348,272 @@ pub async fn service_user_login( } } } + +/// Endpoint for service user login +#[utoipa::path( + post, + path = super::endpoints::REFRESH_TOKEN, + request_body( + content = request::RefreshTokenRequest, + description = "Data required refresh token", + content_type = "application/json" + ), + responses( + (status = 201, description = "Service uuser login successful", body = response::RefreshTokenResponse), + (status = 400, description = "Bad data", body = response::RefreshTokenResponse), + (status = 500, description = "Something went wrong", body = response::RefreshTokenResponse) + ) +)] +pub async fn refresh_token( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, +) -> ( + axum::http::StatusCode, + axum::Json, +) { + if payload.access_token.is_empty() { + let reason = String::from("Access token not provided"); + + ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(response::RefreshTokenResponse { + message: reason, + data: Vec::new(), + }), + ) + } else { + let key = textsender_models::envy::environment::get_secret_key() + .await + .value; + if token_stuff::verify_token(&key, &payload.access_token) { + match token_stuff::extract_id_from_token(&key, &payload.access_token) { + Ok(id) => { + let generate_service_token = + |id, key| -> (Option, Option) { + match token_stuff::create_service_refresh_token(key, id) { + Ok((token, issued)) => (Some(token), Some(issued)), + Err(_err) => (None, None), + } + }; + + let mut response = response::RefreshTokenResponse { + message: String::new(), + data: Vec::new(), + }; + + match repo::user::get_with_id(&pool, &id).await { + Ok(_user) => { + let (refresh_token, issued) = generate_service_token(&id, &key); + match refresh_token { + Some(token) => match issued { + Some(issued_at) => { + response.message = String::from( + super::messages::SUCCESSFUL_MESSAGE, + ); + let lr = textsender_models::token::LoginResult { + user_id: id, + access_token: token, + issued_at, + token_type: String::from( + textsender_models::token::TOKEN_TYPE, + ), + ..Default::default() + }; + response.data.push(lr); + (axum::http::StatusCode::OK, axum::Json(response)) + } + None => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::RefreshTokenResponse { + message: String::from("Issued at not returned"), + data: Vec::new(), + }), + ), + }, + None => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::RefreshTokenResponse { + message: String::from( + "Refresh token not generated", + ), + data: Vec::new(), + }), + ), + } + } + Err(err) => { + println!("Unable to find user, checking service user: {err:?}"); + match repo::service::get(&pool, &id).await { + Ok(_service_user) => { + let (refresh_token, issued) = + generate_service_token(&id, &key); + match refresh_token { + Some(token) => { + match issued { + Some(issued_at) => { + response.message = String::from(super::messages::SUCCESSFUL_MESSAGE); + let lr = textsender_models::token::LoginResult { + user_id: id, + access_token: token, + issued_at, + token_type: String::from( + textsender_models::token::TOKEN_TYPE, + ), + ..Default::default() + }; + response.data.push(lr); + (axum::http::StatusCode::OK, + axum::Json(response)) + } + None => { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { + message: String::from("Issued at not returned"), + data: Vec::new() + })) + } + } + } + None => { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { + message: String::from("Refresh token not generated"), + data: Vec::new() + })) + } + } + } + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::RefreshTokenResponse { + message: err.to_string(), + data: Vec::new(), + }), + ) + } + } + } + } + Err(err) => { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { + message: err.to_string(), + data: Vec::new() + })) + } + } + } else { + (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { + message: String::from("Unable to verify token"), + data: Vec::new() + })) + } + /* + match repo::service::exists(&pool, &payload.username).await { + Ok(exists) => { + if !exists { + println!("User does not exists"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::ServiceUserLoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + }), + ) + } else { + println!("Good to create"); + let service_user = + match repo::service::get_with_username(&pool, &payload.username).await { + Ok(service_user) => service_user, + Err(err) => { + eprintln!("Error: {err:?}"); + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::ServiceUserLoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + }), + ); + } + }; + println!("Service user: {service_user:?}"); + println!("Payload: {:?}", payload.passphrase); + let hashed_password = service_user.passphrase.clone(); + println!("Hash password: {hashed_password:?}"); + + match hashing::verify_password(&payload.passphrase, hashed_password) { + Ok(matches) => { + if matches { + // Create token + println!("Creating token"); + let key = textsender_models::envy::environment::get_secret_key() + .await + .value; + let (token_literal, duration) = + token_stuff::create_token(&key, &service_user.id).unwrap(); + + if token_stuff::verify_token(&key, &token_literal) { + let current_time = time::OffsetDateTime::now_utc(); + let _ = repo::service::update_last_login( + &pool, + &service_user, + ¤t_time, + ) + .await; + + ( + axum::http::StatusCode::OK, + axum::Json(response::ServiceUserLoginResponse { + message: String::from( + super::messages::SUCCESSFUL_MESSAGE, + ), + data: vec![textsender_models::token::LoginResult { + user_id: service_user.id, + access_token: token_literal, + token_type: String::from( + textsender_models::token::TOKEN_TYPE, + ), + issued_at: duration, + ..Default::default() + }], + }), + ) + } else { + eprintln!("Invalid token"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::ServiceUserLoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + }), + ) + } + } else { + eprintln!("No match"); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::ServiceUserLoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + }), + ) + } + } + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::ServiceUserLoginResponse { + message: err.to_string(), + data: Vec::new(), + }), + ), + } + } + } + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::ServiceUserLoginResponse { + message: err.to_string(), + data: Vec::new(), + }), + ), + } + */ + } +} diff --git a/src/lib.rs b/src/lib.rs index af64ea5..0a92e92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,6 +106,10 @@ pub mod init { callers::endpoints::LOGIN_SERVICE_USER, post(callers::login::service_user_login), ) + .route( + callers::endpoints::REFRESH_TOKEN, + post(callers::login::refresh_token), + ) .layer(cors::configure_cors().await) } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 1b51e54..f7aeaad 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -41,6 +41,36 @@ pub mod user { } } + pub async fn get_with_id( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + ) -> Result { + match sqlx::query( + r#" + SELECT id, username, password, phone_number, salt_id, firstname, lastname, created, last_login FROM "user" WHERE id = $1 + "#, + ) + .bind(id) + .fetch_optional(pool) + .await { + Ok(r) => match r { + Some(r) => Ok(textsender_models::user::User { + id: r.try_get("id")?, + username: r.try_get("username")?, + password: r.try_get("password")?, + phone_number: r.try_get("phone_number")?, + salt_id: r.try_get("salt_id")?, + firstname: r.try_get("firstname")?, + lastname: r.try_get("lastname")?, + created: r.try_get("created")?, + last_login: r.try_get("last_login")?, + }), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } + } + pub async fn update_last_login( pool: &sqlx::PgPool, user: &textsender_models::user::User, diff --git a/src/repo/service.rs b/src/repo/service.rs index 5617d98..078751e 100644 --- a/src/repo/service.rs +++ b/src/repo/service.rs @@ -49,6 +49,42 @@ pub async fn get_passphrase( } } +pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, +) -> Result { + match sqlx::query( + r#"SELECT id, username, passphrase, created, last_login, salt_id FROM "service_user" WHERE id = $1"# + ).bind(id) + .fetch_one(pool).await { + Ok(row) => { + let last_login: Option = match row.try_get("last_login") { + Ok(login) => { + Some(login) + } + Err(err) => { + eprintln!("Error: {err:?}"); + None + } + }; + + let service_user = textsender_models::user::ServiceUser { + id: row.try_get("id")?, + username: row.try_get("username")?, + passphrase: row.try_get("passphrase")?, + created: row.try_get("created")?, + last_login, + salt_id: row.try_get("salt_id")?, + }; + + Ok(service_user) + } + Err(err) => { + Err(err) + } + } +} + pub async fn get_with_username( pool: &sqlx::PgPool, username: &String, -- 2.47.3 From ffd87f7226ffac53e121fa34f9506f9323f814fc Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:23:32 -0400 Subject: [PATCH 3/8] Ready to try --- src/callers/login.rs | 220 +++++++++++-------------------------------- 1 file changed, 56 insertions(+), 164 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 2b28ac9..e39fc3d 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -63,7 +63,7 @@ pub mod response { todo!("Add code to convert axum::Response to this type"); } - #[derive(Deserialize, utoipa::ToSchema)] + #[derive(Deserialize, Serialize, utoipa::ToSchema)] pub struct RefreshTokenResponse { pub message: String, pub data: Vec, @@ -359,7 +359,7 @@ pub async fn service_user_login( content_type = "application/json" ), responses( - (status = 201, description = "Service uuser login successful", body = response::RefreshTokenResponse), + (status = 200, description = "Service uuser login successful", body = response::RefreshTokenResponse), (status = 400, description = "Bad data", body = response::RefreshTokenResponse), (status = 500, description = "Something went wrong", body = response::RefreshTokenResponse) ) @@ -388,13 +388,12 @@ pub async fn refresh_token( if token_stuff::verify_token(&key, &payload.access_token) { match token_stuff::extract_id_from_token(&key, &payload.access_token) { Ok(id) => { - let generate_service_token = - |id, key| -> (Option, Option) { - match token_stuff::create_service_refresh_token(key, id) { - Ok((token, issued)) => (Some(token), Some(issued)), - Err(_err) => (None, None), - } - }; + let generate_service_token = |id, key| -> (Option, Option) { + match token_stuff::create_service_refresh_token(key, id) { + Ok((token, issued)) => (Some(token), Some(issued)), + Err(_err) => (None, None), + } + }; let mut response = response::RefreshTokenResponse { message: String::new(), @@ -407,9 +406,8 @@ pub async fn refresh_token( match refresh_token { Some(token) => match issued { Some(issued_at) => { - response.message = String::from( - super::messages::SUCCESSFUL_MESSAGE, - ); + response.message = + String::from(super::messages::SUCCESSFUL_MESSAGE); let lr = textsender_models::token::LoginResult { user_id: id, access_token: token, @@ -433,9 +431,7 @@ pub async fn refresh_token( None => ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { - message: String::from( - "Refresh token not generated", - ), + message: String::from("Refresh token not generated"), data: Vec::new(), }), ), @@ -445,40 +441,42 @@ pub async fn refresh_token( println!("Unable to find user, checking service user: {err:?}"); match repo::service::get(&pool, &id).await { Ok(_service_user) => { - let (refresh_token, issued) = - generate_service_token(&id, &key); + let (refresh_token, issued) = generate_service_token(&id, &key); match refresh_token { - Some(token) => { - match issued { - Some(issued_at) => { - response.message = String::from(super::messages::SUCCESSFUL_MESSAGE); - let lr = textsender_models::token::LoginResult { - user_id: id, - access_token: token, - issued_at, - token_type: String::from( - textsender_models::token::TOKEN_TYPE, - ), - ..Default::default() - }; - response.data.push(lr); - (axum::http::StatusCode::OK, - axum::Json(response)) - } - None => { - (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { - message: String::from("Issued at not returned"), - data: Vec::new() - })) - } + Some(token) => match issued { + Some(issued_at) => { + response.message = String::from( + super::messages::SUCCESSFUL_MESSAGE, + ); + let lr = textsender_models::token::LoginResult { + user_id: id, + access_token: token, + issued_at, + token_type: String::from( + textsender_models::token::TOKEN_TYPE, + ), + ..Default::default() + }; + response.data.push(lr); + (axum::http::StatusCode::OK, axum::Json(response)) } - } - None => { - (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { - message: String::from("Refresh token not generated"), - data: Vec::new() - })) - } + None => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::RefreshTokenResponse { + message: String::from("Issued at not returned"), + data: Vec::new(), + }), + ), + }, + None => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::RefreshTokenResponse { + message: String::from( + "Refresh token not generated", + ), + data: Vec::new(), + }), + ), } } Err(err) => ( @@ -487,133 +485,27 @@ pub async fn refresh_token( message: err.to_string(), data: Vec::new(), }), - ) + ), } } } } - Err(err) => { - (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::RefreshTokenResponse { message: err.to_string(), - data: Vec::new() - })) - } + data: Vec::new(), + }), + ), } } else { - (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::RefreshTokenResponse { - message: String::from("Unable to verify token"), - data: Vec::new() - })) - } - /* - match repo::service::exists(&pool, &payload.username).await { - Ok(exists) => { - if !exists { - println!("User does not exists"); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response::ServiceUserLoginResponse { - message: String::from("Unable to login"), - data: Vec::new(), - }), - ) - } else { - println!("Good to create"); - let service_user = - match repo::service::get_with_username(&pool, &payload.username).await { - Ok(service_user) => service_user, - Err(err) => { - eprintln!("Error: {err:?}"); - return ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response::ServiceUserLoginResponse { - message: String::from("Unable to login"), - data: Vec::new(), - }), - ); - } - }; - println!("Service user: {service_user:?}"); - println!("Payload: {:?}", payload.passphrase); - let hashed_password = service_user.passphrase.clone(); - println!("Hash password: {hashed_password:?}"); - - match hashing::verify_password(&payload.passphrase, hashed_password) { - Ok(matches) => { - if matches { - // Create token - println!("Creating token"); - let key = textsender_models::envy::environment::get_secret_key() - .await - .value; - let (token_literal, duration) = - token_stuff::create_token(&key, &service_user.id).unwrap(); - - if token_stuff::verify_token(&key, &token_literal) { - let current_time = time::OffsetDateTime::now_utc(); - let _ = repo::service::update_last_login( - &pool, - &service_user, - ¤t_time, - ) - .await; - - ( - axum::http::StatusCode::OK, - axum::Json(response::ServiceUserLoginResponse { - message: String::from( - super::messages::SUCCESSFUL_MESSAGE, - ), - data: vec![textsender_models::token::LoginResult { - user_id: service_user.id, - access_token: token_literal, - token_type: String::from( - textsender_models::token::TOKEN_TYPE, - ), - issued_at: duration, - ..Default::default() - }], - }), - ) - } else { - eprintln!("Invalid token"); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response::ServiceUserLoginResponse { - message: String::from("Invalid attempt"), - data: Vec::new(), - }), - ) - } - } else { - eprintln!("No match"); - ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response::ServiceUserLoginResponse { - message: String::from("Invalid attempt"), - data: Vec::new(), - }), - ) - } - } - Err(err) => ( - axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response::ServiceUserLoginResponse { - message: err.to_string(), - data: Vec::new(), - }), - ), - } - } - } - Err(err) => ( + ( axum::http::StatusCode::INTERNAL_SERVER_ERROR, - axum::Json(response::ServiceUserLoginResponse { - message: err.to_string(), + axum::Json(response::RefreshTokenResponse { + message: String::from("Unable to verify token"), data: Vec::new(), }), - ), + ) } - */ } } -- 2.47.3 From e95d5ba468e8fa4e96c385b8a5c4a4f948226aff Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:29:14 -0400 Subject: [PATCH 4/8] Refactor test --- tests/tests.rs | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index bd2cba1..6cdc91e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -170,16 +170,20 @@ const TEST_SERVICE_USERNAME: &str = "swoon"; /// Test service passphrase const TEST_SERVICE_PASSPHRASE: &str = "4n5cf349tfy34w857ty39wq45nfdq23"; -async fn convert_response(response: axum::response::Response) -> Result -where - T: serde::de::DeserializeOwned, -{ - match axum::body::to_bytes(response.into_body(), usize::MAX).await { - Ok(body) => { - let resp: T = serde_json::from_slice(&body).unwrap(); - Ok(resp) +mod util { + pub async fn convert_response( + response: axum::response::Response, + ) -> Result + where + T: serde::de::DeserializeOwned, + { + match axum::body::to_bytes(response.into_body(), usize::MAX).await { + Ok(body) => { + let resp: T = serde_json::from_slice(&body).unwrap(); + Ok(resp) + } + Err(err) => Err(std::io::Error::other(err.to_string())), } - Err(err) => Err(std::io::Error::other(err.to_string())), } } @@ -194,7 +198,9 @@ async fn register_user( response.status() ))) } else { - match convert_response::(response).await { + match util::convert_response::(response) + .await + { Ok(response) => { if response.data.len() > 0 { let user = response.data[0].clone(); @@ -224,7 +230,9 @@ async fn login_user( response.status() ))) } else { - match convert_response::(response).await { + match util::convert_response::(response) + .await + { Ok(response) => { if response.data.len() > 0 { let user = response.data[0].clone(); @@ -252,9 +260,9 @@ async fn register_service_user( response.status() ))) } else { - match convert_response::( - response, - ) + match util::convert_response::< + callers::register::response::RegisterServiceUserResponse, + >(response) .await { Ok(response) => { @@ -286,7 +294,7 @@ async fn login_service_user( response.status() ))) } else { - match convert_response::( + match util::convert_response::( response, ) .await @@ -415,7 +423,7 @@ async fn test_register_service_user() { match requests::register_service_user(&app).await { Ok(response) => { - match convert_response::( + match util::convert_response::( response, ) .await -- 2.47.3 From 4459e1062aaa5f047f1cdf7730905bbd003203be Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:32:37 -0400 Subject: [PATCH 5/8] Saving tests --- tests/tests.rs | 228 ++++++++++++++++++++++++++----------------------- 1 file changed, 119 insertions(+), 109 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 6cdc91e..6b8b1dc 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -187,131 +187,139 @@ mod util { } } -async fn register_user( - app: &axum::Router, -) -> Result { - match requests::register_user(&app).await { - Ok(response) => { - if axum::http::StatusCode::CREATED != response.status() { - Err(std::io::Error::other(format!( - "Status code is off {:?}", - response.status() - ))) - } else { - match util::convert_response::(response) +mod flow { + use super::callers; + use super::requests; + use super::util; + + pub async fn register_user( + app: &axum::Router, + ) -> Result { + match requests::register_user(&app).await { + Ok(response) => { + if axum::http::StatusCode::CREATED != response.status() { + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) + } else { + match util::convert_response::(response) + .await + { + Ok(response) => { + if response.data.len() > 0 { + let user = response.data[0].clone(); + Ok(user) + } else { + Err(std::io::Error::other("No data returned")) + } + } + Err(err) => Err(err), + } + } + } + Err(err) => Err(std::io::Error::other(err.to_string())), + } + } + + pub async fn login_user( + app: &axum::Router, + username: &str, + password: &str, + ) -> Result { + match requests::login_user(&app, username, password).await { + Ok(response) => { + if axum::http::StatusCode::OK != response.status() { + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) + } else { + match util::convert_response::( + response, + ) .await - { - Ok(response) => { - if response.data.len() > 0 { - let user = response.data[0].clone(); - Ok(user) - } else { - Err(std::io::Error::other("No data returned")) + { + Ok(response) => { + if response.data.len() > 0 { + let user = response.data[0].clone(); + Ok(user) + } else { + Err(std::io::Error::other("No data returned")) + } } + Err(err) => Err(err), } - Err(err) => Err(err), } } + Err(err) => Err(std::io::Error::other(err.to_string())), } - Err(err) => Err(std::io::Error::other(err.to_string())), } -} -async fn login_user( - app: &axum::Router, - username: &str, - password: &str, -) -> Result { - match requests::login_user(&app, username, password).await { - Ok(response) => { - if axum::http::StatusCode::OK != response.status() { - Err(std::io::Error::other(format!( - "Status code is off {:?}", - response.status() - ))) - } else { - match util::convert_response::(response) + pub async fn register_service_user( + app: &axum::Router, + ) -> Result { + match requests::register_service_user(&app).await { + Ok(response) => { + if axum::http::StatusCode::CREATED != response.status() { + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) + } else { + match util::convert_response::< + callers::register::response::RegisterServiceUserResponse, + >(response) .await - { - Ok(response) => { - if response.data.len() > 0 { - let user = response.data[0].clone(); - Ok(user) - } else { - Err(std::io::Error::other("No data returned")) + { + Ok(response) => { + if response.data.len() > 0 { + let service_user = response.data[0].clone(); + Ok(service_user) + } else { + Err(std::io::Error::other("No data returned")) + } } + Err(err) => Err(err), } - Err(err) => Err(err), } } + Err(err) => Err(std::io::Error::other(err.to_string())), } - Err(err) => Err(std::io::Error::other(err.to_string())), } -} -async fn register_service_user( - app: &axum::Router, -) -> Result { - match requests::register_service_user(&app).await { - Ok(response) => { - if axum::http::StatusCode::CREATED != response.status() { - Err(std::io::Error::other(format!( - "Status code is off {:?}", - response.status() - ))) - } else { - match util::convert_response::< - callers::register::response::RegisterServiceUserResponse, - >(response) - .await - { - Ok(response) => { - if response.data.len() > 0 { - let service_user = response.data[0].clone(); - Ok(service_user) - } else { - Err(std::io::Error::other("No data returned")) + pub async fn login_service_user( + app: &axum::Router, + username: &str, + passphrase: &str, + ) -> Result { + match requests::login_service_user(&app, username, passphrase).await { + Ok(response) => { + if axum::http::StatusCode::OK != response.status() { + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) + } else { + match util::convert_response::( + response, + ) + .await + { + Ok(response) => { + if response.data.len() > 0 { + let login_result = response.data[0].clone(); + Ok(login_result) + } else { + Err(std::io::Error::other("No data returned")) + } } + Err(err) => Err(err), } - Err(err) => Err(err), } } + Err(err) => Err(std::io::Error::other(err.to_string())), } - Err(err) => Err(std::io::Error::other(err.to_string())), - } -} - -async fn login_service_user( - app: &axum::Router, - username: &str, - passphrase: &str, -) -> Result { - match requests::login_service_user(&app, username, passphrase).await { - Ok(response) => { - if axum::http::StatusCode::OK != response.status() { - Err(std::io::Error::other(format!( - "Status code is off {:?}", - response.status() - ))) - } else { - match util::convert_response::( - response, - ) - .await - { - Ok(response) => { - if response.data.len() > 0 { - let login_result = response.data[0].clone(); - Ok(login_result) - } else { - Err(std::io::Error::other("No data returned")) - } - } - Err(err) => Err(err), - } - } - } - Err(err) => Err(std::io::Error::other(err.to_string())), } } @@ -335,7 +343,7 @@ async fn test_register_user() { let app = init::routes().await.layer(axum::Extension(pool)); - match register_user(&app).await { + match flow::register_user(&app).await { Ok(returned_user) => { assert_eq!( TEST_USERNAME, returned_user.username, @@ -375,8 +383,8 @@ async fn test_login_user() { let app = init::routes().await.layer(axum::Extension(pool)); - match register_user(&app).await { - Ok(user) => match login_user(&app, &user.username, TEST_PASSWORD).await { + match flow::register_user(&app).await { + Ok(user) => match flow::login_user(&app, &user.username, TEST_PASSWORD).await { Ok(login_result) => { assert_eq!( false, @@ -474,14 +482,16 @@ async fn test_login_service_user() { let app = init::routes().await.layer(axum::Extension(pool)); - match register_service_user(&app).await { + match flow::register_service_user(&app).await { Ok(user) => { assert_eq!( false, user.id.is_nil(), "The service user id should not be nil" ); - match login_service_user(&app, TEST_SERVICE_USERNAME, TEST_SERVICE_PASSPHRASE).await { + match flow::login_service_user(&app, TEST_SERVICE_USERNAME, TEST_SERVICE_PASSPHRASE) + .await + { Ok(login_result) => { assert_eq!( false, -- 2.47.3 From 458612fb28055919de1add0f2d902f18cbeb2b9a Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:41:53 -0400 Subject: [PATCH 6/8] Added test --- tests/tests.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 6b8b1dc..ee7005e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -134,6 +134,7 @@ pub mod requests { app.clone().oneshot(req).await } + /// Function to call service user login endpoint pub async fn login_service_user( app: &axum::Router, username: &str, @@ -152,6 +153,24 @@ pub mod requests { app.clone().oneshot(req).await } + + /// Function to call token refresh endpoint + pub async fn refresh_token( + app: &axum::Router, + access_token: &str, + ) -> Result { + let payload = serde_json::json!({ + "access_token": access_token, + }); + let req = axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(super::callers::endpoints::REFRESH_TOKEN) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(axum::body::Body::from(payload.to_string())) + .unwrap(); + + app.clone().oneshot(req).await + } } /// Test user firstname @@ -321,6 +340,39 @@ mod flow { Err(err) => Err(std::io::Error::other(err.to_string())), } } + + pub async fn refresh_token( + app: &axum::Router, + access_token: &str, + ) -> Result { + match requests::refresh_token(app, access_token).await { + Ok(response) => { + if axum::http::StatusCode::OK != response.status() { + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) + } else { + match util::convert_response::( + response, + ) + .await + { + Ok(response) => { + if response.data.len() > 0 { + let login_result = response.data[0].clone(); + Ok(login_result) + } else { + Err(std::io::Error::other("No data returned")) + } + } + Err(err) => Err(err), + } + } + } + Err(err) => Err(std::io::Error::other(err.to_string())), + } + } } #[tokio::test] @@ -516,3 +568,61 @@ async fn test_login_service_user() { } } } + +#[tokio::test] +async fn test_refresh_token() { + let tm_pool = db_mgr::get_pool().await.unwrap(); + let db_name = db_mgr::generate_db_name().await; + + match db_mgr::create_database(&tm_pool, &db_name).await { + Ok(_) => { + println!("Success"); + } + Err(e) => { + assert!(false, "Error: {:?}", e.to_string()); + } + } + + let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); + + db::init::migrations(&pool).await; + + let app = init::routes().await.layer(axum::Extension(pool)); + + match flow::register_user(&app).await { + Ok(user) => match flow::login_user(&app, &user.username, TEST_PASSWORD).await { + Ok(login_result) => { + assert_eq!( + false, + login_result.access_token.is_empty(), + "Access token is empty when it should not be" + ); + match flow::refresh_token(&app, &login_result.access_token).await { + Ok(refresh_login_result) => { + assert_eq!( + false, + refresh_login_result.access_token.is_empty(), + "Refreshed access token should not be empty" + ); + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + }, + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + + match db_mgr::drop_database(&tm_pool, &db_name).await { + Ok(()) => {} + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } +} -- 2.47.3 From 36c40966428f1d799025e6fc2231f110608c0ec1 Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:46:29 -0400 Subject: [PATCH 7/8] Adding portion to test service user refresh token --- tests/tests.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index ee7005e..e861f47 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -198,7 +198,12 @@ mod util { { match axum::body::to_bytes(response.into_body(), usize::MAX).await { Ok(body) => { - let resp: T = serde_json::from_slice(&body).unwrap(); + let resp: T = match serde_json::from_slice(&body) { + Ok(val) => val, + Err(err) => { + return Err(std::io::Error::other(err.to_string())); + } + }; Ok(resp) } Err(err) => Err(std::io::Error::other(err.to_string())), @@ -619,6 +624,46 @@ async fn test_refresh_token() { } } + match flow::register_service_user(&app).await { + Ok(user) => { + assert_eq!( + false, + user.id.is_nil(), + "The service user id should not be nil" + ); + match flow::login_service_user(&app, TEST_SERVICE_USERNAME, TEST_SERVICE_PASSPHRASE) + .await + { + Ok(login_result) => { + assert_eq!( + false, + login_result.access_token.is_empty(), + "Access token is empty when it should not be" + ); + + match flow::refresh_token(&app, &login_result.access_token).await { + Ok(refresh_login_result) => { + assert_eq!( + false, + refresh_login_result.access_token.is_empty(), + "Refreshed access token should not be empty" + ); + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + match db_mgr::drop_database(&tm_pool, &db_name).await { Ok(()) => {} Err(err) => { -- 2.47.3 From eeb7cba11fe69a603ce9d23ec2a8d88dba1841ba Mon Sep 17 00:00:00 2001 From: phoenix Date: Fri, 12 Jun 2026 10:47:15 -0400 Subject: [PATCH 8/8] bump: textsender_auth --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bdcb6ea..cbdfbb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2213,7 +2213,7 @@ dependencies = [ [[package]] name = "textsender_auth" -version = "0.1.17" +version = "0.1.18" dependencies = [ "argon2", "async-std", diff --git a/Cargo.toml b/Cargo.toml index d942b84..8ac862d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "textsender_auth" -version = "0.1.17" +version = "0.1.18" edition = "2024" rust-version = "1.95" -- 2.47.3