From 3fc7581736f1613b60433528ae671fc1eee07e93 Mon Sep 17 00:00:00 2001 From: "Edward J. Jinotti" Date: Tue, 21 Jul 2026 12:41:18 -0400 Subject: [PATCH 1/5] gpt: fix v1 outbound header --- Cargo.toml | 2 +- src/protocol_test_suite.rs | 42 ++++++++++++++++++++++++++++++++++++-- src/sign_outgoing.rs | 14 ++++++++++--- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d8a749..7b93fdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mauth-client" -version = "0.7.1" +version = "0.7.2" authors = ["Mason Gup "] edition = "2024" rust-version = "1.88" diff --git a/src/protocol_test_suite.rs b/src/protocol_test_suite.rs index c9413d0..933f715 100644 --- a/src/protocol_test_suite.rs +++ b/src/protocol_test_suite.rs @@ -1,4 +1,5 @@ use crate::{MAuthInfo, config::ConfigFileSection}; +use mauth_core::verifier::Verifier; use reqwest::{Method, Request}; use serde::Deserialize; use tokio::fs; @@ -14,10 +15,11 @@ struct TestSignConfig { const BASE_PATH: &str = "mauth-protocol-test-suite/protocols/MWSV2/"; -async fn setup_mauth_info() -> (MAuthInfo, u64) { +async fn setup_mauth_info() -> (MAuthInfo, String, u64) { let config_path = Path::new("mauth-protocol-test-suite/signing-config.json"); let sign_config: TestSignConfig = serde_json::from_slice(&fs::read(config_path).await.unwrap()).unwrap(); + let app_uuid = sign_config.app_uuid.clone(); let mock_config_section = ConfigFileSection { app_uuid: sign_config.app_uuid, mauth_baseurl: "https://www.example.com/".to_string(), @@ -32,12 +34,13 @@ async fn setup_mauth_info() -> (MAuthInfo, u64) { }; ( MAuthInfo::from_config_section(&mock_config_section).unwrap(), + app_uuid, sign_config.request_time, ) } async fn test_generate_headers(file_name: String) { - let (mauth_info, req_time) = setup_mauth_info().await; + let (mauth_info, _, req_time) = setup_mauth_info().await; let mut sig_file_path = PathBuf::from(&BASE_PATH); sig_file_path.push(format!("{name}/{name}.sig", name = &file_name)); @@ -71,4 +74,39 @@ async fn test_generate_headers(file_name: String) { assert_eq!(expected_sig, sig_header); } +#[tokio::test] +async fn sign_request_v1_sets_protocol_compliant_headers() { + let (mauth_info, app_uuid, _) = setup_mauth_info().await; + let mut request = Request::new(Method::GET, url::Url::parse("http://www.a.com/").unwrap()); + mauth_info.sign_request_v1(&mut request).unwrap(); + + let headers = request.headers(); + let timestamp = headers.get("X-MWS-Time").unwrap().to_str().unwrap(); + let auth_header = headers + .get("X-MWS-Authentication") + .unwrap() + .to_str() + .unwrap(); + let signature = auth_header + .strip_prefix(&format!("MWS {app_uuid}:")) + .expect("v1 authentication header must contain its protocol token and app UUID"); + assert!(!signature.is_empty()); + + let public_key = fs::read_to_string("mauth-protocol-test-suite/signing-params/rsa-key-pub") + .await + .unwrap(); + Verifier::new(app_uuid, public_key) + .unwrap() + .verify_signature( + 1, + request.method().as_str(), + request.url().path(), + request.url().query().unwrap_or(""), + &[], + timestamp, + signature, + ) + .unwrap(); +} + include!(concat!(env!("OUT_DIR"), "/protocol_tests.rs")); diff --git a/src/sign_outgoing.rs b/src/sign_outgoing.rs index 331e81c..f917549 100644 --- a/src/sign_outgoing.rs +++ b/src/sign_outgoing.rs @@ -86,11 +86,19 @@ impl MAuthInfo { timestamp_str.clone(), )?; - let headers = req.headers_mut(); - headers.insert("X-MWS-Time", HeaderValue::from_str(×tamp_str).unwrap()); - headers.insert("X-MWS-Authentication", HeaderValue::from_str(&sig).unwrap()); + self.set_headers_v1(req, sig, ×tamp_str); Ok(()) } + + pub(crate) fn set_headers_v1(&self, req: &mut Request, signature: String, timestamp_str: &str) { + let sig_head_str = format!("MWS {}:{}", self.app_id, signature); + let headers = req.headers_mut(); + headers.insert("X-MWS-Time", HeaderValue::from_str(timestamp_str).unwrap()); + headers.insert( + "X-MWS-Authentication", + HeaderValue::from_str(&sig_head_str).unwrap(), + ); + } } /// All of the errors that can take place while attempting to sign a request From be209abb6c7ea8d95f4b2190a4a2e090b2f4af39 Mon Sep 17 00:00:00 2001 From: "Edward J. Jinotti" Date: Tue, 21 Jul 2026 13:49:01 -0400 Subject: [PATCH 2/5] gpt: address some claude review nits --- src/protocol_test_suite.rs | 9 ++++----- src/sign_outgoing.rs | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/protocol_test_suite.rs b/src/protocol_test_suite.rs index 933f715..86f075c 100644 --- a/src/protocol_test_suite.rs +++ b/src/protocol_test_suite.rs @@ -15,11 +15,10 @@ struct TestSignConfig { const BASE_PATH: &str = "mauth-protocol-test-suite/protocols/MWSV2/"; -async fn setup_mauth_info() -> (MAuthInfo, String, u64) { +async fn setup_mauth_info() -> (MAuthInfo, u64) { let config_path = Path::new("mauth-protocol-test-suite/signing-config.json"); let sign_config: TestSignConfig = serde_json::from_slice(&fs::read(config_path).await.unwrap()).unwrap(); - let app_uuid = sign_config.app_uuid.clone(); let mock_config_section = ConfigFileSection { app_uuid: sign_config.app_uuid, mauth_baseurl: "https://www.example.com/".to_string(), @@ -34,13 +33,12 @@ async fn setup_mauth_info() -> (MAuthInfo, String, u64) { }; ( MAuthInfo::from_config_section(&mock_config_section).unwrap(), - app_uuid, sign_config.request_time, ) } async fn test_generate_headers(file_name: String) { - let (mauth_info, _, req_time) = setup_mauth_info().await; + let (mauth_info, req_time) = setup_mauth_info().await; let mut sig_file_path = PathBuf::from(&BASE_PATH); sig_file_path.push(format!("{name}/{name}.sig", name = &file_name)); @@ -76,7 +74,8 @@ async fn test_generate_headers(file_name: String) { #[tokio::test] async fn sign_request_v1_sets_protocol_compliant_headers() { - let (mauth_info, app_uuid, _) = setup_mauth_info().await; + let (mauth_info, _) = setup_mauth_info().await; + let app_uuid = mauth_info.app_id; let mut request = Request::new(Method::GET, url::Url::parse("http://www.a.com/").unwrap()); mauth_info.sign_request_v1(&mut request).unwrap(); diff --git a/src/sign_outgoing.rs b/src/sign_outgoing.rs index f917549..dcbacc8 100644 --- a/src/sign_outgoing.rs +++ b/src/sign_outgoing.rs @@ -90,7 +90,7 @@ impl MAuthInfo { Ok(()) } - pub(crate) fn set_headers_v1(&self, req: &mut Request, signature: String, timestamp_str: &str) { + fn set_headers_v1(&self, req: &mut Request, signature: String, timestamp_str: &str) { let sig_head_str = format!("MWS {}:{}", self.app_id, signature); let headers = req.headers_mut(); headers.insert("X-MWS-Time", HeaderValue::from_str(timestamp_str).unwrap()); From aa5ebb5ff4d02c6654ae09db596f0d460be94802 Mon Sep 17 00:00:00 2001 From: "Edward J. Jinotti" Date: Tue, 21 Jul 2026 13:57:49 -0400 Subject: [PATCH 3/5] add factbook --- factbook.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 factbook.yaml diff --git a/factbook.yaml b/factbook.yaml new file mode 100644 index 0000000..574cc67 --- /dev/null +++ b/factbook.yaml @@ -0,0 +1,20 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +spec: + type: library + lifecycle: production + owner: ae@mdsol.com + experience: enablement +metadata: + json_schema: https://github.com/mdsol/platform-standards/tree/main/schemas/v1alpha1.schema.json + name: mauth-core + title: MAuth Core + description: A library to generate and verify MAuth signatures + teams: + - name: Architecture Enablement + number: 119 + people: + - role: technical owner + email: ykitamura@mdsol.com + annotations: + product: N/A From 45539ad2641fc6da88822aa2a4b2b00d6975c820 Mon Sep 17 00:00:00 2001 From: "Edward J. Jinotti" Date: Tue, 21 Jul 2026 14:01:07 -0400 Subject: [PATCH 4/5] gpt: clippy fixes --- build.rs | 6 +++--- src/config.rs | 2 +- src/protocol_test_suite.rs | 4 ++-- src/sign_outgoing.rs | 2 +- src/validate_incoming.rs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/build.rs b/build.rs index bfc4b21..691dcfb 100644 --- a/build.rs +++ b/build.rs @@ -14,7 +14,7 @@ fn main() { r_path.file_name().unwrap().to_str().unwrap().to_string(), ) }) - .filter(|(path, name)| path.join(format!("{}.sts", &name)).exists()) + .filter(|(path, name)| path.join(format!("{}.sts", name)).exists()) .map(|(_, name)| name) .collect(); @@ -30,8 +30,8 @@ async fn {formatted_name}_generate_headers() {{ test_generate_headers("{name}".to_string()).await; }} "#, - formatted_name = &formatted_name, - name = &name + formatted_name = formatted_name, + name = name )); } fs::write(Path::new(&out_dir).join("protocol_tests.rs"), &code_str).unwrap(); diff --git a/src/config.rs b/src/config.rs index 7a4fbd4..275291b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -38,7 +38,7 @@ impl MAuthInfo { pub fn from_config_section(section: &ConfigFileSection) -> Result { let full_uri: Url = format!( "{}/mauth/{}/security_tokens/", - §ion.mauth_baseurl, §ion.mauth_api_version + section.mauth_baseurl, section.mauth_api_version ) .parse()?; diff --git a/src/protocol_test_suite.rs b/src/protocol_test_suite.rs index 86f075c..dda691c 100644 --- a/src/protocol_test_suite.rs +++ b/src/protocol_test_suite.rs @@ -41,11 +41,11 @@ async fn test_generate_headers(file_name: String) { let (mauth_info, req_time) = setup_mauth_info().await; let mut sig_file_path = PathBuf::from(&BASE_PATH); - sig_file_path.push(format!("{name}/{name}.sig", name = &file_name)); + sig_file_path.push(format!("{name}/{name}.sig", name = file_name)); let sig = String::from_utf8(fs::read(sig_file_path).await.unwrap()).unwrap(); let mut authz_file_path = PathBuf::from(&BASE_PATH); - authz_file_path.push(format!("{name}/{name}.authz", name = &file_name)); + authz_file_path.push(format!("{name}/{name}.authz", name = file_name)); let auth_headers: serde_json::Value = serde_json::from_slice(&fs::read(authz_file_path).await.unwrap()).unwrap(); diff --git a/src/sign_outgoing.rs b/src/sign_outgoing.rs index dcbacc8..98db998 100644 --- a/src/sign_outgoing.rs +++ b/src/sign_outgoing.rs @@ -51,7 +51,7 @@ impl MAuthInfo { } pub(crate) fn set_headers_v2(&self, req: &mut Request, signature: String, timestamp_str: &str) { - let sig_head_str = format!("MWSV2 {}:{};", self.app_id, &signature); + let sig_head_str = format!("MWSV2 {}:{};", self.app_id, signature); let headers = req.headers_mut(); headers.insert("MCC-Time", HeaderValue::from_str(timestamp_str).unwrap()); headers.insert( diff --git a/src/validate_incoming.rs b/src/validate_incoming.rs index a99cefd..e06d90c 100644 --- a/src/validate_incoming.rs +++ b/src/validate_incoming.rs @@ -254,7 +254,7 @@ impl MAuthInfo { return Some(pub_key.clone()); } } - let uri = self.mauth_uri_base.join(&format!("{}", &app_uuid)).unwrap(); + let uri = self.mauth_uri_base.join(&format!("{}", app_uuid)).unwrap(); let mauth_response = CLIENT.get().unwrap().get(uri).send().await; match mauth_response { Err(_) => None, From 267d3ac5416babe1e3c8aff2055f798b801a8b2f Mon Sep 17 00:00:00 2001 From: "Edward J. Jinotti" Date: Wed, 22 Jul 2026 07:33:04 -0400 Subject: [PATCH 5/5] fix factbook --- factbook.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/factbook.yaml b/factbook.yaml index 574cc67..aa22f04 100644 --- a/factbook.yaml +++ b/factbook.yaml @@ -7,9 +7,9 @@ spec: experience: enablement metadata: json_schema: https://github.com/mdsol/platform-standards/tree/main/schemas/v1alpha1.schema.json - name: mauth-core - title: MAuth Core - description: A library to generate and verify MAuth signatures + name: mauth-client-rust + title: MAuth Client Rust + description: A client library for MAuth in Rust. teams: - name: Architecture Enablement number: 119