use std::time::Duration;
use reqwest::header::HeaderMap;
use serde::Deserialize;
use thiserror::Error;
const JWK_URL: &str =
"https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com";
#[derive(Debug, Deserialize)]
pub(crate) struct JwkKeys {
pub(crate) keys: Vec<JwkKey>,
pub(crate) max_age: Option<Duration>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct JwkKey {
pub(crate) e: String,
pub(crate) alg: String,
pub(crate) kid: String,
pub(crate) n: String,
}
#[derive(Debug, Deserialize)]
struct KeyResponse {
keys: Vec<JwkKey>,
}
#[derive(Error, Debug)]
pub enum JwkKeysError {
#[error("Fetch `{0}`")]
Fetch(reqwest::Error),
#[error("NoCacheControlKey")]
NoCacheControlKey,
#[error("InvalidCacheControlValue `{0}`")]
InvalidCacheControlValue(reqwest::header::ToStrError),
#[error("InvalidMaxAge `{0}`")]
InvalidMaxAge(String),
#[error("Parse `{0}`")]
Parse(reqwest::Error),
}
impl JwkKeys {
pub(crate) async fn fetch_keys() -> Result<Self, JwkKeysError> {
let response = reqwest::get(JWK_URL).await.map_err(JwkKeysError::Fetch)?;
let max_age = match parse_max_age(response.headers()) {
Ok(v) => Some(v),
Err(e) => {
tracing::error!("could not parse max_age: {:?}", e);
None
}
};
let public_keys = response
.json::<KeyResponse>()
.await
.map_err(JwkKeysError::Parse)?;
Ok(JwkKeys {
keys: public_keys.keys,
max_age,
})
}
}
fn parse_max_age(headers: &HeaderMap) -> Result<Duration, JwkKeysError> {
let cache_control = match headers.get("Cache-Control") {
Some(header_value) => header_value.to_str(),
None => return Err(JwkKeysError::NoCacheControlKey),
};
let cache_control_value = match cache_control {
Ok(v) => v,
Err(err) => return Err(JwkKeysError::InvalidCacheControlValue(err)),
};
for v in cache_control_value.split(',') {
let mut pair = v.split('=').map(|s| s.trim());
let key = match pair.next() {
Some(v) => v,
None => continue,
};
if key.to_lowercase() != "max-age" {
continue;
}
let value = match pair.next().and_then(|v| v.parse().ok()) {
Some(v) => v,
None => continue,
};
return Ok(Duration::from_secs(value));
}
Err(JwkKeysError::InvalidMaxAge(cache_control_value.to_owned()))
}