use std::time::Instant;
use crate::error::{Error, OAuthError};
use crate::model::oauth2::EveJwtKeys;
use crate::oauth2::jwk::cache::JwtKeyCache;
use crate::oauth2::jwk::refresh::refresh_jwt_keys;
use crate::oauth2::jwk::util::{
check_refresh_cooldown, is_cache_approaching_expiry, is_cache_expired,
};
use crate::oauth2::OAuth2Api;
use crate::Client;
pub struct JwkApi<'a> {
pub(super) client: &'a Client,
}
impl OAuth2Api<'_> {
pub fn jwk(&self) -> self::JwkApi<'_> {
self::JwkApi::new(self.client)
}
}
impl<'a> JwkApi<'a> {
pub(self) fn new(client: &'a Client) -> Self {
Self { client }
}
pub async fn get_jwt_keys(&self) -> Result<EveJwtKeys, Error> {
let esi_client = self.client;
let jwt_key_cache = &esi_client.inner.jwt_key_cache;
let config = &jwt_key_cache.config;
trace!("Checking JWT key cache state");
if let Some((keys, timestamp)) = jwt_key_cache.get_keys().await {
let elapsed_seconds = timestamp.elapsed().as_secs();
if !is_cache_expired(jwt_key_cache, timestamp) {
if jwt_key_cache.config.background_refresh_enabled
&& is_cache_approaching_expiry(jwt_key_cache, timestamp)
{
debug!("JWT keys approaching expiry (age: {}s)", elapsed_seconds);
let _ = self.trigger_background_jwt_refresh().await;
}
trace!(
"JWT keys still valid, using keys from cache (age: {}s)",
elapsed_seconds
);
return Ok(keys);
} else {
debug!(
"JWT key cache expired (age: {}s)",
timestamp.elapsed().as_secs()
);
}
}
let cooldown = check_refresh_cooldown(jwt_key_cache).await;
if let Some(cooldown_remaining) = cooldown {
let message = format!(
"JWT key refresh cooldown still active due to recent refresh failure during last {} seconds. Cooldown remaining: {} seconds.",
&config.refresh_cooldown.as_secs(), cooldown_remaining
);
error!(message);
return Err(Error::OAuthError(OAuthError::JwtKeyRefreshCooldown(
message,
)));
}
if !jwt_key_cache.refresh_lock_try_acquire() {
return self.wait_for_ongoing_refresh().await;
}
refresh_jwt_keys(
&esi_client.inner.reqwest_client,
jwt_key_cache,
jwt_key_cache.config.refresh_max_retries,
)
.await
}
pub async fn fetch_and_update_cache(&self) -> Result<EveJwtKeys, Error> {
let esi_client = self.client;
fetch_and_update_cache(
&esi_client.inner.reqwest_client,
&esi_client.inner.jwt_key_cache,
)
.await
}
pub async fn fetch_jwt_keys(&self) -> Result<EveJwtKeys, Error> {
let esi_client = self.client;
fetch_jwt_keys(
&esi_client.inner.reqwest_client,
&esi_client.inner.jwt_key_cache.config.jwk_url,
)
.await
}
}
pub(super) async fn fetch_jwt_keys(
reqwest_client: &reqwest::Client,
jwk_url: &str,
) -> Result<EveJwtKeys, Error> {
debug!("Fetching JWT keys from EVE OAuth2 API: {}", jwk_url);
let start_time = Instant::now();
let result = reqwest_client.get(jwk_url.to_string()).send().await;
let elapsed = start_time.elapsed();
let response = match result {
Ok(resp) => {
debug!(
"Received response from JWT keys endpoint, status: {} (took {}ms)",
resp.status(),
elapsed.as_millis()
);
if let Err(err) = resp.error_for_status_ref() {
return Err(err.into());
}
resp
}
Err(e) => {
let message = format!(
"Failed to connect to JWT keys endpoint after {}ms: {:?}",
elapsed.as_millis(),
e
);
error!(message);
return Err(e.into());
}
};
let result = response.json::<EveJwtKeys>().await;
let elapsed = start_time.elapsed();
let jwt_keys = match result {
Ok(keys) => {
trace!(
"Successfully parsed JWT keys response with {} keys (took {}ms)",
keys.keys.len(),
elapsed.as_millis()
);
keys
}
Err(e) => {
error!(
"Failed to parse JWT keys response after {}ms: {:?}",
elapsed.as_millis(),
e
);
return Err(e.into());
}
};
Ok(jwt_keys)
}
pub(super) async fn fetch_and_update_cache(
reqwest_client: &reqwest::Client,
jwt_key_cache: &JwtKeyCache,
) -> Result<EveJwtKeys, Error> {
trace!("Fetching fresh JWT keys and updating cache");
let start_time = Instant::now();
let fetch_result = fetch_jwt_keys(reqwest_client, &jwt_key_cache.config.jwk_url).await;
match fetch_result {
Ok(fresh_keys) => {
trace!(
"Successfully fetched {} JWT keys, updating cache",
fresh_keys.keys.len()
);
jwt_key_cache.update_keys(fresh_keys.clone()).await;
let elapsed = start_time.elapsed();
debug!(
"JWT keys cache updated successfully with {} keys (took {}ms)",
fresh_keys.keys.len(),
elapsed.as_millis()
);
Ok(fresh_keys)
}
Err(e) => {
let elapsed = start_time.elapsed();
error!(
"Failed to fetch JWT keys after {}ms: {:?}",
elapsed.as_millis(),
e
);
Err(e)
}
}
}