use super::Issuer;
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use serde::Deserialize;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
pub(super) struct Cached {
token: String,
renew_at: tokio::time::Instant,
}
impl Cached {
pub fn new(token: String) -> anyhow::Result<Self> {
#[derive(Deserialize)]
struct Expiry {
exp: u64,
}
let payload = token
.split('.')
.nth(1)
.ok_or_else(|| anyhow::anyhow!("permission_watch_pass_invalid"))?;
let bytes = URL_SAFE_NO_PAD
.decode(payload)
.map_err(|_| anyhow::anyhow!("permission_watch_pass_invalid"))?;
let expiry: Expiry = serde_json::from_slice(&bytes)
.map_err(|_| anyhow::anyhow!("permission_watch_pass_invalid"))?;
let remaining = expiry.exp.saturating_sub(now()?).saturating_sub(60);
let renew_at = tokio::time::Instant::now()
.checked_add(Duration::from_secs(remaining))
.ok_or_else(|| anyhow::anyhow!("permission_watch_expiry_invalid"))?;
Ok(Self { token, renew_at })
}
}
fn now() -> anyhow::Result<u64> {
Ok(SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs())
}
impl Issuer {
pub(in crate::assistant) async fn watch_deadline(&self) -> tokio::time::Instant {
self.watch_pass.lock().await.renew_at
}
pub(in crate::assistant) async fn watch_credentials(&self) -> anyhow::Result<String> {
let mut cached = self.watch_pass.lock().await;
if cached.renew_at <= tokio::time::Instant::now() {
let response = self
.http
.post(format!("{}/api/agents/{}/pass", self.origin, self.agent))
.headers(crate::assistant::tools::proof_headers(&self.launch)?)
.json(&serde_json::json!({"audience":"haematite"}))
.send()
.await
.map_err(|_| anyhow::anyhow!("permission_watch_renewal_unreachable"))?;
let value = self.json(response).await?;
let current = now()?;
anyhow::ensure!(
value["audience"] == "haematite"
&& value["expires_at"]
.as_u64()
.is_some_and(|expiry| expiry > current.saturating_add(60)),
"permission_watch_renewal_invalid"
);
let token = value["pass"]
.as_str()
.filter(|token| !token.is_empty())
.ok_or_else(|| anyhow::anyhow!("permission_watch_renewal_invalid"))?;
let renewed = Cached::new(token.to_owned())?;
anyhow::ensure!(
renewed.renew_at > tokio::time::Instant::now(),
"permission_watch_renewal_expiry_invalid"
);
*cached = renewed;
}
Ok(cached.token.clone())
}
}