From 99390ce8b76a63f4928b05852a9697397a9f5766 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 3 Aug 2025 23:09:50 +0000 Subject: [PATCH] tsk-50: Create Special endpoint for services to obtain a token (#53) Reviewed-on: https://git.kundeng.us/phoenix/icarus_auth/pulls/53 Co-authored-by: phoenix Co-committed-by: phoenix --- .env.docker.sample | 1 + .env.sample | 1 + Cargo.lock | 6 +- Cargo.toml | 4 +- migrations/20250402221858_init_migrate.sql | 6 ++ migrations/20250802185652_passphrase_data.sql | 2 + src/callers/login.rs | 53 +++++++++ src/callers/mod.rs | 1 + src/main.rs | 102 ++++++++++++++---- src/repo/mod.rs | 29 +++++ src/token_stuff/mod.rs | 9 ++ 11 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 migrations/20250802185652_passphrase_data.sql diff --git a/.env.docker.sample b/.env.docker.sample index 5c0ad33..ffde663 100644 --- a/.env.docker.sample +++ b/.env.docker.sample @@ -1,4 +1,5 @@ SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h +SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH POSTGRES_AUTH_USER=icarus_op POSTGRES_AUTH_PASSWORD=password POSTGRES_AUTH_DB=icarus_auth_db diff --git a/.env.sample b/.env.sample index b556c08..c00c477 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,5 @@ SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h +SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH POSTGRES_AUTH_USER=icarus_op_test POSTGRES_AUTH_PASSWORD=password POSTGRES_AUTH_DB=icarus_auth_test_db diff --git a/Cargo.lock b/Cargo.lock index 9370a38..ba72c60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -728,7 +728,7 @@ dependencies = [ [[package]] name = "icarus_auth" -version = "0.4.1" +version = "0.4.2" dependencies = [ "argon2", "axum", @@ -752,8 +752,8 @@ dependencies = [ [[package]] name = "icarus_envy" -version = "0.3.0" -source = "git+ssh://git@git.kundeng.us/phoenix/icarus_envy.git?tag=v0.3.0-devel-d73fba9899-006#d73fba9899372b0655a90cb426645930135152da" +version = "0.3.1" +source = "git+ssh://git@git.kundeng.us/phoenix/icarus_envy.git?tag=v0.3.1-main-3cd42dab6b-006#3cd42dab6b2503609883f5f57ad3508755c34a2e" dependencies = [ "const_format", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index 71caf6d..caa4c42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "icarus_auth" -version = "0.4.1" +version = "0.4.2" edition = "2024" rust-version = "1.88" @@ -19,7 +19,7 @@ rand = { version = "0.9.1" } time = { version = "0.3.41", features = ["macros", "serde"] } josekit = { version = "0.10.3" } icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.5.4-devel-1e95822b5a-111" } -icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.0-devel-d73fba9899-006" } +icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.1-main-3cd42dab6b-006" } [dev-dependencies] http-body-util = { version = "0.1.3" } diff --git a/migrations/20250402221858_init_migrate.sql b/migrations/20250402221858_init_migrate.sql index 8fe068c..0ad2121 100644 --- a/migrations/20250402221858_init_migrate.sql +++ b/migrations/20250402221858_init_migrate.sql @@ -20,3 +20,9 @@ CREATE TABLE IF NOT EXISTS "salt" ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), salt TEXT NOT NULL ); + +CREATE TABLE IF NOT EXISTS "passphrase" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + passphrase TEXT NOT NULL, + date_created TIMESTAMPTZ NOT NULL DEFAULT NOW() +); diff --git a/migrations/20250802185652_passphrase_data.sql b/migrations/20250802185652_passphrase_data.sql new file mode 100644 index 0000000..e18d2a1 --- /dev/null +++ b/migrations/20250802185652_passphrase_data.sql @@ -0,0 +1,2 @@ +-- Add migration script here +INSERT INTO "passphrase" (passphrase) VALUES('iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH'); diff --git a/src/callers/login.rs b/src/callers/login.rs index 9476df5..6d5fd20 100644 --- a/src/callers/login.rs +++ b/src/callers/login.rs @@ -6,6 +6,13 @@ pub mod request { pub username: String, pub password: String, } + + pub mod service_login { + #[derive(Debug, serde::Deserialize, serde::Serialize)] + pub struct Request { + pub passphrase: String, + } + } } pub mod response { @@ -16,6 +23,14 @@ pub mod response { pub message: String, pub data: Vec, } + + pub mod service_login { + #[derive(Debug, Default, serde::Deserialize, serde::Serialize)] + pub struct Response { + pub message: String, + pub data: Vec, + } + } } pub mod endpoint { @@ -79,4 +94,42 @@ pub mod endpoint { } } } + + pub async fn service_login( + axum::Extension(pool): axum::Extension, + axum::Json(payload): axum::Json, + ) -> ( + axum::http::StatusCode, + axum::Json, + ) { + let mut response = response::service_login::Response::default(); + + match repo::service::valid_passphrase(&pool, &payload.passphrase).await { + Ok((id, _passphrase, _date_created)) => { + let key = icarus_envy::environment::get_secret_key().await; + let (token_literal, duration) = token_stuff::create_service_token(&key).unwrap(); + + if token_stuff::verify_token(&key, &token_literal) { + let login_result = icarus_models::login_result::LoginResult { + id, + username: String::from("service"), + token: token_literal, + token_type: String::from(icarus_models::token::TOKEN_TYPE), + expiration: duration, + }; + + response.data.push(login_result); + response.message = String::from("Successful"); + + (axum::http::StatusCode::OK, axum::Json(response)) + } else { + (axum::http::StatusCode::OK, axum::Json(response)) + } + } + Err(err) => { + response.message = err.to_string(); + (axum::http::StatusCode::BAD_REQUEST, axum::Json(response)) + } + } + } } diff --git a/src/callers/mod.rs b/src/callers/mod.rs index ab9f31e..4fdaf82 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -7,4 +7,5 @@ pub mod endpoints { pub const REGISTER: &str = "/api/v2/register"; pub const DBTEST: &str = "/api/v2/test/db"; pub const LOGIN: &str = "/api/v2/login"; + pub const SERVICE_LOGIN: &str = "/api/v2/service/login"; } diff --git a/src/main.rs b/src/main.rs index f12ff7c..5d8a4ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,10 @@ mod init { callers::endpoints::LOGIN, post(callers::login::endpoint::login), ) + .route( + callers::endpoints::SERVICE_LOGIN, + post(callers::login::endpoint::service_login), + ) } pub async fn app() -> Router { @@ -154,6 +158,25 @@ mod tests { }) } + pub mod requests { + use tower::ServiceExt; // for `call`, `oneshot`, and `ready` + + pub async fn register( + app: &axum::Router, + usr: &icarus_auth::callers::register::request::Request, + ) -> Result { + let payload = super::get_test_register_payload(&usr); + let req = axum::http::Request::builder() + .method(axum::http::Method::POST) + .uri(crate::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 + } + } + #[tokio::test] async fn test_hello_world() { let app = init::app().await; @@ -198,18 +221,8 @@ mod tests { let app = init::routes().await.layer(axum::Extension(pool)); let usr = get_test_register_request(); - let payload = get_test_register_payload(&usr); - let response = app - .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; + let response = requests::register(&app, &usr).await; match response { Ok(resp) => { @@ -265,19 +278,8 @@ mod tests { let app = init::routes().await.layer(axum::Extension(pool)); let usr = get_test_register_request(); - let payload = get_test_register_payload(&usr); - 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; + let response = requests::register(&app, &usr).await; match response { Ok(resp) => { @@ -341,4 +343,58 @@ mod tests { let _ = db_mgr::drop_database(&tm_pool, &db_name).await; } + + #[tokio::test] + async fn test_service_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 passphrase = + String::from("iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH"); + let payload = serde_json::json!({ + "passphrase": passphrase + }); + + match app + .oneshot( + Request::builder() + .method(axum::http::Method::POST) + .uri(callers::endpoints::SERVICE_LOGIN) + .header(axum::http::header::CONTENT_TYPE, "application/json") + .body(Body::from(payload.to_string())) + .unwrap(), + ) + .await + { + Ok(response) => { + assert_eq!(StatusCode::OK, response.status(), "Status is not right"); + let body = axum::body::to_bytes(response.into_body(), usize::MAX) + .await + .unwrap(); + let parsed_body: callers::login::response::service_login::Response = + serde_json::from_slice(&body).unwrap(); + let _login_result = &parsed_body.data[0]; + } + Err(err) => { + assert!(false, "Error: {err:?}"); + } + } + + let _ = db_mgr::drop_database(&tm_pool, &db_name).await; + } } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 52e9e3c..db62bb4 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -195,3 +195,32 @@ pub mod salt { } } } + +pub mod service { + use sqlx::Row; + + pub async fn valid_passphrase( + pool: &sqlx::PgPool, + passphrase: &String, + ) -> Result<(uuid::Uuid, String, time::OffsetDateTime), sqlx::Error> { + let result = sqlx::query( + r#" + SELECT * FROM "passphrase" WHERE passphrase = $1 + "#, + ) + .bind(passphrase) + .fetch_one(pool) + .await; + + match result { + Ok(row) => { + let id: uuid::Uuid = row.try_get("id")?; + let passphrase: String = row.try_get("passphrase")?; + let date_created: Option = row.try_get("date_created")?; + + Ok((id, passphrase, date_created.unwrap())) + } + Err(err) => Err(err), + } + } +} diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs index ea2f412..241216a 100644 --- a/src/token_stuff/mod.rs +++ b/src/token_stuff/mod.rs @@ -29,6 +29,15 @@ pub fn create_token(provided_key: &String) -> Result<(String, i64), josekit::Jos icarus_models::token::create_token(provided_key, &resource, time::Duration::hours(4)) } +pub fn create_service_token(provided: &String) -> Result<(String, i64), josekit::JoseError> { + let resource = icarus_models::token::TokenResource { + message: String::from("Service random"), + issuer: String::from(ISSUER), + audiences: vec![String::from(AUDIENCE)], + }; + icarus_models::token::create_token(provided, &resource, time::Duration::hours(1)) +} + 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();