#![doc = include_str!("../README.md")]
mod claims;
mod error;
use std::time::Duration;
use jwtk::jwk::RemoteJwksVerifier;
use reqwest::Client;
pub use claims::*;
pub use error::Error;
pub use jwtk;
pub use reqwest;
pub use uuid;
pub struct Validator {
inner: RemoteJwksVerifier,
audience: String,
}
impl Validator {
pub fn new(team_name: &str, audience: impl Into<String>) -> Self {
Validator::with_client(Client::default(), team_name, audience)
}
#[cfg(feature = "env")]
pub fn from_env() -> Result<Self, Error> {
fn var(name: &'static str) -> Result<String, Error> {
std::env::var(name).map_err(|_| Error::MissingEnv(name))
}
let team_name = var("CF_ACCESS_TEAM")?;
let audience = var("CF_ACCESS_AUD")?;
Ok(Validator::new(&team_name, audience))
}
pub fn with_client(client: Client, team_name: &str, audience: impl Into<String>) -> Self {
let issuer = format!("https://{team_name}.cloudflareaccess.com");
let url = format!("{issuer}/cdn-cgi/access/certs");
Validator {
inner: RemoteJwksVerifier::new(url, Some(client), CACHE_DURATION),
audience: audience.into(),
}
}
pub async fn validate(&self, jwt: &str) -> Result<Claims, Error> {
let mut token = self.inner.verify::<ClaimsExtra>(jwt).await?;
if !token.claims().aud.iter().any(|aud| **aud == self.audience) {
return Err(Error::InvalidAud);
}
let claims = std::mem::take(token.claims_mut());
let token = match claims.extra {
ClaimsExtra::Identity {
email,
ty,
identity_nonce,
country,
} => IdentityClaims {
sub: claims.sub.ok_or(Error::MissingSub)?.parse()?,
email,
ty,
identity_nonce,
country,
}
.into(),
ClaimsExtra::Service(claims) => claims.into(),
};
Ok(token)
}
}
const CACHE_DURATION: Duration = Duration::from_secs(60 * 60 * 24 * 3);