mod in_memory;
use std::sync::{Arc, PoisonError};
use crate::{
core::{
dpop::ResourceServerDPoP, http::HttpClient, platform::MaybeSend, platform::MaybeSendSync,
},
token::RefreshToken,
};
use snafu::prelude::*;
use std::sync::RwLock;
use crate::grant::core::TokenResponse;
pub use in_memory::{InMemoryTokenCache, InMemoryTokenCacheBuilder};
pub trait TokenCache {
type Error<C: HttpClient>: crate::core::Error + 'static;
type DPoP: ResourceServerDPoP;
fn get_token_response<C: HttpClient>(
&self,
http_client: &C,
) -> impl Future<Output = Result<Arc<TokenResponse>, GetTokenError<Self::Error<C>>>> + MaybeSend;
fn resource_server_dpop(&self) -> &Self::DPoP;
fn prime(&self, response: Arc<TokenResponse>) -> impl Future<Output = ()> + MaybeSend;
fn invalidate(&self);
}
#[derive(Debug, Snafu)]
pub enum GetTokenError<E: crate::core::Error + 'static> {
RefreshFailed {
source: E,
},
#[snafu(display(
"Token refresh failed and exchange also failed: refresh={refresh_source}, exchange={exchange_source}"
))]
BothFailed {
refresh_source: E,
exchange_source: E,
},
ExchangeFailed {
source: E,
},
NoTokenSource,
}
impl<E: crate::core::Error + 'static> crate::core::Error for GetTokenError<E> {
fn is_retryable(&self) -> bool {
match self {
GetTokenError::RefreshFailed { source } | GetTokenError::ExchangeFailed { source } => {
source.is_retryable()
}
GetTokenError::BothFailed {
exchange_source, ..
} => exchange_source.is_retryable(),
GetTokenError::NoTokenSource => false,
}
}
}
#[cfg(test)]
mod tests {
use crate::core::{client_auth::NoAuth, dpop::NoDPoP};
use crate::{
cache::{InMemoryRefreshTokenStore, InMemoryTokenCache},
grant::client_credentials::{ClientCredentialsGrant, ClientCredentialsGrantParameters},
};
#[test]
fn test_setup() {
let _cache = InMemoryTokenCache::builder()
.grant(
ClientCredentialsGrant::builder()
.client_id("client_id")
.client_auth(NoAuth)
.token_endpoint("https://blah")
.unwrap()
.dpop(NoDPoP)
.build(),
)
.grant_parameters(
ClientCredentialsGrantParameters::builder()
.scopes(["read", "write"])
.build(),
)
.refresh_store(InMemoryRefreshTokenStore::default())
.build();
}
}
pub trait RefreshTokenStore: MaybeSendSync {
fn get(&self) -> impl Future<Output = Option<RefreshToken>> + MaybeSend;
fn set(&self, token: &RefreshToken) -> impl Future<Output = ()> + MaybeSend;
fn clear(&self) -> impl Future<Output = ()> + MaybeSend;
}
#[derive(Debug, Default)]
pub struct InMemoryRefreshTokenStore {
refresh_token: RwLock<Option<RefreshToken>>,
}
impl RefreshTokenStore for InMemoryRefreshTokenStore {
async fn get(&self) -> Option<RefreshToken> {
self.refresh_token
.read()
.unwrap_or_else(PoisonError::into_inner)
.clone()
}
async fn set(&self, token: &RefreshToken) {
*self
.refresh_token
.write()
.unwrap_or_else(PoisonError::into_inner) = Some(token.clone());
}
async fn clear(&self) {
*self
.refresh_token
.write()
.unwrap_or_else(PoisonError::into_inner) = None;
}
}