mod claims;
mod key_set;
use std::sync::Arc;
use jsonwebtoken::{Algorithm, Validation, decode, decode_header};
use crate::application::oauth::{Refusal, SourceError, TokenClaims, TokenSource};
use self::claims::JwtClaims;
use self::key_set::{KeyFailure, KeySet};
#[derive(Debug, Clone)]
pub struct JwtSource {
keys: KeySet,
}
impl JwtSource {
#[must_use]
pub fn new(jwks_uri: impl Into<Arc<str>>, accepted: Vec<Algorithm>) -> Self {
Self {
keys: KeySet::new(jwks_uri, accepted),
}
}
#[must_use]
pub fn with_http_client(mut self, http: reqwest::Client) -> Self {
self.keys = self.keys.with_http_client(http);
self
}
pub fn invalidate(&self) {
self.keys.invalidate();
}
pub async fn warm(&self) -> Result<(), SourceError> {
self.keys.warm().await.map_err(|failure| match failure {
KeyFailure::Unreadable(reason) => SourceError::Unavailable(reason),
KeyFailure::NoKey(error) => SourceError::Unavailable(error.to_string()),
})
}
#[must_use]
pub fn accepted_algorithms(&self) -> &[Algorithm] {
self.keys.accepted()
}
}
#[must_use]
pub fn default_algorithms() -> Vec<Algorithm> {
vec![Algorithm::RS256]
}
impl TokenSource for JwtSource {
async fn claims(&self, presented: &str) -> Result<TokenClaims, SourceError> {
let header = decode_header(presented).map_err(|_| Refusal::Malformed)?;
if header.crit.as_ref().is_some_and(|crit| !crit.is_empty()) {
return Err(Refusal::Malformed.into());
}
if header.jwk.is_some() {
return Err(Refusal::Malformed.into());
}
let key = self
.keys
.key_for(&header, true)
.await
.map_err(map_key_failure)?;
let mut validation = Validation::new(header.alg);
validation.validate_exp = false;
validation.validate_nbf = false;
validation.validate_aud = false;
validation.required_spec_claims.clear();
let payload = decode::<JwtClaims>(presented, &key, &validation)
.map_err(|_| Refusal::Malformed)?
.claims;
Ok(payload.into_token_claims())
}
}
fn map_key_failure(failure: KeyFailure) -> SourceError {
match failure {
KeyFailure::Unreadable(reason) => SourceError::Unavailable(reason),
KeyFailure::NoKey(_) => Refusal::Unverified.into(),
}
}
#[cfg(test)]
#[path = "jwt/tests.rs"]
mod tests;
#[cfg(test)]
impl JwtSource {
pub(super) fn seed(&self, keys: jsonwebtoken::jwk::JwkSet) {
self.keys.seed(keys);
}
}