use std::collections::BTreeMap;
use std::fmt::Debug;
use std::sync::Mutex;
use std::time::Duration;
use async_trait::async_trait;
use crate::core::Timestamp;
use super::{PeerCredential, PeerId};
#[derive(Debug, thiserror::Error)]
pub enum CredentialError {
#[error("could not obtain a credential for '{audience}': {detail}")]
Unavailable { audience: PeerId, detail: String },
#[error(
"the issuer returned a credential for '{issued_for}' when '{audience}' was \
requested — an unbound token is one the recipient can replay"
)]
WrongAudience {
audience: PeerId,
issued_for: PeerId,
},
#[error("the credential issued for '{audience}' is already spent")]
Stale { audience: PeerId },
}
#[async_trait]
pub trait TokenExchange: Send + Sync + Debug {
async fn exchange(&self, audience: &PeerId) -> Result<PeerCredential, CredentialError>;
}
#[async_trait]
pub trait CredentialSource: Send + Sync + Debug {
async fn credential(
&self,
audience: &PeerId,
now: Timestamp,
) -> Result<PeerCredential, CredentialError>;
}
#[derive(Debug)]
pub struct Fixed(PeerCredential);
impl Fixed {
#[must_use]
pub const fn new(credential: PeerCredential) -> Self {
Self(credential)
}
}
#[async_trait]
impl CredentialSource for Fixed {
async fn credential(
&self,
audience: &PeerId,
_now: Timestamp,
) -> Result<PeerCredential, CredentialError> {
if self.0.audience() != audience {
return Err(CredentialError::WrongAudience {
audience: audience.clone(),
issued_for: self.0.audience().clone(),
});
}
Ok(self.0.clone())
}
}
#[derive(Debug)]
pub struct Cached {
exchange: std::sync::Arc<dyn TokenExchange>,
skew: Duration,
held: Mutex<BTreeMap<PeerId, PeerCredential>>,
}
impl Cached {
pub const DEFAULT_SKEW: Duration = Duration::from_mins(1);
#[must_use]
pub fn new(exchange: std::sync::Arc<dyn TokenExchange>) -> Self {
Self {
exchange,
skew: Self::DEFAULT_SKEW,
held: Mutex::new(BTreeMap::new()),
}
}
#[must_use]
pub const fn skew(mut self, skew: Duration) -> Self {
self.skew = skew;
self
}
}
#[async_trait]
impl CredentialSource for Cached {
async fn credential(
&self,
audience: &PeerId,
now: Timestamp,
) -> Result<PeerCredential, CredentialError> {
{
let held = self.held.lock().expect("credential cache");
if let Some(c) = held.get(audience)
&& c.is_usable_at(now, self.skew)
{
return Ok(c.clone());
}
}
let fresh = self.exchange.exchange(audience).await?;
if fresh.audience() != audience {
return Err(CredentialError::WrongAudience {
audience: audience.clone(),
issued_for: fresh.audience().clone(),
});
}
if !fresh.is_usable_at(now, self.skew) {
return Err(CredentialError::Stale {
audience: audience.clone(),
});
}
self.held
.lock()
.expect("credential cache")
.insert(audience.clone(), fresh.clone());
Ok(fresh)
}
}