diff --git a/Cargo.toml b/Cargo.toml index 400f2eccb..9b7707636 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license = "MIT" chrono = {version = "0.4", optional = true, default-features = false, features = ["clock", "serde"] } openssl = {version = "0.10", optional = true} reqwest = {version = "0.11", features = ["json"]} +secrecy = "0.8.0" serde = {version="1.0", features= ["derive"]} serde_json = "1.0" serde_repr = "0.1" @@ -25,17 +26,7 @@ tokio = {version = "1", features = ["rt", "rt-multi-thread", "macros"]} wiremock = "0.5" [features] -default = [ - "account_balance", - "b2b", - "b2c", - "bill_manager", - "c2b_register", - "c2b_simulate", - "express_request", - "transaction_reversal", - "transaction_status" -] +default = ["account_balance", "b2b", "b2c", "bill_manager", "c2b_register", "c2b_simulate", "express_request", "transaction_reversal", "transaction_status"] account_balance = ["dep:openssl"] b2b = ["dep:openssl"] b2c = ["dep:openssl"] diff --git a/src/client.rs b/src/client.rs index 65c87ee7b..3a9640e4b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -10,6 +10,7 @@ use openssl::base64; use openssl::rsa::Padding; use openssl::x509::X509; use reqwest::Client as HttpClient; +use secrecy::{ExposeSecret, Secret}; use serde_json::Value; use std::cell::RefCell; @@ -25,8 +26,8 @@ pub type MpesaResult = Result; #[derive(Clone, Debug)] pub struct Mpesa { client_key: String, - client_secret: String, - initiator_password: RefCell>, + client_secret: Secret, + initiator_password: RefCell>>, pub(crate) environment: Env, pub(crate) http_client: HttpClient, } @@ -42,6 +43,9 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { /// Environment::Sandbox, /// ); /// ``` + /// + /// # Panics + /// This method can panic if a TLS backend cannot be initialized for the internal http_client pub fn new>(client_key: S, client_secret: S, environment: Env) -> Self { let http_client = HttpClient::builder() .connect_timeout(std::time::Duration::from_millis(10_000)) @@ -52,7 +56,7 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { .expect("Error building http client"); Self { client_key: client_key.into(), - client_secret: client_secret.into(), + client_secret: Secret::new(client_secret.into()), initiator_password: RefCell::new(None), environment, http_client, @@ -65,7 +69,7 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { let Some(p) = &*self.initiator_password.borrow() else { return DEFAULT_INITIATOR_PASSWORD.to_owned(); }; - p.to_owned() + p.expose_secret().into() } /// Optional in development but required for production, you will need to call this method and set your production initiator password. @@ -82,7 +86,7 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { /// client.set_initiator_password("your_initiator_password"); /// ``` pub fn set_initiator_password>(&self, initiator_password: S) { - *self.initiator_password.borrow_mut() = Some(initiator_password.into()); + *self.initiator_password.borrow_mut() = Some(Secret::new(initiator_password.into())); } /// Checks if the client can be authenticated @@ -102,7 +106,6 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { /// /// # Errors /// Returns a `MpesaError` on failure - #[allow(clippy::single_char_pattern)] pub(crate) async fn auth(&self) -> MpesaResult { let url = format!( "{}/oauth/v1/generate?grant_type=client_credentials", @@ -111,7 +114,7 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa { let response = self .http_client .get(&url) - .basic_auth(&self.client_key, Some(&self.client_secret)) + .basic_auth(&self.client_key, Some(&self.client_secret.expose_secret())) .send() .await?; if response.status().is_success() {