use crate::locator::AuthLocator;
use anyhow::Context;
use redact::Secret;
use std::io::Write;
use tokio::fs;
use tracing::instrument;
pub struct CliTokenDiskCache(AuthLocator);
#[derive(serde::Serialize, serde::Deserialize)]
struct CachedToken {
#[serde(serialize_with = "redact::expose_secret")]
jwt: Option<Secret<String>>,
}
impl CliTokenDiskCache {
pub fn new(locator: AuthLocator) -> Self {
Self(locator)
}
pub async fn get_token(&self) -> Result<Option<Secret<String>>, anyhow::Error> {
self.get_cache().await.map(|data| data.and_then(|d| d.jwt))
}
async fn get_cache(&self) -> Result<Option<CachedToken>, anyhow::Error> {
let cache_path = self.0.token_cache();
let data = match fs::read(&cache_path).await {
Ok(data) => data,
Err(e) => {
return if e.kind() == std::io::ErrorKind::NotFound {
Ok(None)
} else {
Err(e.into())
};
}
};
let data = std::str::from_utf8(&data)
.with_context(|| format!("`{}` contains non UTF-8 data", cache_path.display()))?;
let data = toml::from_str(data).context("Failed to deserialize CLI token cache")?;
Ok(data)
}
#[instrument(skip_all, "Updating the CLI token cache")]
pub async fn upsert_token(&self, new_token: Secret<String>) -> Result<(), anyhow::Error> {
let updated_data = match self.get_cache().await? {
None => CachedToken {
jwt: Some(new_token),
},
Some(mut data) => {
data.jwt = Some(new_token);
data
}
};
let tmp_dir = self.0.tmp();
fs::create_dir_all(&tmp_dir).await.with_context(|| {
format!(
"Failed to create `{}` to store temporary files.",
tmp_dir.display()
)
})?;
let mut tmp_file = tempfile::NamedTempFile::new_in(&tmp_dir)
.context("Failed to create a temporary file")?;
tmp_file.write_all(
"# This file is autogenerated and managed by `pavex` CLI. Do NOT edit it.\n".as_bytes(),
)?;
let updated_data = toml::to_string(&updated_data)
.context("Failed to serialize updated CLI token cache")?;
tmp_file.write_all(updated_data.as_bytes())?;
fs::rename(tmp_file.path(), &self.0.token_cache())
.await
.context("Failed to swap the existing CLI cache file with the updated one")
}
}