diff --git a/examples/custom_flow.rs b/examples/custom_flow.rs index f34bb7c..462e4fd 100644 --- a/examples/custom_flow.rs +++ b/examples/custom_flow.rs @@ -44,7 +44,7 @@ async fn main() { let sec = yup_oauth2::read_application_secret("client_secret.json") .await .expect("client secret couldn't be read."); - let mut auth = yup_oauth2::InstalledFlowAuthenticator::builder( + let auth = yup_oauth2::InstalledFlowAuthenticator::builder( sec, yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect, ) diff --git a/examples/custom_storage.rs b/examples/custom_storage.rs index e5599b0..f1db2cf 100644 --- a/examples/custom_storage.rs +++ b/examples/custom_storage.rs @@ -1,9 +1,11 @@ //! Demonstrating how to create a custom token store +use anyhow::anyhow; use async_trait::async_trait; +use std::sync::RwLock; use yup_oauth2::storage::{ScopeSet, TokenInfo, TokenStorage}; struct ExampleTokenStore { - store: Vec, + store: RwLock>, } struct StoredToken { @@ -15,12 +17,17 @@ struct StoredToken { /// to disk, an OS keychain, a database or whatever suits your use-case #[async_trait] impl TokenStorage for ExampleTokenStore { - async fn set(&mut self, scopes: ScopeSet<'_, &str>, token: TokenInfo) -> anyhow::Result<()> { + async fn set(&self, scopes: ScopeSet<'_, &str>, token: TokenInfo) -> anyhow::Result<()> { let data = serde_json::to_string(&token).unwrap(); println!("Storing token for scopes {:?}", scopes); - self.store.push(StoredToken { + let mut store = self + .store + .write() + .map_err(|_| anyhow!("Unable to lock store for writing"))?; + + store.push(StoredToken { scopes: scopes.scopes(), serialized_token: data, }); @@ -30,13 +37,15 @@ impl TokenStorage for ExampleTokenStore { async fn get(&self, target_scopes: ScopeSet<'_, &str>) -> Option { // Retrieve the token data - for stored_token in self.store.iter() { - if target_scopes.is_covered_by(&stored_token.scopes) { - return serde_json::from_str(&stored_token.serialized_token).ok(); + self.store.read().ok().and_then(|store| { + for stored_token in store.iter() { + if target_scopes.is_covered_by(&stored_token.scopes) { + return serde_json::from_str(&stored_token.serialized_token).ok(); + } } - } - None + None + }) } } @@ -46,12 +55,14 @@ async fn main() { let sec = yup_oauth2::read_application_secret("client_secret.json") .await .expect("client secret couldn't be read."); - let mut auth = yup_oauth2::InstalledFlowAuthenticator::builder( + let auth = yup_oauth2::InstalledFlowAuthenticator::builder( sec, yup_oauth2::InstalledFlowReturnMethod::HTTPRedirect, ) .with_storage(yup_oauth2::authenticator::StorageType::Custom(Box::new( - ExampleTokenStore { store: vec![] }, + ExampleTokenStore { + store: RwLock::new(vec![]), + }, ))) .build() .await diff --git a/src/authenticator.rs b/src/authenticator.rs index a095f27..bd2cf83 100644 --- a/src/authenticator.rs +++ b/src/authenticator.rs @@ -47,7 +47,7 @@ where C: hyper::client::connect::Connect + Clone + Send + Sync + 'static, { /// Return the current token for the provided scopes. - pub async fn token<'a, T>(&'a mut self, scopes: &'a [T]) -> Result + pub async fn token<'a, T>(&'a self, scopes: &'a [T]) -> Result where T: AsRef, { @@ -57,7 +57,7 @@ where /// Return a token for the provided scopes, but don't reuse cached tokens. Instead, /// always fetch a new token from the OAuth server. pub async fn force_refreshed_token<'a, T>( - &'a mut self, + &'a self, scopes: &'a [T], ) -> Result where @@ -68,7 +68,7 @@ where /// Return a cached token or fetch a new one from the server. async fn find_token<'a, T>( - &'a mut self, + &'a self, scopes: &'a [T], force_refresh: bool, ) -> Result diff --git a/src/storage.rs b/src/storage.rs index cfe7f82..d26c90e 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -137,7 +137,7 @@ pub trait TokenStorage: Send + Sync { /// Store a token for the given set of scopes so that it can be retrieved later by get() /// ScopeSet implements Hash so that you can easily serialize and store it. /// TokenInfo can be serialized with serde. - async fn set(&mut self, scopes: ScopeSet<'_, &str>, token: TokenInfo) -> anyhow::Result<()>; + async fn set(&self, scopes: ScopeSet<'_, &str>, token: TokenInfo) -> anyhow::Result<()>; /// Retrieve a token stored by set for the given set of scopes async fn get(&self, scopes: ScopeSet<'_, &str>) -> Option; @@ -151,7 +151,7 @@ pub(crate) enum Storage { impl Storage { pub(crate) async fn set( - &mut self, + &self, scopes: ScopeSet<'_, T>, token: TokenInfo, ) -> anyhow::Result<()> diff --git a/tests/tests.rs b/tests/tests.rs index 53f27ab..548d9c7 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -92,7 +92,7 @@ async fn test_device_success() { }))), ); - let mut auth = create_device_flow_auth(&server).await; + let auth = create_device_flow_auth(&server).await; let token = auth .token(&["https://www.googleapis.com/scope/1"]) .await @@ -117,7 +117,7 @@ async fn test_device_no_code() { "error_description": "description" }))), ); - let mut auth = create_device_flow_auth(&server).await; + let auth = create_device_flow_auth(&server).await; let res = auth.token(&["https://www.googleapis.com/scope/1"]).await; assert!(res.is_err()); assert!(format!("{}", res.unwrap_err()).contains("invalid_client_id")); @@ -155,7 +155,7 @@ async fn test_device_no_token() { "error": "access_denied" }))), ); - let mut auth = create_device_flow_auth(&server).await; + let auth = create_device_flow_auth(&server).await; let res = auth.token(&["https://www.googleapis.com/scope/1"]).await; assert!(res.is_err()); assert!(format!("{}", res.unwrap_err()).contains("access_denied")); @@ -239,7 +239,7 @@ async fn create_installed_flow_auth( async fn test_installed_interactive_success() { let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = + let auth = create_installed_flow_auth(&server, InstalledFlowReturnMethod::Interactive, None).await; server.expect( Expectation::matching(all_of![ @@ -268,7 +268,7 @@ async fn test_installed_interactive_success() { async fn test_installed_redirect_success() { let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = + let auth = create_installed_flow_auth(&server, InstalledFlowReturnMethod::HTTPRedirect, None).await; server.expect( Expectation::matching(all_of![ @@ -297,7 +297,7 @@ async fn test_installed_redirect_success() { async fn test_installed_error() { let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = + let auth = create_installed_flow_auth(&server, InstalledFlowReturnMethod::Interactive, None).await; server.expect( Expectation::matching(all_of![ @@ -347,7 +347,7 @@ async fn test_service_account_success() { use chrono::Utc; let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = create_service_account_auth(&server).await; + let auth = create_service_account_auth(&server).await; server.expect( Expectation::matching(request::method_path("POST", "/token")) @@ -369,7 +369,7 @@ async fn test_service_account_success() { async fn test_service_account_error() { let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = create_service_account_auth(&server).await; + let auth = create_service_account_auth(&server).await; server.expect( Expectation::matching(request::method_path("POST", "/token")).respond_with(json_encoded( serde_json::json!({ @@ -388,7 +388,7 @@ async fn test_service_account_error() { async fn test_refresh() { let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = + let auth = create_installed_flow_auth(&server, InstalledFlowReturnMethod::Interactive, None).await; // We refresh a token whenever it's within 1 minute of expiring. So // acquiring a token that expires in 59 seconds will force a refresh on @@ -486,7 +486,7 @@ async fn test_refresh() { async fn test_memory_storage() { let _ = env_logger::try_init(); let server = Server::run(); - let mut auth = + let auth = create_installed_flow_auth(&server, InstalledFlowReturnMethod::Interactive, None).await; server.expect( Expectation::matching(all_of![ @@ -519,7 +519,7 @@ async fn test_memory_storage() { // Create a new authenticator. This authenticator does not share a cache // with the previous one. Validate that it receives a different token. - let mut auth2 = + let auth2 = create_installed_flow_auth(&server, InstalledFlowReturnMethod::Interactive, None).await; server.expect( Expectation::matching(all_of![ @@ -565,7 +565,7 @@ async fn test_disk_storage() { }))), ); { - let mut auth = create_installed_flow_auth( + let auth = create_installed_flow_auth( &server, InstalledFlowReturnMethod::Interactive, Some(storage_path.clone()), @@ -589,7 +589,7 @@ async fn test_disk_storage() { // Create a new authenticator. This authenticator uses the same token // storage file as the previous one so should receive a token without // making any http requests. - let mut auth = create_installed_flow_auth( + let auth = create_installed_flow_auth( &server, InstalledFlowReturnMethod::Interactive, Some(storage_path.clone()),