From 8f13f1b6ecd68b577a88892679f58b346dab2562 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 20:44:58 -0400 Subject: [PATCH 01/21] Adding login module to callers --- src/callers/login.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/callers/login.rs diff --git a/src/callers/login.rs b/src/callers/login.rs new file mode 100644 index 0000000..e69de29 -- 2.47.3 From 4b64fb1778ad354a141f0ff5fbd7e58c8f5cb09d Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 21:32:35 -0400 Subject: [PATCH 02/21] Saving code --- src/callers/login.rs | 128 +++++++++++++++++++++++++++++++++++++++++++ src/callers/mod.rs | 3 + src/main.rs | 10 +++- 3 files changed, 139 insertions(+), 2 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index e69de29..7c41ef7 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -0,0 +1,128 @@ +use crate::repo; +use crate::hashing; +use crate::token_stuff; + +pub mod request { + use serde::{Deserialize}; + #[derive(Default, Deserialize, utoipa::ToSchema)] + pub struct LoginRequest { + pub username: String, + pub password: String, + } +} + +pub mod response { + use serde::{Deserialize}; + #[derive(Default, Deserialize, utoipa::ToSchema)] + pub struct LoginResponse { + pub message: String, + pub data: Vec, + } +} + +/// Endpoint for a user login +#[utoipa::path( + post, + path = super::endpoints::LOGIN, + request_body( + content = request::LoginRequest, + description = "Data required for a user to lgoin", + content_type = "application/json" + ), + responses( + (status = 201, description = "User login successful", body = response::LoginResponse), + (status = 400, description = "Bad data", body = response::LoginResponse), + (status = 500, description = "Something went wrong", body = response::LoginResponse) + ) +)] +pub async fn login(axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json) -> (axum::http::StatusCode, axum::Json) { + if payload.username.is_empty() || payload.password.is_empty() { + let reason = if payload.username.is_empty() { + String::from("Username not provided") + } else { + String::from("Password not provided") + }; + + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response::LoginResponse { + message: reason, + data: Vec::new(), + })) + } else { + match repo::user::exists(&pool, &payload.username).await { + Ok(exists) => { + if !exists { + println!("User does not exists"); + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + })); + } else { + let user = match repo::user::get(&pool, &payload.username).await { + Ok(user) => { + user + } + Err(_err) => { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + })); + } + }; + let hashed_password = user.password.clone(); + match hashing::verify_password(&payload.password, hashed_password) { + Ok(matches) => { + if matches { + // Create token + let key = textsender_models::envy::environment::get_secret_key().await.value; + let (token_literal, duration) = + token_stuff::create_token(&key, &user.id).unwrap(); + + if token_stuff::verify_token(&key, &token_literal) { + let current_time = time::OffsetDateTime::now_utc(); + let _ = repo::user::update_last_login(&pool, &user, ¤t_time).await; + + ( + axum::http::StatusCode::OK, + axum::Json(response::LoginResponse { + message: String::from("Successful"), + data: vec![textsender_models::token::LoginResult { + user_id: user.id, + access_token: token_literal, + token_type: String::from(textsender_models::token::TOKEN_TYPE), + expires_in: duration, + ..Default::default() + }], + }), + ) + } else { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + })); + } + } else { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + })); + } + } + Err(err) => { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: err.to_string(), + data: Vec::new(), + })); + } + } + } + } + Err(err) => { + return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + message: err.to_string(), + data: Vec::new(), + })); + } + } + } +} diff --git a/src/callers/mod.rs b/src/callers/mod.rs index ab14499..c561eea 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -1,8 +1,11 @@ pub mod common; +pub mod login; pub mod register; pub mod endpoints { pub const ROOT: &str = "/"; pub const REGISTER: &str = "/api/v1/register"; + /// Endpoint for a user to login + pub const LOGIN: &str = "/api/v1/login"; pub const DBTEST: &str = "/api/v1/test/db"; } diff --git a/src/main.rs b/src/main.rs index ca94b2a..ee04c8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,17 +27,19 @@ mod init { use super::callers; use callers::common as common_callers; + use callers::login as login_caller; use callers::register as register_caller; use register_caller::response as register_responses; + use login_caller::response as login_responses; #[derive(utoipa::OpenApi)] #[openapi( paths( common_callers::endpoint::db_ping, common_callers::endpoint::root, - register_caller::register_user, + register_caller::register_user, login_caller::login, ), components(schemas(common_callers::response::TestResult, - register_responses::Response)), + register_responses::Response, login_responses::LoginResponse,)), tags( (name = "TextSender Auth API", description = "Auth API for TextSender API") ) @@ -108,6 +110,10 @@ mod init { callers::endpoints::REGISTER, post(callers::register::register_user), ) + .route( + callers::endpoints::LOGIN, + post(callers::login::login), + ) .layer(cors::configure_cors().await) } -- 2.47.3 From dc5dfcec079dd667aef2741e47f8f67eef2e19bb Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 21:40:03 -0400 Subject: [PATCH 03/21] Code cleanup --- src/callers/login.rs | 98 +++++++++++++++++++++++++++----------------- src/main.rs | 9 ++-- 2 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 7c41ef7..0d0f6db 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -1,9 +1,9 @@ -use crate::repo; use crate::hashing; +use crate::repo; use crate::token_stuff; pub mod request { - use serde::{Deserialize}; + use serde::Deserialize; #[derive(Default, Deserialize, utoipa::ToSchema)] pub struct LoginRequest { pub username: String, @@ -12,7 +12,7 @@ pub mod request { } pub mod response { - use serde::{Deserialize}; + use serde::Deserialize; #[derive(Default, Deserialize, utoipa::ToSchema)] pub struct LoginResponse { pub message: String, @@ -35,8 +35,10 @@ pub mod response { (status = 500, description = "Something went wrong", body = response::LoginResponse) ) )] -pub async fn login(axum::Extension(pool): axum::Extension, - axum::Json(payload): axum::Json) -> (axum::http::StatusCode, axum::Json) { +pub async fn user_login( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, +) -> (axum::http::StatusCode, axum::Json) { if payload.username.is_empty() || payload.password.is_empty() { let reason = if payload.username.is_empty() { String::from("Username not provided") @@ -44,43 +46,55 @@ pub async fn login(axum::Extension(pool): axum::Extension, String::from("Password not provided") }; - (axum::http::StatusCode::BAD_REQUEST, axum::Json(response::LoginResponse { - message: reason, - data: Vec::new(), - })) + ( + axum::http::StatusCode::BAD_REQUEST, + axum::Json(response::LoginResponse { + message: reason, + data: Vec::new(), + }), + ) } else { match repo::user::exists(&pool, &payload.username).await { Ok(exists) => { if !exists { println!("User does not exists"); - return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { - message: String::from("Unable to login"), - data: Vec::new(), - })); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + }), + ) } else { let user = match repo::user::get(&pool, &payload.username).await { - Ok(user) => { - user - } + Ok(user) => user, Err(_err) => { - return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { - message: String::from("Unable to login"), - data: Vec::new(), - })); + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Unable to login"), + data: Vec::new(), + }), + ); } }; let hashed_password = user.password.clone(); + match hashing::verify_password(&payload.password, hashed_password) { Ok(matches) => { if matches { // Create token - let key = textsender_models::envy::environment::get_secret_key().await.value; + let key = textsender_models::envy::environment::get_secret_key() + .await + .value; let (token_literal, duration) = token_stuff::create_token(&key, &user.id).unwrap(); if token_stuff::verify_token(&key, &token_literal) { let current_time = time::OffsetDateTime::now_utc(); - let _ = repo::user::update_last_login(&pool, &user, ¤t_time).await; + let _ = + repo::user::update_last_login(&pool, &user, ¤t_time) + .await; ( axum::http::StatusCode::OK, @@ -89,40 +103,50 @@ pub async fn login(axum::Extension(pool): axum::Extension, data: vec![textsender_models::token::LoginResult { user_id: user.id, access_token: token_literal, - token_type: String::from(textsender_models::token::TOKEN_TYPE), + token_type: String::from( + textsender_models::token::TOKEN_TYPE, + ), expires_in: duration, ..Default::default() }], }), ) } else { - return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { - message: String::from("Invalid attempt"), - data: Vec::new(), - })); + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { + message: String::from("Invalid attempt"), + data: Vec::new(), + }), + ) } } else { - return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { message: String::from("Invalid attempt"), data: Vec::new(), - })); + }), + ) } } - Err(err) => { - return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { message: err.to_string(), data: Vec::new(), - })); - } + }), + ), } } } - Err(err) => { - return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::Json(response::LoginResponse { + Err(err) => ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + axum::Json(response::LoginResponse { message: err.to_string(), data: Vec::new(), - })); - } + }), + ), } } } diff --git a/src/main.rs b/src/main.rs index ee04c8b..aac0b44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,8 +29,8 @@ mod init { use callers::common as common_callers; use callers::login as login_caller; use callers::register as register_caller; - use register_caller::response as register_responses; use login_caller::response as login_responses; + use register_caller::response as register_responses; #[derive(utoipa::OpenApi)] #[openapi( @@ -39,7 +39,7 @@ mod init { register_caller::register_user, login_caller::login, ), components(schemas(common_callers::response::TestResult, - register_responses::Response, login_responses::LoginResponse,)), + register_responses::Response, login_responses::LoginResponse)), tags( (name = "TextSender Auth API", description = "Auth API for TextSender API") ) @@ -110,10 +110,7 @@ mod init { callers::endpoints::REGISTER, post(callers::register::register_user), ) - .route( - callers::endpoints::LOGIN, - post(callers::login::login), - ) + .route(callers::endpoints::LOGIN, post(callers::login::login)) .layer(cors::configure_cors().await) } -- 2.47.3 From ba4830ad189b3090fc53cc5d6f40f8f1c33f1fd5 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 21:43:34 -0400 Subject: [PATCH 04/21] Saving changes --- src/main.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index aac0b44..e20576f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ mod init { #[openapi( paths( common_callers::endpoint::db_ping, common_callers::endpoint::root, - register_caller::register_user, login_caller::login, + register_caller::register_user, login_caller::user_login, ), components(schemas(common_callers::response::TestResult, register_responses::Response, login_responses::LoginResponse)), @@ -110,7 +110,9 @@ mod init { callers::endpoints::REGISTER, post(callers::register::register_user), ) - .route(callers::endpoints::LOGIN, post(callers::login::login)) + .route(callers::endpoints::LOGIN, + post(callers::login::user_login), + ) .layer(cors::configure_cors().await) } -- 2.47.3 From 45c22db9a2d8381fe19869edc1c5a1600fb9de39 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:03:32 -0400 Subject: [PATCH 05/21] Cleaning up --- src/callers/login.rs | 6 +++--- src/main.rs | 4 +--- src/token_stuff/mod.rs | 4 ---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 0d0f6db..af0493c 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -12,8 +12,8 @@ pub mod request { } pub mod response { - use serde::Deserialize; - #[derive(Default, Deserialize, utoipa::ToSchema)] + use serde::{Deserialize, Serialize}; + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] pub struct LoginResponse { pub message: String, pub data: Vec, @@ -106,7 +106,7 @@ pub async fn user_login( token_type: String::from( textsender_models::token::TOKEN_TYPE, ), - expires_in: duration, + issued_at: duration, ..Default::default() }], }), diff --git a/src/main.rs b/src/main.rs index e20576f..a204c32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -110,9 +110,7 @@ mod init { callers::endpoints::REGISTER, post(callers::register::register_user), ) - .route(callers::endpoints::LOGIN, - post(callers::login::user_login), - ) + .route(callers::endpoints::LOGIN, post(callers::login::user_login)) .layer(cors::configure_cors().await) } diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs index 1ca1704..7eb3511 100644 --- a/src/token_stuff/mod.rs +++ b/src/token_stuff/mod.rs @@ -11,10 +11,6 @@ pub const MESSAGE: &str = "Something random"; pub const ISSUER: &str = "textsender_auth"; pub const AUDIENCE: &str = "textsender"; -pub fn get_issued() -> time::Result { - Ok(time::OffsetDateTime::now_utc()) -} - pub fn get_expiration(issued: &time::OffsetDateTime) -> Result { let duration_expire = time::Duration::hours(4); Ok(*issued + duration_expire) -- 2.47.3 From e1b633d6773be6d29a9f40df7a781d1516931edc Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:06:05 -0400 Subject: [PATCH 06/21] Adding test file --- tests/tests.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 tests/tests.rs diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 0000000..1e68fb2 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,11 @@ + + +#[cfg(test)] +mod tests { + #[test] + fn test_demo() { + let i = 0; + + assert_eq!(0, i, "Values should match {} {}", 0, 1); + } +} -- 2.47.3 From a4683b6989fc694e69e9eb2ad7cc1b01281eef51 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:14:03 -0400 Subject: [PATCH 07/21] Moved test file --- {tests => src}/tests.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {tests => src}/tests.rs (100%) diff --git a/tests/tests.rs b/src/tests.rs similarity index 100% rename from tests/tests.rs rename to src/tests.rs -- 2.47.3 From 8b6101a426d2015a192a8f3c9959ad4295c07aca Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:22:15 -0400 Subject: [PATCH 08/21] Moved file --- {src => tests}/tests.rs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src => tests}/tests.rs (100%) diff --git a/src/tests.rs b/tests/tests.rs similarity index 100% rename from src/tests.rs rename to tests/tests.rs -- 2.47.3 From f4484200d872dcfa507f699abf371ae903376b51 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:22:47 -0400 Subject: [PATCH 09/21] Saving test --- tests/tests.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 1e68fb2..293b229 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,11 +1,8 @@ -#[cfg(test)] -mod tests { - #[test] - fn test_demo() { - let i = 0; +#[test] +fn test_demo() { + let i = 0; - assert_eq!(0, i, "Values should match {} {}", 0, 1); - } + assert_eq!(0, i, "Values should match {} {}", 0, 1); } -- 2.47.3 From 637c6ba2a02cbfe4526c58f89ffacda16766d5a2 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:48:15 -0400 Subject: [PATCH 10/21] Saving changes --- src/lib.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 124 +--------------------------------------------------- 2 files changed, 121 insertions(+), 122 deletions(-) create mode 100644 src/lib.rs diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..e4bd359 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,119 @@ +pub mod callers; +pub mod config; +pub mod db; +pub mod hashing; +pub mod repo; +pub mod token_stuff; + +pub mod init { + use axum::{ + Router, + routing::{get, post}, + }; + use utoipa::OpenApi; + + use super::callers; + use callers::common as common_callers; + use callers::login as login_caller; + use callers::register as register_caller; + use login_caller::response as login_responses; + use register_caller::response as register_responses; + + #[derive(utoipa::OpenApi)] + #[openapi( + paths( + common_callers::endpoint::db_ping, common_callers::endpoint::root, + register_caller::register_user, login_caller::user_login, + ), + components(schemas(common_callers::response::TestResult, + register_responses::Response, login_responses::LoginResponse)), + tags( + (name = "TextSender Auth API", description = "Auth API for TextSender API") + ) + )] + struct ApiDoc; + + mod cors { + pub async fn configure_cors() -> tower_http::cors::CorsLayer { + // Start building the CORS layer with common settings + let cors = tower_http::cors::CorsLayer::new() + .allow_methods([ + axum::http::Method::GET, + axum::http::Method::POST, + axum::http::Method::PUT, + axum::http::Method::DELETE, + ]) // Specify allowed methods:cite[2] + .allow_headers([ + axum::http::header::CONTENT_TYPE, + axum::http::header::AUTHORIZATION, + ]) // Specify allowed headers:cite[2] + .allow_credentials(true) // If you need to send cookies or authentication headers:cite[2] + .max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2] + + // Dynamically set the allowed origin based on the environment + match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { + Ok("production") => { + let allowed_origins_env = + textsender_models::envy::environment::get_allowed_origins().await; + match textsender_models::envy::utility::delimitize(&allowed_origins_env) { + Ok(alwd) => { + let allowed_origins: Vec = alwd + .into_iter() + .map(|s| s.parse::().unwrap()) + .collect(); + cors.allow_origin(allowed_origins) + } + Err(err) => { + eprintln!( + "Could not parse out allowed origins from env: Error: {err:?}" + ); + std::process::exit(-1); + } + } + } + _ => { + // Development (default): Allow localhost origins + cors.allow_origin(vec![ + "http://localhost:4200".parse().unwrap(), + "http://127.0.0.1:4200".parse().unwrap(), + ]) + } + } + } + } + + pub async fn routes() -> Router { + // build our application with a route + Router::new() + .route( + callers::endpoints::DBTEST, + get(callers::common::endpoint::db_ping), + ) + .route( + callers::endpoints::ROOT, + get(callers::common::endpoint::root), + ) + .route( + callers::endpoints::REGISTER, + post(callers::register::register_user), + ) + .route(callers::endpoints::LOGIN, post(callers::login::user_login)) + .layer(cors::configure_cors().await) + } + + pub async fn app() -> Router { + let pool = super::db::init::create_pool() + .await + .expect("Failed to create pool"); + + super::db::init::migrations(&pool).await; + + routes() + .await + .merge( + utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") + .url("/api-docs/openapi.json", ApiDoc::openapi()), + ) + .layer(axum::Extension(pool)) + } +} diff --git a/src/main.rs b/src/main.rs index a204c32..026e22a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,132 +1,12 @@ -pub mod callers; -pub mod config; -pub mod db; -pub mod hashing; -pub mod repo; -pub mod token_stuff; - #[tokio::main] async fn main() { // initialize tracing tracing_subscriber::fmt::init(); - let app = init::app().await; + let app = textsender_auth::init::app().await; // run our app with hyper, listening globally on port 9080 - let url = config::get_full(); + let url = textsender_auth::config::get_full(); let listener = tokio::net::TcpListener::bind(url).await.unwrap(); axum::serve(listener, app).await.unwrap(); } - -mod init { - use axum::{ - Router, - routing::{get, post}, - }; - use utoipa::OpenApi; - - use super::callers; - use callers::common as common_callers; - use callers::login as login_caller; - use callers::register as register_caller; - use login_caller::response as login_responses; - use register_caller::response as register_responses; - - #[derive(utoipa::OpenApi)] - #[openapi( - paths( - common_callers::endpoint::db_ping, common_callers::endpoint::root, - register_caller::register_user, login_caller::user_login, - ), - components(schemas(common_callers::response::TestResult, - register_responses::Response, login_responses::LoginResponse)), - tags( - (name = "TextSender Auth API", description = "Auth API for TextSender API") - ) - )] - struct ApiDoc; - - mod cors { - pub async fn configure_cors() -> tower_http::cors::CorsLayer { - // Start building the CORS layer with common settings - let cors = tower_http::cors::CorsLayer::new() - .allow_methods([ - axum::http::Method::GET, - axum::http::Method::POST, - axum::http::Method::PUT, - axum::http::Method::DELETE, - ]) // Specify allowed methods:cite[2] - .allow_headers([ - axum::http::header::CONTENT_TYPE, - axum::http::header::AUTHORIZATION, - ]) // Specify allowed headers:cite[2] - .allow_credentials(true) // If you need to send cookies or authentication headers:cite[2] - .max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2] - - // Dynamically set the allowed origin based on the environment - match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { - Ok("production") => { - let allowed_origins_env = - textsender_models::envy::environment::get_allowed_origins().await; - match textsender_models::envy::utility::delimitize(&allowed_origins_env) { - Ok(alwd) => { - let allowed_origins: Vec = alwd - .into_iter() - .map(|s| s.parse::().unwrap()) - .collect(); - cors.allow_origin(allowed_origins) - } - Err(err) => { - eprintln!( - "Could not parse out allowed origins from env: Error: {err:?}" - ); - std::process::exit(-1); - } - } - } - _ => { - // Development (default): Allow localhost origins - cors.allow_origin(vec![ - "http://localhost:4200".parse().unwrap(), - "http://127.0.0.1:4200".parse().unwrap(), - ]) - } - } - } - } - - pub async fn routes() -> Router { - // build our application with a route - Router::new() - .route( - callers::endpoints::DBTEST, - get(callers::common::endpoint::db_ping), - ) - .route( - callers::endpoints::ROOT, - get(callers::common::endpoint::root), - ) - .route( - callers::endpoints::REGISTER, - post(callers::register::register_user), - ) - .route(callers::endpoints::LOGIN, post(callers::login::user_login)) - .layer(cors::configure_cors().await) - } - - pub async fn app() -> Router { - let pool = super::db::init::create_pool() - .await - .expect("Failed to create pool"); - - super::db::init::migrations(&pool).await; - - routes() - .await - .merge( - utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") - .url("/api-docs/openapi.json", ApiDoc::openapi()), - ) - .layer(axum::Extension(pool)) - } -} -- 2.47.3 From 724d3ca9835a4e28f6a14977c352ba5348dd164b Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:48:41 -0400 Subject: [PATCH 11/21] Updated test --- tests/tests.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 293b229..7c71642 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,4 +1,97 @@ +use textsender_auth; +use textsender_auth::callers; +use textsender_auth::db; +use textsender_auth::init; +mod db_mgr { + use std::str::FromStr; + + pub const LIMIT: usize = 6; + + pub async fn get_pool() -> Result { + let tm_db_url = textsender_models::envy::environment::get_db_url() + .await + .value; + let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); + sqlx::PgPool::connect_with(tm_options).await + } + + pub async fn generate_db_name() -> String { + let db_name = + get_database_name().await.unwrap() + &"_" + &uuid::Uuid::new_v4().to_string()[..LIMIT]; + db_name + } + + pub async fn connect_to_db(db_name: &str) -> Result { + let db_url = textsender_models::envy::environment::get_db_url() + .await + .value; + let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name); + sqlx::PgPool::connect_with(options).await + } + + pub async fn create_database( + template_pool: &sqlx::PgPool, + db_name: &str, + ) -> Result<(), sqlx::Error> { + let create_query = format!("CREATE DATABASE {}", db_name); + match sqlx::query(&create_query).execute(template_pool).await { + Ok(_) => Ok(()), + Err(e) => Err(e), + } + } + + // Function to drop a database + pub async fn drop_database( + template_pool: &sqlx::PgPool, + db_name: &str, + ) -> Result<(), sqlx::Error> { + let drop_query = format!("DROP DATABASE IF EXISTS {} WITH (FORCE)", db_name); + sqlx::query(&drop_query).execute(template_pool).await?; + Ok(()) + } + + pub async fn get_database_name() -> Result> { + let database_url = textsender_models::envy::environment::get_db_url() + .await + .value; + + let parsed_url = url::Url::parse(&database_url)?; + if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" { + match parsed_url + .path_segments() + .and_then(|segments| segments.last().map(|s| s.to_string())) + { + Some(sss) => Ok(sss), + None => Err("Error parsing".into()), + } + } else { + // Handle other database types if needed + Err("Error parsing".into()) + } + } +} + +pub mod requests { + use tower::ServiceExt; // for `call`, `oneshot`, and `ready` + + pub async fn register_user( + app: &axum::Router, + ) -> Result { + let payload = serde_json::json!({ + "username": "Billy", + "password": "Bob", + }); + let req = axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(super::callers::endpoints::LOGIN) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(axum::body::Body::from(payload.to_string())) + .unwrap(); + + app.clone().oneshot(req).await + } +} #[test] fn test_demo() { @@ -6,3 +99,32 @@ fn test_demo() { assert_eq!(0, i, "Values should match {} {}", 0, 1); } + +#[tokio::test] +async fn test_register_user() { + 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 requests::register_user(&app).await { + Ok(_response) => {} + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } +} -- 2.47.3 From b57403026eb0472264e06e991cfdeb2c0c7589c5 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 22:52:17 -0400 Subject: [PATCH 12/21] Removing file --- .dockerignore.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .dockerignore.yml diff --git a/.dockerignore.yml b/.dockerignore.yml deleted file mode 100644 index 4f436f8..0000000 --- a/.dockerignore.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Ignore build artifacts -target/ -pkg/ - -# Ignore git directory -.git/ - -.gitea/ - -# Ignore environment files (configure via docker-compose instead) -.env* - -# Ignore OS specific files -*.DS_Store - -# Add any other files/directories you don't need in the image -# e.g., logs/, tmp/ -- 2.47.3 From b76784148e122d82b2158dd9b10b309e1fb7a3a0 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:02:32 -0400 Subject: [PATCH 13/21] Adding code to test --- tests/tests.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 7c71642..977e7a2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -127,4 +127,11 @@ async fn test_register_user() { 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 c2c734b119b0db40706782d2b3133894311dcb19 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:14:45 -0400 Subject: [PATCH 14/21] Updated test --- tests/tests.rs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 977e7a2..21a2480 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -79,8 +79,11 @@ pub mod requests { app: &axum::Router, ) -> Result { let payload = serde_json::json!({ - "username": "Billy", - "password": "Bob", + "username": String::from(super::TEST_USERNAME), + "password": String::from(super::TEST_PASSWORD), + "phone_number": String::from(super::TEST_PHONE_NUMBER), + "firstname": String::from(super::TEST_FIRSTNAME), + "lastname": String::from(super::TEST_LASTNAME), }); let req = axum::http::Request::builder() .method(axum::http::Method::POST) @@ -100,6 +103,12 @@ fn test_demo() { assert_eq!(0, i, "Values should match {} {}", 0, 1); } +const TEST_FIRSTNAME: &str = "Billy"; +const TEST_LASTNAME: &str = "Bob"; +const TEST_USERNAME: &str = "BillyBob01"; +const TEST_PASSWORD: &str = "923ndcry392qryudx328qrdy328r"; +const TEST_PHONE_NUMBER: &str = "+10123456789"; + #[tokio::test] async fn test_register_user() { let tm_pool = db_mgr::get_pool().await.unwrap(); @@ -122,7 +131,19 @@ async fn test_register_user() { let app = init::routes().await.layer(axum::Extension(pool)); match requests::register_user(&app).await { - Ok(_response) => {} + Ok(response) => { + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let parsed_body: callers::register::response::Response = + serde_json::from_slice(&body).unwrap(); + assert!(parsed_body.data.len() > 0, "No data found"); + let returned_user = &parsed_body.data[0]; + assert_eq!( + TEST_USERNAME, returned_user.username, + "Error with returned user" + ); + } Err(err) => { assert!(false, "Error: {err:?}"); } -- 2.47.3 From df692e31ab8c4bd97349d66cb314305339dd5c33 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:17:32 -0400 Subject: [PATCH 15/21] Adding another test --- tests/tests.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index 21a2480..05dac2a 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -112,7 +112,6 @@ const TEST_PHONE_NUMBER: &str = "+10123456789"; #[tokio::test] async fn test_register_user() { 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 { @@ -156,3 +155,7 @@ async fn test_register_user() { } } } + +#[tokio::test] +async fn test_login_user() { +} -- 2.47.3 From e2af4080ef6fd36b7cfdf427d016710565a69cd9 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:29:20 -0400 Subject: [PATCH 16/21] Fixing stuff --- tests/tests.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 05dac2a..f7e5a9f 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -158,4 +158,22 @@ async fn test_register_user() { #[tokio::test] async fn test_login_user() { + 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()); + } + } + + match db_mgr::drop_database(&tm_pool, &db_name).await { + Ok(()) => {} + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } } -- 2.47.3 From 31e501d6b998e17494f389286dbb24a8fbfa1310 Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:34:06 -0400 Subject: [PATCH 17/21] Hopefully --- tests/tests.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index f7e5a9f..7708911 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -131,13 +131,19 @@ async fn test_register_user() { match requests::register_user(&app).await { Ok(response) => { + assert_eq!( + axum::http::StatusCode::CREATED, + response.status(), + "Status does not match {:?}", + response.status() + ); let body = axum::body::to_bytes(response.into_body(), usize::MAX) .await .unwrap(); let parsed_body: callers::register::response::Response = serde_json::from_slice(&body).unwrap(); - assert!(parsed_body.data.len() > 0, "No data found"); let returned_user = &parsed_body.data[0]; + assert_eq!( TEST_USERNAME, returned_user.username, "Error with returned user" -- 2.47.3 From b54d7e49c9e8845ea7dc604aedd2ec3fc6c3d5fb Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:50:12 -0400 Subject: [PATCH 18/21] Let's gogit add tests --- tests/tests.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 7708911..618ae12 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -85,6 +85,25 @@ pub mod requests { "firstname": String::from(super::TEST_FIRSTNAME), "lastname": String::from(super::TEST_LASTNAME), }); + let req = axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(super::callers::endpoints::REGISTER) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(axum::body::Body::from(payload.to_string())) + .unwrap(); + + app.clone().oneshot(req).await + } + + pub async fn login_user( + app: &axum::Router, + username: &str, + password: &str, + ) -> Result { + let payload = serde_json::json!({ + "username": username, + "password": password, + }); let req = axum::http::Request::builder() .method(axum::http::Method::POST) .uri(super::callers::endpoints::LOGIN) @@ -109,6 +128,52 @@ const TEST_USERNAME: &str = "BillyBob01"; const TEST_PASSWORD: &str = "923ndcry392qryudx328qrdy328r"; const TEST_PHONE_NUMBER: &str = "+10123456789"; +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("")) + } else { + match axum::body::to_bytes(response.into_body(), usize::MAX).await { + Ok(body) => { + let parsed_body: callers::register::response::Response = + serde_json::from_slice(&body).unwrap(); + Ok(parsed_body.data[0].clone()) + } + 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("")) + } else { + match axum::body::to_bytes(response.into_body(), usize::MAX).await { + Ok(body) => { + let parsed_body: callers::login::response::LoginResponse = + serde_json::from_slice(&body).unwrap(); + Ok(parsed_body.data[0].clone()) + } + Err(err) => Err(std::io::Error::other(err.to_string())), + } + } + } + Err(err) => Err(std::io::Error::other(err.to_string())), + } +} + #[tokio::test] async fn test_register_user() { let tm_pool = db_mgr::get_pool().await.unwrap(); @@ -176,6 +241,24 @@ async fn test_login_user() { } } + 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 register_user(&app).await { + Ok(user) => match login_user(&app, &user.username, TEST_PASSWORD).await { + Ok(_login_result) => {} + 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 ebd9fb0a2e149cd90cfc3cc202daa8ceccd62d5c Mon Sep 17 00:00:00 2001 From: phoenix Date: Tue, 9 Jun 2026 23:57:43 -0400 Subject: [PATCH 19/21] Code formatting --- tests/tests.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/tests/tests.rs b/tests/tests.rs index 618ae12..7bd28bd 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -115,13 +115,6 @@ pub mod requests { } } -#[test] -fn test_demo() { - let i = 0; - - assert_eq!(0, i, "Values should match {} {}", 0, 1); -} - const TEST_FIRSTNAME: &str = "Billy"; const TEST_LASTNAME: &str = "Bob"; const TEST_USERNAME: &str = "BillyBob01"; @@ -134,7 +127,10 @@ async fn register_user( match requests::register_user(&app).await { Ok(response) => { if axum::http::StatusCode::CREATED != response.status() { - Err(std::io::Error::other("")) + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) } else { match axum::body::to_bytes(response.into_body(), usize::MAX).await { Ok(body) => { @@ -158,7 +154,10 @@ async fn login_user( match requests::login_user(&app, username, password).await { Ok(response) => { if axum::http::StatusCode::OK != response.status() { - Err(std::io::Error::other("")) + Err(std::io::Error::other(format!( + "Status code is off {:?}", + response.status() + ))) } else { match axum::body::to_bytes(response.into_body(), usize::MAX).await { Ok(body) => { @@ -207,6 +206,8 @@ async fn test_register_user() { .unwrap(); let parsed_body: callers::register::response::Response = serde_json::from_slice(&body).unwrap(); + assert!(parsed_body.data.len() > 0, "No data returned"); + let returned_user = &parsed_body.data[0]; assert_eq!( @@ -249,7 +250,13 @@ async fn test_login_user() { match register_user(&app).await { Ok(user) => match login_user(&app, &user.username, TEST_PASSWORD).await { - Ok(_login_result) => {} + Ok(login_result) => { + assert_eq!( + false, + login_result.access_token.is_empty(), + "Access token is empty when it should not be" + ); + } Err(err) => { assert!(false, "Error: {err:?}"); } -- 2.47.3 From 0f9f55c3c12538cc9ea9f9654620799b09431f40 Mon Sep 17 00:00:00 2001 From: phoenix Date: Wed, 10 Jun 2026 00:08:50 -0400 Subject: [PATCH 20/21] Leaving todo --- src/callers/login.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/callers/login.rs b/src/callers/login.rs index af0493c..83e6cff 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -18,6 +18,16 @@ pub mod response { pub message: String, pub data: Vec, } + + pub async fn extract( + response: axum::response::Response, + ) -> Result { + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let _parsed_body: LoginResponse = serde_json::from_slice(&body).unwrap(); + todo!("Add code to convert axum::Response to this type"); + } } /// Endpoint for a user login -- 2.47.3 From f705cd4a59d90ccbce54b325ed70eccb9b5da58e Mon Sep 17 00:00:00 2001 From: phoenix Date: Wed, 10 Jun 2026 00:09:17 -0400 Subject: [PATCH 21/21] 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 c999c73..d4ab5d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2213,7 +2213,7 @@ dependencies = [ [[package]] name = "textsender_auth" -version = "0.1.14" +version = "0.1.15" dependencies = [ "argon2", "async-std", diff --git a/Cargo.toml b/Cargo.toml index 6128aaf..3cc1795 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "textsender_auth" -version = "0.1.14" +version = "0.1.15" edition = "2024" rust-version = "1.95" -- 2.47.3