1use crate::{CLIENT, MAuthInfo};
2use mauth_core::signer::Signer;
3use reqwest::Client;
4use reqwest::Url;
5use reqwest_middleware::ClientBuilder;
6use serde::Deserialize;
7use std::io;
8use thiserror::Error;
9use uuid::Uuid;
10
11const CONFIG_FILE: &str = ".mauth_config.yml";
12
13impl MAuthInfo {
14 pub fn from_default_file() -> Result<MAuthInfo, ConfigReadError> {
18 Self::from_config_section(&Self::config_section_from_default_file()?)
19 }
20
21 pub(crate) fn config_section_from_default_file() -> Result<ConfigFileSection, ConfigReadError> {
22 let mut home = dirs::home_dir().unwrap();
23 home.push(CONFIG_FILE);
24 let config_data = std::fs::read_to_string(&home)?;
25
26 let config_data_value: serde_yml::Value = serde_yml::from_slice(&config_data.into_bytes())?;
27 let common_section = config_data_value
28 .get("common")
29 .ok_or(ConfigReadError::InvalidFile(None))?;
30 let common_section_typed: ConfigFileSection =
31 serde_yml::from_value(common_section.clone())?;
32 Ok(common_section_typed)
33 }
34
35 pub fn from_config_section(section: &ConfigFileSection) -> Result<MAuthInfo, ConfigReadError> {
39 let full_uri: Url = format!(
40 "{}/mauth/{}/security_tokens/",
41 §ion.mauth_baseurl, §ion.mauth_api_version
42 )
43 .parse()?;
44
45 let mut pk_data = section.private_key_data.clone();
46 if pk_data.is_none()
47 && let Some(pk_file_path) = section.private_key_file.as_ref()
48 {
49 pk_data = Some(std::fs::read_to_string(pk_file_path)?);
50 }
51 if pk_data.is_none() {
52 return Err(ConfigReadError::NoPrivateKey);
53 }
54
55 let mauth_info = MAuthInfo {
56 app_id: Uuid::parse_str(§ion.app_uuid)?,
57 mauth_uri_base: full_uri,
58 sign_with_v1_also: !section.v2_only_sign_requests.unwrap_or(false),
59 allow_v1_auth: !section.v2_only_authenticate.unwrap_or(false),
60 signer: Signer::new(section.app_uuid.clone(), pk_data.unwrap())?,
61 };
62
63 CLIENT.get_or_init(|| {
64 let builder = ClientBuilder::new(Client::new()).with(mauth_info.clone());
65 #[cfg(any(
66 feature = "tracing-otel-26",
67 feature = "tracing-otel-27",
68 feature = "tracing-otel-28",
69 feature = "tracing-otel-29",
70 feature = "tracing-otel-30",
71 feature = "tracing-otel-31",
72 ))]
73 let builder = builder.with(reqwest_tracing::TracingMiddleware::default());
74 builder.build()
75 });
76
77 Ok(mauth_info)
78 }
79}
80
81#[derive(Deserialize, Clone)]
84pub struct ConfigFileSection {
85 pub app_uuid: String,
86 pub mauth_baseurl: String,
87 pub mauth_api_version: String,
88 pub private_key_file: Option<String>,
89 pub private_key_data: Option<String>,
90 pub v2_only_sign_requests: Option<bool>,
91 pub v2_only_authenticate: Option<bool>,
92}
93
94impl Default for ConfigFileSection {
95 fn default() -> Self {
96 Self {
97 app_uuid: "".to_string(),
98 mauth_baseurl: "".to_string(),
99 mauth_api_version: "v1".to_string(),
100 private_key_file: None,
101 private_key_data: None,
102 v2_only_sign_requests: Some(true),
103 v2_only_authenticate: Some(true),
104 }
105 }
106}
107
108#[derive(Debug, Error)]
111pub enum ConfigReadError {
112 #[error("File Read Error: {0}")]
113 FileReadError(#[from] io::Error),
114 #[error("Not a valid maudit config file: {0:?}")]
115 InvalidFile(Option<serde_yml::Error>),
116 #[error("MAudit URI not valid: {0}")]
117 InvalidUri(#[from] url::ParseError),
118 #[error("App UUID not valid: {0}")]
119 InvalidAppUuid(#[from] uuid::Error),
120 #[error("Unable to parse RSA private key: {0}")]
121 PrivateKeyDecodeError(String),
122 #[error("Neither private_key_file nor private_key_data were provided")]
123 NoPrivateKey,
124}
125
126impl From<mauth_core::error::Error> for ConfigReadError {
127 fn from(err: mauth_core::error::Error) -> ConfigReadError {
128 match err {
129 mauth_core::error::Error::PrivateKeyDecodeError(pkey_err) => {
130 ConfigReadError::PrivateKeyDecodeError(format!("{pkey_err}"))
131 }
132 _ => panic!("should not be possible to get this error type from signer construction"),
133 }
134 }
135}
136
137impl From<serde_yml::Error> for ConfigReadError {
138 fn from(err: serde_yml::Error) -> ConfigReadError {
139 ConfigReadError::InvalidFile(Some(err))
140 }
141}
142
143#[cfg(test)]
144mod test {
145 use super::*;
146 use tokio::fs;
147
148 #[tokio::test]
149 async fn invalid_uri_returns_right_error() {
150 let bad_config = ConfigFileSection {
151 app_uuid: "".to_string(),
152 mauth_baseurl: "dfaedfaewrfaew".to_string(),
153 mauth_api_version: "".to_string(),
154 private_key_file: Some("".to_string()),
155 private_key_data: None,
156 v2_only_sign_requests: None,
157 v2_only_authenticate: None,
158 };
159 let load_result = MAuthInfo::from_config_section(&bad_config);
160 assert!(matches!(load_result, Err(ConfigReadError::InvalidUri(_))));
161 }
162
163 #[tokio::test]
164 async fn bad_file_path_returns_right_error() {
165 let bad_config = ConfigFileSection {
166 app_uuid: "".to_string(),
167 mauth_baseurl: "https://example.com/".to_string(),
168 mauth_api_version: "v1".to_string(),
169 private_key_file: Some("no_such_file".to_string()),
170 private_key_data: None,
171 v2_only_sign_requests: None,
172 v2_only_authenticate: None,
173 };
174 let load_result = MAuthInfo::from_config_section(&bad_config);
175 assert!(matches!(
176 load_result,
177 Err(ConfigReadError::FileReadError(_))
178 ));
179 }
180
181 #[tokio::test]
182 async fn bad_key_file_returns_right_error() {
183 let filename = "dummy_file";
184 fs::write(&filename, b"definitely not a key").await.unwrap();
185 let bad_config = ConfigFileSection {
186 app_uuid: "c7db7fde-2448-11ef-b358-125eb8485a60".to_string(),
187 mauth_baseurl: "https://example.com/".to_string(),
188 mauth_api_version: "v1".to_string(),
189 private_key_file: Some(filename.to_string()),
190 private_key_data: None,
191 v2_only_sign_requests: None,
192 v2_only_authenticate: None,
193 };
194 let load_result = MAuthInfo::from_config_section(&bad_config);
195 fs::remove_file(&filename).await.unwrap();
196 assert!(matches!(
197 load_result,
198 Err(ConfigReadError::PrivateKeyDecodeError(_))
199 ));
200 }
201
202 #[tokio::test]
203 async fn bad_uuid_returns_right_error() {
204 let filename = "valid_key_file";
205 fs::write(&filename, "invalid data").await.unwrap();
206 let bad_config = ConfigFileSection {
207 app_uuid: "".to_string(),
208 mauth_baseurl: "https://example.com/".to_string(),
209 mauth_api_version: "v1".to_string(),
210 private_key_file: Some(filename.to_string()),
211 private_key_data: None,
212 v2_only_sign_requests: None,
213 v2_only_authenticate: None,
214 };
215 let load_result = MAuthInfo::from_config_section(&bad_config);
216 fs::remove_file(&filename).await.unwrap();
217 assert!(matches!(
218 load_result,
219 Err(ConfigReadError::InvalidAppUuid(_))
220 ));
221 }
222}