From 866c1a33316efbb1cdf9b94649d6f6447c7c5ed7 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 5 Apr 2025 16:40:53 -0400 Subject: [PATCH 01/24] Added jwt dependency --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 6231883..7053b4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ uuid = { version = "1.16.0", features = ["v4", "serde"] } argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version rand = { version = "0.9" } time = { version = "0.3.41", features = ["macros", "serde"] } +jwt = { version = "0.16.0" } icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.0" } [dev-dependencies] -- 2.43.0 From e02056a59fdc7457363ad3a9ca1a56dac7beb6a1 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 5 Apr 2025 16:48:14 -0400 Subject: [PATCH 02/24] Changed jwt package --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7053b4b..a649325 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ uuid = { version = "1.16.0", features = ["v4", "serde"] } argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version rand = { version = "0.9" } time = { version = "0.3.41", features = ["macros", "serde"] } -jwt = { version = "0.16.0" } +josekit = { version = "0.10.1" } icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.0" } [dev-dependencies] -- 2.43.0 From 1022c6b1346f0a281e709f4797d760bf63483a1d Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 17:33:16 -0400 Subject: [PATCH 03/24] Added code for creating token --- src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 1e67995..cf4130f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,3 +39,67 @@ pub mod db_pool { env::var(keys::DBURL).expect(keys::error::ERROR) } } + +mod token_stuff { + use dotenvy; + use josekit::{ + self, + jws::{JwsHeader, alg::hmac::HmacJwsAlgorithm::Hs256}, + jwt::{self, JwtPayload}, + }; + + pub const TOKENTYPE: &str = "JWT"; + pub const TESTKEY: &str = "dfsdferferf"; + + fn get_key() -> Result { + Ok(String::from(TESTKEY)) + } + + pub fn create_token(provided_key: &String) -> Result { + let mut header = JwsHeader::new(); + header.set_token_type(TOKENTYPE); + + let mut payload = JwtPayload::new(); + payload.set_subject("Something random"); + + let mut key: String = String::new(); + if provided_key.is_empty() { + key = get_key().unwrap(); + } else { + key = provided_key.clone(); + } + + let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); + let jwt = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(); + + Ok(jwt) + } + + pub fn verify_token(key: &String, token: &String) -> bool { + let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap(); + let (payload, _header) = jwt::decode_with_verifier(&token, &ver).unwrap(); + match payload.subject() { + Some(_sub) => true, + None => false, + } + } +} + +#[cfg(test)] +mod tests { + use crate::token_stuff; + + #[test] + fn test_tokenize() { + let special_key = String::from("mysecret-dsfsdfsdfsdfsdgfsdgsdgsdfg"); + match token_stuff::create_token(&special_key) { + Ok(token) => { + let result = token_stuff::verify_token(&special_key, &token); + assert!(result, "Token not verified"); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + }; + } +} -- 2.43.0 From 43bdce1e93ce6656da108746fa384ec8a92999bd Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 17:35:21 -0400 Subject: [PATCH 04/24] Added new variable --- .env.sample | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.sample b/.env.sample index 135f9aa..c7494ce 100644 --- a/.env.sample +++ b/.env.sample @@ -1 +1,2 @@ DATABASE_URL=postgres://username:password@localhost/database_name +SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h \ No newline at end of file -- 2.43.0 From 84a9bc4d212de8bc9438c19b6a531611c2374116 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 17:50:16 -0400 Subject: [PATCH 05/24] Added login endpoint --- src/callers/login.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/callers/mod.rs | 2 ++ 2 files changed, 43 insertions(+) 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..8026e81 --- /dev/null +++ b/src/callers/login.rs @@ -0,0 +1,41 @@ +pub mod request { + use serde::{Deserialize, Serialize}; + + #[derive(Default, Deserialize, Serialize)] + pub struct Request { + } +} + +pub mod response { + use serde::{Deserialize, Serialize}; + + #[derive(Default, Deserialize, Serialize)] + pub struct Response { + pub message: String, + } +} + +pub mod endpoint { + use axum::{Json, http::StatusCode}; + + use crate::hashing; + use crate::repo; + use crate::token_stuff; + + use super::request; + use super::response; + + pub async fn login( + axum::Extension(pool): axum::Extension, + Json(payload): Json, + ) -> (StatusCode, Json) { + let mut usr = icarus_models::user::User::default(); + + ( + StatusCode::OK, + Json(response::Response { + message: String::from("Not implemented"), + }), + ) + } +} diff --git a/src/callers/mod.rs b/src/callers/mod.rs index 33ddec1..ab9f31e 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -1,8 +1,10 @@ pub mod common; +pub mod login; pub mod register; pub mod endpoints { pub const ROOT: &str = "/"; pub const REGISTER: &str = "/api/v2/register"; pub const DBTEST: &str = "/api/v2/test/db"; + pub const LOGIN: &str = "/api/v2/login"; } -- 2.43.0 From 2bc5b712635a1760fe2de1b58050666b0eca7ff3 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 17:50:57 -0400 Subject: [PATCH 06/24] Updated test --- src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cf4130f..08bc22a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,9 +50,12 @@ mod token_stuff { pub const TOKENTYPE: &str = "JWT"; pub const TESTKEY: &str = "dfsdferferf"; + pub const KEY_ENV: &str = "SECRET_KEY"; - fn get_key() -> Result { - Ok(String::from(TESTKEY)) + pub fn get_key() -> Result { + dotenvy::dotenv().ok(); + let key = std::env::var(KEY_ENV).expect("SECRET_KEY_NOT_FOUND"); + Ok(key) } pub fn create_token(provided_key: &String) -> Result { @@ -91,7 +94,7 @@ mod tests { #[test] fn test_tokenize() { - let special_key = String::from("mysecret-dsfsdfsdfsdfsdgfsdgsdgsdfg"); + let special_key = token_stuff::get_key().unwrap(); match token_stuff::create_token(&special_key) { Ok(token) => { let result = token_stuff::verify_token(&special_key, &token); -- 2.43.0 From db3f08381097e55164fce6a70c3cac69cc3af1b7 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 18:40:40 -0400 Subject: [PATCH 07/24] Filled out code --- src/callers/login.rs | 53 ++++++++++++++++++++++++++++++++------ src/hashing/mod.rs | 4 +++ src/lib.rs | 13 ++++------ src/repo/mod.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 15 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 8026e81..84e790a 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -3,6 +3,8 @@ pub mod request { #[derive(Default, Deserialize, Serialize)] pub struct Request { + pub username: String, + pub password: String, } } @@ -29,13 +31,50 @@ pub mod endpoint { axum::Extension(pool): axum::Extension, Json(payload): Json, ) -> (StatusCode, Json) { - let mut usr = icarus_models::user::User::default(); + let usr = icarus_models::user::User { + username: payload.username, + password: payload.password, + ..Default::default() + }; + // usr.username = payload.username; + // usr.password = payload.password; - ( - StatusCode::OK, - Json(response::Response { - message: String::from("Not implemented"), - }), - ) + // Check if user exists + let user_exists = repo::user::exists(&pool, &usr.username).await.unwrap(); + if !user_exists { + // End + } + let user = repo::user::get(&pool, &usr.username).await.unwrap(); + let salt = repo::salt::get(&pool, &user.salt_id).await.unwrap(); + let salt_literal = salt.salt.clone(); + let salt_str = hashing::get_salt(&salt_literal).unwrap(); + + // Check if password is correct + let hash_password = hashing::hash_password(&usr.password, &salt_str).unwrap(); + if hashing::verify_password(&usr.password, hash_password.clone()).unwrap() { + println!("Do work"); + } + + // Create token + let key = token_stuff::get_key().unwrap(); + let token_literal = token_stuff::create_token(&key).unwrap(); + + let result = token_stuff::verify_token(&key, &token_literal); + + if result { + ( + StatusCode::OK, + Json(response::Response { + message: String::from("Not implemented"), + }), + ) + } else { + ( + StatusCode::BAD_REQUEST, + Json(response::Response { + message: String::from("Not implemented"), + }), + ) + } } } diff --git a/src/hashing/mod.rs b/src/hashing/mod.rs index 1386d0c..a3fbbd4 100644 --- a/src/hashing/mod.rs +++ b/src/hashing/mod.rs @@ -15,6 +15,10 @@ pub fn generate_salt() -> Result { Ok(salt) } +pub fn get_salt(s: &str) -> Result { + SaltString::from_b64(s) +} + pub fn hash_password( password: &String, salt: &SaltString, diff --git a/src/lib.rs b/src/lib.rs index 08bc22a..ea7dde2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,7 +41,6 @@ pub mod db_pool { } mod token_stuff { - use dotenvy; use josekit::{ self, jws::{JwsHeader, alg::hmac::HmacJwsAlgorithm::Hs256}, @@ -49,7 +48,6 @@ mod token_stuff { }; pub const TOKENTYPE: &str = "JWT"; - pub const TESTKEY: &str = "dfsdferferf"; pub const KEY_ENV: &str = "SECRET_KEY"; pub fn get_key() -> Result { @@ -65,12 +63,11 @@ mod token_stuff { let mut payload = JwtPayload::new(); payload.set_subject("Something random"); - let mut key: String = String::new(); - if provided_key.is_empty() { - key = get_key().unwrap(); + let key: String = if provided_key.is_empty() { + get_key().unwrap() } else { - key = provided_key.clone(); - } + provided_key.to_owned() + }; let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); let jwt = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(); @@ -80,7 +77,7 @@ mod token_stuff { pub fn verify_token(key: &String, token: &String) -> bool { let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap(); - let (payload, _header) = jwt::decode_with_verifier(&token, &ver).unwrap(); + let (payload, _header) = jwt::decode_with_verifier(token, &ver).unwrap(); match payload.subject() { Some(_sub) => true, None => false, diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 049a840..b8a8c8c 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -7,6 +7,41 @@ pub mod user { pub date_created: Option, } + pub async fn get( + pool: &sqlx::PgPool, + username: &String, + ) -> Result { + let result = sqlx::query( + r#" + SELECT * FROM "user" WHERE username = $1 + "#, + ) + .bind(username) + .fetch_optional(pool) + .await; + + match result { + Ok(r) => match r { + Some(r) => Ok(icarus_models::user::User { + id: r.try_get("id")?, + username: r.try_get("username")?, + password: r.try_get("password")?, + email: r.try_get("email")?, + email_verified: r.try_get("email_verified")?, + phone: r.try_get("phone")?, + salt_id: r.try_get("salt_id")?, + firstname: r.try_get("firstname")?, + lastname: r.try_get("lastname")?, + date_created: r.try_get("date_created")?, + last_login: r.try_get("last_login")?, + status: r.try_get("status")?, + }), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } + } + pub async fn exists(pool: &sqlx::PgPool, username: &String) -> Result { let result = sqlx::query( r#" @@ -72,6 +107,31 @@ pub mod salt { pub id: uuid::Uuid, } + pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + ) -> Result { + let result = sqlx::query( + r#" + SELECT * FROM "salt" WHERE id = $1 + "#, + ) + .bind(id) + .fetch_optional(pool) + .await; + + match result { + Ok(r) => match r { + Some(r) => Ok(icarus_models::user::salt::Salt { + id: r.try_get("id")?, + salt: r.try_get("salt")?, + }), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } + } + pub async fn insert( pool: &sqlx::PgPool, salt: &icarus_models::user::salt::Salt, -- 2.43.0 From 98d58b99062d48e7007f59fb894466c730379a81 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 18:45:17 -0400 Subject: [PATCH 08/24] Moved code for db stuff --- src/lib.rs | 11 ++++++++++- src/main.rs | 18 +++--------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ea7dde2..67b590b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,7 +15,7 @@ mod connection_settings { pub const MAXCONN: u32 = 5; } -pub mod db_pool { +pub mod db { use sqlx::postgres::PgPoolOptions; use std::env; @@ -38,6 +38,15 @@ pub mod db_pool { env::var(keys::DBURL).expect(keys::error::ERROR) } + + pub async fn migrations(pool: &sqlx::PgPool) { + // Run migrations using the sqlx::migrate! macro + // Assumes your migrations are in a ./migrations folder relative to Cargo.toml + sqlx::migrate!("./migrations") + .run(pool) + .await + .expect("Failed to run migrations on testcontainer DB"); + } } mod token_stuff { diff --git a/src/main.rs b/src/main.rs index da52960..c9c136c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,17 +14,6 @@ async fn main() { axum::serve(listener, app).await.unwrap(); } -mod db { - pub async fn migrations(pool: &sqlx::PgPool) { - // Run migrations using the sqlx::migrate! macro - // Assumes your migrations are in a ./migrations folder relative to Cargo.toml - sqlx::migrate!("./migrations") - .run(pool) - .await - .expect("Failed to run migrations on testcontainer DB"); - } -} - mod init { use axum::{ Router, @@ -32,7 +21,6 @@ mod init { }; use crate::callers; - use crate::db; pub async fn routes() -> Router { // build our application with a route @@ -46,11 +34,11 @@ mod init { } pub async fn app() -> Router { - let pool = icarus_auth::db_pool::create_pool() + let pool = icarus_auth::db::create_pool() .await .expect("Failed to create pool"); - db::migrations(&pool).await; + icarus_auth::db::migrations(&pool).await; routes().await.layer(axum::Extension(pool)) } @@ -180,7 +168,7 @@ mod tests { let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); - db::migrations(&pool).await; + icarus_auth::db::migrations(&pool).await; let app = init::routes().await.layer(axum::Extension(pool)); -- 2.43.0 From 584ee5be75266b48b12b41af1b664c8b24d04f18 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 18:48:44 -0400 Subject: [PATCH 09/24] Updated workflow --- .gitea/workflows/workflow.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitea/workflows/workflow.yml b/.gitea/workflows/workflow.yml index 557a245..aa3fded 100644 --- a/.gitea/workflows/workflow.yml +++ b/.gitea/workflows/workflow.yml @@ -73,6 +73,7 @@ jobs: # Define DATABASE_URL for tests to use DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:5432/${{ secrets.DB_TEST_NAME || 'testdb' }} RUST_LOG: info # Optional: configure test log level + SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }} # Make SSH agent available if tests fetch private dependencies SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }} run: | -- 2.43.0 From 200d1d02898a71a39d5650f7c5a083b12ac405fd Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 19:10:02 -0400 Subject: [PATCH 10/24] Login endpoint is doing something --- src/callers/login.rs | 9 +++++++++ src/lib.rs | 2 +- src/main.rs | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 84e790a..a2e8f79 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -14,6 +14,7 @@ pub mod response { #[derive(Default, Deserialize, Serialize)] pub struct Response { pub message: String, + pub data: Vec, } } @@ -66,6 +67,13 @@ pub mod endpoint { StatusCode::OK, Json(response::Response { message: String::from("Not implemented"), + data: vec![icarus_models::login_result::LoginResult { + id: user.id, + username: user.username, + token: token_literal, + token_type: String::from("JWT"), + expiration: -1, + }], }), ) } else { @@ -73,6 +81,7 @@ pub mod endpoint { StatusCode::BAD_REQUEST, Json(response::Response { message: String::from("Not implemented"), + data: vec![icarus_models::login_result::LoginResult::default()], }), ) } diff --git a/src/lib.rs b/src/lib.rs index 67b590b..9942006 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,7 +45,7 @@ pub mod db { sqlx::migrate!("./migrations") .run(pool) .await - .expect("Failed to run migrations on testcontainer DB"); + .expect("Failed to run migrations"); } } diff --git a/src/main.rs b/src/main.rs index c9c136c..4a516b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,10 @@ mod init { callers::endpoints::REGISTER, post(callers::register::register_user), ) + .route( + callers::endpoints::LOGIN, + post(callers::login::endpoint::login), + ) } pub async fn app() -> Router { -- 2.43.0 From b8eee7fc7e9646bdc085f4250785525d435efb23 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 19:25:23 -0400 Subject: [PATCH 11/24] Added login test --- src/main.rs | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/main.rs b/src/main.rs index 4a516b8..0dcfcb6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -236,4 +236,118 @@ mod tests { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + + #[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()); + } + } + + let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); + + icarus_auth::db::migrations(&pool).await; + + let app = init::routes().await.layer(axum::Extension(pool)); + + let usr = icarus_auth::callers::register::request::Request { + username: String::from("somethingsss"), + password: String::from("Raindown!"), + email: String::from("dev@null.com"), + phone: String::from("1234567890"), + firstname: String::from("Bob"), + lastname: String::from("Smith"), + }; + + let payload = json!({ + "username": &usr.username, + "password": &usr.password, + "email": &usr.email, + "phone": &usr.phone, + "firstname": &usr.firstname, + "lastname": &usr.lastname, + }); + + let response = app + .clone() + .oneshot( + Request::builder() + .method(axum::http::Method::POST) + .uri(callers::endpoints::REGISTER) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(Body::from(payload.to_string())) + .unwrap(), + ) + .await; + + match response { + Ok(resp) => { + assert_eq!( + resp.status(), + StatusCode::CREATED, + "Message: {:?} {:?}", + resp, + usr.username + ); + let body = axum::body::to_bytes(resp.into_body(), usize::MAX) + .await + .unwrap(); + let parsed_body: callers::register::response::Response = + serde_json::from_slice(&body).unwrap(); + let returned_usr = &parsed_body.data[0]; + + assert_eq!(false, returned_usr.id.is_nil(), "Id is not populated"); + + assert_eq!( + usr.username, returned_usr.username, + "Usernames do not match" + ); + assert!(returned_usr.date_created.is_some(), "Date Created is empty"); + + let login_payload = json!({ + "username": &usr.username, + "password": &usr.password, + }); + + match app + .oneshot( + Request::builder() + .method(axum::http::Method::POST) + .uri(callers::endpoints::LOGIN) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(Body::from(login_payload.to_string())) + .unwrap(), + ) + .await + { + Ok(resp) => { + assert_eq!(StatusCode::OK, resp.status(), "Status is not right"); + let body = axum::body::to_bytes(resp.into_body(), usize::MAX) + .await + .unwrap(); + let parsed_body: callers::login::response::Response = + serde_json::from_slice(&body).unwrap(); + let login_result = &parsed_body.data[0]; + assert!(!login_result.id.is_nil(), "Id is nil"); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + } + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + }; + + let _ = db_mgr::drop_database(&tm_pool, &db_name).await; + } } -- 2.43.0 From 732f5f64bcc58334e832aff6882fc7ec83591204 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 19:32:49 -0400 Subject: [PATCH 12/24] Reorg test --- src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9942006..8c2299e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -58,6 +58,9 @@ mod token_stuff { pub const TOKENTYPE: &str = "JWT"; pub const KEY_ENV: &str = "SECRET_KEY"; + pub const MESSAGE: &str = "Something random"; + pub const ISSUER: &str = "icarus_auth"; + pub const AUDIENCE: &str = "icarus"; pub fn get_key() -> Result { dotenvy::dotenv().ok(); @@ -70,7 +73,9 @@ mod token_stuff { header.set_token_type(TOKENTYPE); let mut payload = JwtPayload::new(); - payload.set_subject("Something random"); + payload.set_subject(MESSAGE); + payload.set_issuer(ISSUER); + payload.set_audience(vec![AUDIENCE]); let key: String = if provided_key.is_empty() { get_key().unwrap() -- 2.43.0 From 697dff62a88fbc54e19272fdddfb2bf7d696a83f Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 19:41:33 -0400 Subject: [PATCH 13/24] Test cleanup --- src/main.rs | 62 ++++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0dcfcb6..2dfe642 100644 --- a/src/main.rs +++ b/src/main.rs @@ -133,6 +133,30 @@ mod tests { } } + fn get_test_register_request() -> icarus_auth::callers::register::request::Request { + icarus_auth::callers::register::request::Request { + username: String::from("somethingsss"), + password: String::from("Raindown!"), + email: String::from("dev@null.com"), + phone: String::from("1234567890"), + firstname: String::from("Bob"), + lastname: String::from("Smith"), + } + } + + fn get_test_register_payload( + usr: &icarus_auth::callers::register::request::Request, + ) -> serde_json::Value { + json!({ + "username": &usr.username, + "password": &usr.password, + "email": &usr.email, + "phone": &usr.phone, + "firstname": &usr.firstname, + "lastname": &usr.lastname, + }) + } + #[tokio::test] async fn test_hello_world() { let app = init::app().await; @@ -176,23 +200,8 @@ mod tests { let app = init::routes().await.layer(axum::Extension(pool)); - let usr = icarus_auth::callers::register::request::Request { - username: String::from("somethingsss"), - password: String::from("Raindown!"), - email: String::from("dev@null.com"), - phone: String::from("1234567890"), - firstname: String::from("Bob"), - lastname: String::from("Smith"), - }; - - let payload = json!({ - "username": &usr.username, - "password": &usr.password, - "email": &usr.email, - "phone": &usr.phone, - "firstname": &usr.firstname, - "lastname": &usr.lastname, - }); + let usr = get_test_register_request(); + let payload = get_test_register_payload(&usr); let response = app .oneshot( @@ -258,23 +267,8 @@ mod tests { let app = init::routes().await.layer(axum::Extension(pool)); - let usr = icarus_auth::callers::register::request::Request { - username: String::from("somethingsss"), - password: String::from("Raindown!"), - email: String::from("dev@null.com"), - phone: String::from("1234567890"), - firstname: String::from("Bob"), - lastname: String::from("Smith"), - }; - - let payload = json!({ - "username": &usr.username, - "password": &usr.password, - "email": &usr.email, - "phone": &usr.phone, - "firstname": &usr.firstname, - "lastname": &usr.lastname, - }); + let usr = get_test_register_request(); + let payload = get_test_register_payload(&usr); let response = app .clone() -- 2.43.0 From 16e1eb132bcbd52ceec441ec6de89cdda58185b2 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 19:57:52 -0400 Subject: [PATCH 14/24] Code refactor --- src/callers/login.rs | 93 +++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index a2e8f79..fa21b5e 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -28,6 +28,16 @@ pub mod endpoint { use super::request; use super::response; + async fn not_found(message: &String) -> (StatusCode, Json) { + ( + StatusCode::NOT_FOUND, + Json(response::Response { + message: message.clone(), + data: Vec::new(), + }), + ) + } + pub async fn login( axum::Extension(pool): axum::Extension, Json(payload): Json, @@ -37,53 +47,56 @@ pub mod endpoint { password: payload.password, ..Default::default() }; - // usr.username = payload.username; - // usr.password = payload.password; // Check if user exists - let user_exists = repo::user::exists(&pool, &usr.username).await.unwrap(); - if !user_exists { - // End - } + match repo::user::exists(&pool, &usr.username).await { + Ok(exists) => { + if !exists { + return not_found(&"Not Found".to_string()).await; + } + } + Err(err) => { + return not_found(&err.to_string()).await; + } + }; + + // End let user = repo::user::get(&pool, &usr.username).await.unwrap(); let salt = repo::salt::get(&pool, &user.salt_id).await.unwrap(); - let salt_literal = salt.salt.clone(); - let salt_str = hashing::get_salt(&salt_literal).unwrap(); + let salt_str = hashing::get_salt(&salt.salt).unwrap(); // Check if password is correct - let hash_password = hashing::hash_password(&usr.password, &salt_str).unwrap(); - if hashing::verify_password(&usr.password, hash_password.clone()).unwrap() { - println!("Do work"); - } + match hashing::hash_password(&usr.password, &salt_str) { + Ok(hash_password) => { + if hashing::verify_password(&usr.password, hash_password.clone()).unwrap() { + // Create token + let key = token_stuff::get_key().unwrap(); + let token_literal = token_stuff::create_token(&key).unwrap(); - // Create token - let key = token_stuff::get_key().unwrap(); - let token_literal = token_stuff::create_token(&key).unwrap(); - - let result = token_stuff::verify_token(&key, &token_literal); - - if result { - ( - StatusCode::OK, - Json(response::Response { - message: String::from("Not implemented"), - data: vec![icarus_models::login_result::LoginResult { - id: user.id, - username: user.username, - token: token_literal, - token_type: String::from("JWT"), - expiration: -1, - }], - }), - ) - } else { - ( - StatusCode::BAD_REQUEST, - Json(response::Response { - message: String::from("Not implemented"), - data: vec![icarus_models::login_result::LoginResult::default()], - }), - ) + if token_stuff::verify_token(&key, &token_literal) { + ( + StatusCode::OK, + Json(response::Response { + message: String::from("Successful"), + data: vec![icarus_models::login_result::LoginResult { + id: user.id, + username: user.username, + token: token_literal, + token_type: String::from(token_stuff::TOKENTYPE), + expiration: -1, + }], + }), + ) + } else { + return not_found(&"Could not verify password".to_string()).await; + } + } else { + return not_found(&"Error Hashing".to_string()).await; + } + } + Err(err) => { + return not_found(&err.to_string()).await; + } } } } -- 2.43.0 From 784ffbb335ccb24eb9aa4a0197cee1f8d8988211 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:06:54 -0400 Subject: [PATCH 15/24] Refactor --- src/lib.rs | 70 +----------------------------------------------------- 1 file changed, 1 insertion(+), 69 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8c2299e..9ab8f4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ pub mod callers; pub mod config; pub mod hashing; pub mod repo; +pub mod token_stuff; pub mod keys { pub const DBURL: &str = "DATABASE_URL"; @@ -48,72 +49,3 @@ pub mod db { .expect("Failed to run migrations"); } } - -mod token_stuff { - use josekit::{ - self, - jws::{JwsHeader, alg::hmac::HmacJwsAlgorithm::Hs256}, - jwt::{self, JwtPayload}, - }; - - pub const TOKENTYPE: &str = "JWT"; - pub const KEY_ENV: &str = "SECRET_KEY"; - pub const MESSAGE: &str = "Something random"; - pub const ISSUER: &str = "icarus_auth"; - pub const AUDIENCE: &str = "icarus"; - - pub fn get_key() -> Result { - dotenvy::dotenv().ok(); - let key = std::env::var(KEY_ENV).expect("SECRET_KEY_NOT_FOUND"); - Ok(key) - } - - pub fn create_token(provided_key: &String) -> Result { - let mut header = JwsHeader::new(); - header.set_token_type(TOKENTYPE); - - let mut payload = JwtPayload::new(); - payload.set_subject(MESSAGE); - payload.set_issuer(ISSUER); - payload.set_audience(vec![AUDIENCE]); - - let key: String = if provided_key.is_empty() { - get_key().unwrap() - } else { - provided_key.to_owned() - }; - - let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); - let jwt = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(); - - Ok(jwt) - } - - pub fn verify_token(key: &String, token: &String) -> bool { - let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap(); - let (payload, _header) = jwt::decode_with_verifier(token, &ver).unwrap(); - match payload.subject() { - Some(_sub) => true, - None => false, - } - } -} - -#[cfg(test)] -mod tests { - use crate::token_stuff; - - #[test] - fn test_tokenize() { - let special_key = token_stuff::get_key().unwrap(); - match token_stuff::create_token(&special_key) { - Ok(token) => { - let result = token_stuff::verify_token(&special_key, &token); - assert!(result, "Token not verified"); - } - Err(err) => { - assert!(false, "Error: {:?}", err.to_string()); - } - }; - } -} -- 2.43.0 From 3b2a9a0cd7433ae5edbf25eea6a50e7b5922646a Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:11:14 -0400 Subject: [PATCH 16/24] Code refactoriing --- src/callers/login.rs | 10 +++---- src/token_stuff/mod.rs | 67 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 src/token_stuff/mod.rs diff --git a/src/callers/login.rs b/src/callers/login.rs index fa21b5e..791f46a 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -28,11 +28,11 @@ pub mod endpoint { use super::request; use super::response; - async fn not_found(message: &String) -> (StatusCode, Json) { + async fn not_found(message: &str) -> (StatusCode, Json) { ( StatusCode::NOT_FOUND, Json(response::Response { - message: message.clone(), + message: String::from(message), data: Vec::new(), }), ) @@ -52,7 +52,7 @@ pub mod endpoint { match repo::user::exists(&pool, &usr.username).await { Ok(exists) => { if !exists { - return not_found(&"Not Found".to_string()).await; + return not_found("Not Found").await; } } Err(err) => { @@ -88,10 +88,10 @@ pub mod endpoint { }), ) } else { - return not_found(&"Could not verify password".to_string()).await; + return not_found("Could not verify password").await; } } else { - return not_found(&"Error Hashing".to_string()).await; + return not_found("Error Hashing").await; } } Err(err) => { diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs new file mode 100644 index 0000000..996ade9 --- /dev/null +++ b/src/token_stuff/mod.rs @@ -0,0 +1,67 @@ +use josekit::{ + self, + jws::{JwsHeader, alg::hmac::HmacJwsAlgorithm::Hs256}, + jwt::{self, JwtPayload}, +}; + +pub const TOKENTYPE: &str = "JWT"; +pub const KEY_ENV: &str = "SECRET_KEY"; +pub const MESSAGE: &str = "Something random"; +pub const ISSUER: &str = "icarus_auth"; +pub const AUDIENCE: &str = "icarus"; + +pub fn get_key() -> Result { + dotenvy::dotenv().ok(); + let key = std::env::var(KEY_ENV).expect("SECRET_KEY_NOT_FOUND"); + Ok(key) +} + +pub fn create_token(provided_key: &String) -> Result { + let mut header = JwsHeader::new(); + header.set_token_type(TOKENTYPE); + + let mut payload = JwtPayload::new(); + payload.set_subject(MESSAGE); + payload.set_issuer(ISSUER); + payload.set_audience(vec![AUDIENCE]); + + let key: String = if provided_key.is_empty() { + get_key().unwrap() + } else { + provided_key.to_owned() + }; + + let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); + let jwt = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(); + + Ok(jwt) +} + +pub fn verify_token(key: &String, token: &String) -> bool { + let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap(); + let (payload, _header) = jwt::decode_with_verifier(token, &ver).unwrap(); + match payload.subject() { + Some(_sub) => true, + None => false, + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_tokenize() { + let special_key = get_key().unwrap(); + match create_token(&special_key) { + Ok(token) => { + let result = verify_token(&special_key, &token); + assert!(result, "Token not verified"); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + }; + } +} -- 2.43.0 From 14973dfc084d35df729f2692b76c39fba297a229 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:13:34 -0400 Subject: [PATCH 17/24] Removed comment --- src/callers/login.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 791f46a..5ced23b 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -60,7 +60,6 @@ pub mod endpoint { } }; - // End let user = repo::user::get(&pool, &usr.username).await.unwrap(); let salt = repo::salt::get(&pool, &user.salt_id).await.unwrap(); let salt_str = hashing::get_salt(&salt.salt).unwrap(); -- 2.43.0 From 40f8cd770de00fe9e13624b4b4d0b349ea4e3151 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:25:02 -0400 Subject: [PATCH 18/24] Added expiration claim to token --- src/token_stuff/mod.rs | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs index 996ade9..66b4045 100644 --- a/src/token_stuff/mod.rs +++ b/src/token_stuff/mod.rs @@ -4,6 +4,8 @@ use josekit::{ jwt::{self, JwtPayload}, }; +use time; + pub const TOKENTYPE: &str = "JWT"; pub const KEY_ENV: &str = "SECRET_KEY"; pub const MESSAGE: &str = "Something random"; @@ -16,6 +18,13 @@ pub fn get_key() -> Result { Ok(key) } +pub fn get_expiration() -> time::Result { + let now = time::OffsetDateTime::now_utc(); + let epoch = time::OffsetDateTime::UNIX_EPOCH; + let since_the_epoch = now - epoch; + Ok(since_the_epoch) +} + pub fn create_token(provided_key: &String) -> Result { let mut header = JwsHeader::new(); header.set_token_type(TOKENTYPE); @@ -24,6 +33,16 @@ pub fn create_token(provided_key: &String) -> Result payload.set_subject(MESSAGE); payload.set_issuer(ISSUER); payload.set_audience(vec![AUDIENCE]); + match get_expiration() { + Ok(duration) => { + let expire = duration.whole_seconds(); + let _ = payload.set_claim( + "expiration", + Some(serde_json::to_value(expire.to_string()).unwrap()), + ); + } + Err(_) => {} + }; let key: String = if provided_key.is_empty() { get_key().unwrap() @@ -32,9 +51,7 @@ pub fn create_token(provided_key: &String) -> Result }; let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); - let jwt = josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(); - - Ok(jwt) + Ok(josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap()) } pub fn verify_token(key: &String, token: &String) -> bool { -- 2.43.0 From e36add777c63417fd85302088dabbaad5a06e7e2 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:42:10 -0400 Subject: [PATCH 19/24] Added expiration pop --- src/callers/login.rs | 4 ++-- src/token_stuff/mod.rs | 28 +++++++++++++++------------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 5ced23b..152c837 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -70,7 +70,7 @@ pub mod endpoint { if hashing::verify_password(&usr.password, hash_password.clone()).unwrap() { // Create token let key = token_stuff::get_key().unwrap(); - let token_literal = token_stuff::create_token(&key).unwrap(); + let (token_literal, duration) = token_stuff::create_token(&key).unwrap(); if token_stuff::verify_token(&key, &token_literal) { ( @@ -82,7 +82,7 @@ pub mod endpoint { username: user.username, token: token_literal, token_type: String::from(token_stuff::TOKENTYPE), - expiration: -1, + expiration: duration as i32, }], }), ) diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs index 66b4045..66f78b3 100644 --- a/src/token_stuff/mod.rs +++ b/src/token_stuff/mod.rs @@ -25,7 +25,7 @@ pub fn get_expiration() -> time::Result { Ok(since_the_epoch) } -pub fn create_token(provided_key: &String) -> Result { +pub fn create_token(provided_key: &String) -> Result<(String, i64), josekit::JoseError> { let mut header = JwsHeader::new(); header.set_token_type(TOKENTYPE); @@ -40,18 +40,20 @@ pub fn create_token(provided_key: &String) -> Result "expiration", Some(serde_json::to_value(expire.to_string()).unwrap()), ); + + let key: String = if provided_key.is_empty() { + get_key().unwrap() + } else { + provided_key.to_owned() + }; + + let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); + Ok((josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(), duration.whole_seconds())) } - Err(_) => {} - }; - - let key: String = if provided_key.is_empty() { - get_key().unwrap() - } else { - provided_key.to_owned() - }; - - let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); - Ok(josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap()) + Err(e) => { + Err(josekit::JoseError::InvalidClaim(e.into())) + } + } } pub fn verify_token(key: &String, token: &String) -> bool { @@ -72,7 +74,7 @@ mod tests { fn test_tokenize() { let special_key = get_key().unwrap(); match create_token(&special_key) { - Ok(token) => { + Ok((token, _duration)) => { let result = verify_token(&special_key, &token); assert!(result, "Token not verified"); } -- 2.43.0 From 7aada47ce40c73a8609173434891f610f68db9e3 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:49:05 -0400 Subject: [PATCH 20/24] Code refactoring --- src/token_stuff/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs index 66f78b3..2771dec 100644 --- a/src/token_stuff/mod.rs +++ b/src/token_stuff/mod.rs @@ -48,11 +48,12 @@ pub fn create_token(provided_key: &String) -> Result<(String, i64), josekit::Jos }; let signer = Hs256.signer_from_bytes(key.as_bytes()).unwrap(); - Ok((josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(), duration.whole_seconds())) - } - Err(e) => { - Err(josekit::JoseError::InvalidClaim(e.into())) + Ok(( + josekit::jwt::encode_with_signer(&payload, &header, &signer).unwrap(), + duration.whole_seconds(), + )) } + Err(e) => Err(josekit::JoseError::InvalidClaim(e.into())), } } -- 2.43.0 From d5f9960b6dabf71dc4557d63952bd9c766cfa208 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:52:49 -0400 Subject: [PATCH 21/24] Updated icarus_model --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a649325..ac054ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x versio rand = { version = "0.9" } time = { version = "0.3.41", features = ["macros", "serde"] } josekit = { version = "0.10.1" } -icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.0" } +icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.1-devel-6467521a02-111" } [dev-dependencies] http-body-util = { version = "0.1.3" } -- 2.43.0 From c77a7a1453b7241042d1c16180fbfcd8ce10f713 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 20:53:03 -0400 Subject: [PATCH 22/24] Change type of field assignment --- src/callers/login.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callers/login.rs b/src/callers/login.rs index 152c837..66286ee 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -82,7 +82,7 @@ pub mod endpoint { username: user.username, token: token_literal, token_type: String::from(token_stuff::TOKENTYPE), - expiration: duration as i32, + expiration: duration, }], }), ) -- 2.43.0 From 557732134604a4569b075e820b31785d5dc63436 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 21:08:29 -0400 Subject: [PATCH 23/24] Version bump --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ac054ea..055c23d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icarus_auth" -version = "0.2.0" +version = "0.3.0" edition = "2024" rust-version = "1.86" -- 2.43.0 From fc4f34dc567f8fd1bf44c4ca57205ae85b0512b9 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 6 Apr 2025 21:18:18 -0400 Subject: [PATCH 24/24] Upgrading icarus_model to v0.4.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 055c23d..701c694 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x versio rand = { version = "0.9" } time = { version = "0.3.41", features = ["macros", "serde"] } josekit = { version = "0.10.1" } -icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.1-devel-6467521a02-111" } +icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.1" } [dev-dependencies] http-body-util = { version = "0.1.3" } -- 2.43.0