use std::{path::Path, time::Duration};
use async_mutex::Mutex as AsyncMutex;
use async_trait::async_trait;
use log::debug;
use miette::Diagnostic;
use serde_json::json;
use thiserror::Error;
use url::Url;
use super::service_token::ServiceToken;
use crate::credentials::{
token_store::TokenStore, AutoRefreshable, ClearTokenError, Credentials, GetTokenError,
TokenExpiry,
};
use crate::reqwest_client::create_client;
use crate::user_agent::get_user_agent;
pub struct ServiceAccessKeyCredentials {
access_key: String,
audience: Option<String>,
cts_base_url: Url,
token_store: AsyncMutex<TokenStore<ServiceToken>>,
client: reqwest_middleware::ClientWithMiddleware,
}
#[derive(Diagnostic, Error, Debug)]
pub enum AcquireTokenError {
#[error("Failed to acquire token: {0}")]
RequestFailed(Box<dyn std::error::Error + Sync + Send>),
#[error("Failed to parse json response: {0}")]
BadResponse(Box<dyn std::error::Error + Sync + Send>),
}
impl ServiceAccessKeyCredentials {
pub fn new(
token_path: &Path,
access_key: &str,
cts_base_url: &Url,
audience: Option<&str>,
) -> Self {
Self {
access_key: access_key.to_string(),
audience: audience.map(|s| s.to_string()),
cts_base_url: cts_base_url.to_owned(),
token_store: AsyncMutex::new(TokenStore::new(token_path)),
client: create_client(),
}
}
async fn authorise(&self) -> Result<ServiceToken, AcquireTokenError> {
debug!(target: "service_access_key_credentials", "Authorising Access Token with CTS");
let url = self.cts_base_url.join("/api/authorise").unwrap();
let token: ServiceToken = self
.client
.post(url)
.json(&json!({ "accessKey": self.access_key, "audience": self.audience }))
.header("user-agent", get_user_agent())
.send()
.await
.map_err(|e| AcquireTokenError::RequestFailed(Box::new(e)))?
.error_for_status()
.map_err(|e| AcquireTokenError::RequestFailed(Box::new(e)))?
.json()
.await
.map_err(|e| AcquireTokenError::BadResponse(Box::new(e)))?;
debug!(target: "service_access_key_credentials",
"Access Token Acquired - expiry(epoch seconds): {}",
&token.expiry
);
Ok(token)
}
}
#[async_trait]
impl Credentials for ServiceAccessKeyCredentials {
type Token = ServiceToken;
async fn get_token(&self) -> Result<Self::Token, GetTokenError> {
debug!(target: "service_access_key_credentials", "getting token (waiting for lock)");
let mut token_store = self.token_store.lock().await;
debug!(target: "service_access_key_credentials", "getting token (got lock)");
if let Some(cached_token) = token_store.get() {
debug!(target: "service_access_key_credentials", "found cached token");
if !cached_token.is_expired() {
debug!(target: "service_access_key_credentials", "using cached token");
return Ok(cached_token);
}
debug!(target: "service_access_key_credentials", "cached token is expired");
}
debug!(target: "service_access_key_credentials", "fetching new token from CTS");
let new_token = self
.authorise()
.await
.map_err(|e| GetTokenError::AcquireNewTokenFailed(Box::new(e)))?;
token_store
.set(&new_token)
.map_err(|e| GetTokenError::PersistTokenError(Box::new(e)))?;
Ok(new_token)
}
async fn clear_token(&self) -> Result<(), ClearTokenError> {
debug!(target: "service_access_key_credentials", "clearing token");
let mut token_store = self.token_store.lock().await;
token_store
.clear()
.map_err(|e| ClearTokenError(Box::new(e)))
}
}
#[async_trait]
impl AutoRefreshable for ServiceAccessKeyCredentials {
async fn refresh(&self) -> Duration {
let token = {
let mut token_store = self.token_store.lock().await;
token_store.get()
};
if let Some(cached_token) = token {
debug!(target: "service_access_key_credentials", "Found token on disk");
if !cached_token.should_refresh() {
debug!(target: "service_access_key_credentials", "Access token is still new");
return cached_token.refresh_interval();
}
}
debug!(target: "service_access_key_credentials", "Access token is missing or close to expiry, refreshing");
if let Ok(new_token) = self.authorise().await {
let mut token_store = self.token_store.lock().await;
if token_store.set(&new_token).is_ok() {
debug!(target: "service_access_key_credentials", "Access token refreshed and saved to disk");
return new_token.refresh_interval();
}
}
Self::Token::min_refresh_interval()
}
}