Skip to content

Commit

Permalink
Initial implementation of auth caching
Browse files Browse the repository at this point in the history
  • Loading branch information
itsyaasir committed Nov 9, 2023
1 parent abbfa0d commit 1e127c4
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 35 deletions.
18 changes: 11 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,22 @@ readme = "./README.md"
license = "MIT"

[dependencies]
chrono = {version = "0.4", optional = true, default-features = false, features = ["clock", "serde"] }
openssl = {version = "0.10", optional = true}
reqwest = {version = "0.11", features = ["json"]}
serde = {version="1.0", features= ["derive"]}
cached = { version = "0.46", features = ["wasm", "async", "proc_macro"] }
chrono = { version = "0.4", optional = true, default-features = false, features = [
"clock",
"serde",
] }
openssl = { version = "0.10", optional = true }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_repr = "0.1"
thiserror = "1.0.37"
wiremock = "0.5"

[dev-dependencies]
dotenv = "0.15"
tokio = {version = "1", features = ["rt", "rt-multi-thread", "macros"]}
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
wiremock = "0.5"

[features]
Expand All @@ -34,7 +38,7 @@ default = [
"c2b_simulate",
"express_request",
"transaction_reversal",
"transaction_status"
"transaction_status",
]
account_balance = ["dep:openssl"]
b2b = ["dep:openssl"]
Expand All @@ -44,4 +48,4 @@ c2b_register = []
c2b_simulate = []
express_request = ["dep:chrono"]
transaction_reversal = ["dep:openssl"]
transaction_status= ["dep:openssl"]
transaction_status = ["dep:openssl"]
61 changes: 34 additions & 27 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use crate::auth::AUTH;

Check failure on line 1 in src/client.rs

View workflow job for this annotation

GitHub Actions / clippy

unresolved import `crate::auth::AUTH`

error[E0432]: unresolved import `crate::auth::AUTH` --> src/client.rs:1:5 | 1 | use crate::auth::AUTH; | ^^^^^^^^^^^^^^^^^ no `AUTH` in `auth`
use crate::environment::ApiEnvironment;
use crate::services::{
AccountBalanceBuilder, B2bBuilder, B2cBuilder, BulkInvoiceBuilder, C2bRegisterBuilder,
C2bSimulateBuilder, CancelInvoiceBuilder, MpesaExpressRequestBuilder, OnboardBuilder,
OnboardModifyBuilder, ReconciliationBuilder, SingleInvoiceBuilder, TransactionReversalBuilder,
TransactionStatusBuilder,
};
use crate::{ApiError, MpesaError};
use crate::{auth, MpesaError};
use cached::Cached;

Check failure on line 10 in src/client.rs

View workflow job for this annotation

GitHub Actions / clippy

unused import: `cached::Cached`

error: unused import: `cached::Cached` --> src/client.rs:10:5 | 10 | use cached::Cached; | ^^^^^^^^^^^^^^ | = note: `-D unused-imports` implied by `-D warnings`
use openssl::base64;
use openssl::rsa::Padding;
use openssl::x509::X509;
use reqwest::Client as HttpClient;
use serde_json::Value;

use std::cell::RefCell;

/// Source: [test credentials](https://developer.safaricom.co.ke/test_credentials)
Expand Down Expand Up @@ -68,6 +70,16 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa<Env> {
p.to_owned()
}

/// Get the client key
pub fn client_key(&self) -> &str {
&self.client_key
}

/// Get the client secret
pub fn client_secret(&self) -> &str {
&self.client_secret
}

/// Optional in development but required for production, you will need to call this method and set your production initiator password.
/// If in development, default initiator password is already pre-set
/// ```ignore
Expand Down Expand Up @@ -102,33 +114,28 @@ impl<'mpesa, Env: ApiEnvironment> Mpesa<Env> {
///
/// # Errors
/// Returns a `MpesaError` on failure
#[allow(clippy::single_char_pattern)]
pub(crate) async fn auth(&self) -> MpesaResult<String> {
let url = format!(
"{}/oauth/v1/generate?grant_type=client_credentials",
self.environment.base_url()
);
let response = self
.http_client
.get(&url)
.basic_auth(&self.client_key, Some(&self.client_secret))
.send()
.await?;
if response.status().is_success() {
let value = response.json::<Value>().await?;
let access_token = value
.get("access_token")
.ok_or_else(|| String::from("Failed to extract token from the response"))
.unwrap();
let access_token = access_token
.as_str()
.ok_or_else(|| String::from("Error converting access token to string"))
.unwrap();

return Ok(access_token.to_string());
if let Some(token) = AUTH.lock().await.cache_get(&self.client_key) {
return Ok(token.clone());
}
let error = response.json::<ApiError>().await?;
Err(MpesaError::AuthenticationError(error))

// Generate a new access token
let new_token = match auth::auth_prime_cache(self).await {

Check failure on line 123 in src/client.rs

View workflow job for this annotation

GitHub Actions / clippy

cannot find function `auth_prime_cache` in module `auth`

error[E0425]: cannot find function `auth_prime_cache` in module `auth` --> src/client.rs:123:37 | 123 | let new_token = match auth::auth_prime_cache(self).await { | ^^^^^^^^^^^^^^^^ not found in `auth`
Ok(token) => token,
Err(e) => return Err(e),
};

// Double-check if the access token is cached by another thread
if let Some(token) = AUTH.lock().await.cache_get(&self.client_key) {
return Ok(token.clone());
}

// Cache the new token
AUTH.lock()
.await
.cache_set(self.client_key.clone(), new_token.clone());

Ok(new_token)
}

/// **B2C Builder**
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod constants;
pub mod environment;
mod errors;
pub mod services;
mod auth;

Check failure on line 8 in src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

file not found for module `auth`

error[E0583]: file not found for module `auth` --> src/lib.rs:8:1 | 8 | mod auth; | ^^^^^^^^^ | = help: to create the module `auth`, create file "src/auth.rs" or "src/auth/mod.rs"

pub use client::{Mpesa, MpesaResult};
pub use constants::{
Expand Down
3 changes: 2 additions & 1 deletion tests/mpesa-rust/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ macro_rules! get_mpesa_client {
.and(path("/oauth/v1/generate"))
.and(query_param("grant_type", "client_credentials"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"access_token": "dummy_access_token"
"access_token": "dummy_access_token",
"expiry_in": 3600
})))
.expect($expected_requests)
.mount(&server)
Expand Down

0 comments on commit 1e127c4

Please sign in to comment.