use std::sync::Arc;
use axess_clock::{Clock, SystemClock};
use axess_identity::{TenantId, UserId};
use crate::delegated::error::DelegatedError;
use super::credential::{DelegatedCredentialStore, store_err};
use super::grant::refresh_token_grant;
use super::provider::DelegatedProvider;
const DEFAULT_REFRESH_SKEW_SECS: i64 = 60;
pub struct StoredDelegationSession<S>
where
S: DelegatedCredentialStore,
{
provider: Arc<DelegatedProvider>,
store: Arc<S>,
tenant: TenantId,
user: UserId,
http: reqwest::Client,
clock: Arc<dyn Clock>,
refresh_skew_secs: i64,
}
impl<S> StoredDelegationSession<S>
where
S: DelegatedCredentialStore,
{
pub fn new(
provider: Arc<DelegatedProvider>,
store: Arc<S>,
tenant: TenantId,
user: UserId,
) -> Self {
Self {
provider,
store,
tenant,
user,
http: reqwest::Client::new(),
clock: Arc::new(SystemClock),
refresh_skew_secs: DEFAULT_REFRESH_SKEW_SECS,
}
}
pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
self.http = http;
self
}
pub fn with_clock(mut self, clock: Arc<dyn Clock>) -> Self {
self.clock = clock;
self
}
pub fn with_refresh_skew_secs(mut self, secs: i64) -> Self {
self.refresh_skew_secs = secs;
self
}
pub async fn get_access_token(&self) -> Result<String, DelegatedError> {
let cred = self
.store
.load(&self.tenant, &self.user, &self.provider.name)
.await
.map_err(store_err)?
.ok_or(DelegatedError::NotConnected)?;
let now = self.clock.now();
let skew = chrono::Duration::seconds(self.refresh_skew_secs);
if cred.is_fresh(now, skew) {
return Ok((*cred.access_token).to_string());
}
let refresh_token = cred
.refresh_token
.as_deref()
.ok_or(DelegatedError::RefreshRejected)?
.to_string();
let refreshed = refresh_token_grant(&self.provider, &refresh_token, &self.http).await?;
let final_credential = super::credential::StoredDelegation {
refresh_token: refreshed.refresh_token.or(cred.refresh_token),
..refreshed
};
let access = (*final_credential.access_token).to_string();
self.store
.save(&self.tenant, &self.user, final_credential)
.await
.map_err(store_err)?;
Ok(access)
}
pub async fn revoke(&self) -> Result<(), DelegatedError> {
self.store
.revoke(&self.tenant, &self.user, &self.provider.name)
.await
.map_err(store_err)
}
}