use async_trait::async_trait;
use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation};
use serde::Deserialize;
use crate::error::{IdentityError, Result};
#[derive(Debug, Clone)]
pub struct VerifiedIdToken {
pub iss: String,
pub sub: String,
pub aud: String,
pub email: Option<String>,
pub email_verified: bool,
pub name: Option<String>,
pub nonce: Option<String>,
pub extra: serde_json::Map<String, serde_json::Value>,
}
#[async_trait]
pub trait IdTokenVerifier: Send + Sync + 'static {
async fn verify(&self, id_token: &str, expected_nonce: Option<&str>)
-> Result<VerifiedIdToken>;
}
#[derive(Debug, Deserialize)]
struct RawIdClaims {
iss: String,
sub: String,
#[serde(default)]
email: Option<String>,
#[serde(default)]
email_verified: Option<serde_json::Value>,
#[serde(default)]
name: Option<String>,
#[serde(default)]
nonce: Option<String>,
#[serde(flatten)]
extra: serde_json::Map<String, serde_json::Value>,
}
struct KeyEntry {
kid: Option<String>,
alg: Algorithm,
key: DecodingKey,
}
pub struct JwtIdTokenVerifier {
issuer: String,
audience: String,
keys: Vec<KeyEntry>,
leeway_secs: u64,
}
impl JwtIdTokenVerifier {
pub fn new(issuer: impl Into<String>, audience: impl Into<String>) -> Self {
Self {
issuer: issuer.into(),
audience: audience.into(),
keys: Vec::new(),
leeway_secs: 60,
}
}
pub fn add_rsa_pem(mut self, kid: Option<String>, alg: Algorithm, pem: &str) -> Result<Self> {
let key = DecodingKey::from_rsa_pem(pem.as_bytes())
.map_err(|e| IdentityError::Internal(format!("id_token key: {e}")))?;
self.keys.push(KeyEntry { kid, alg, key });
Ok(self)
}
pub fn add_ec_pem(mut self, kid: Option<String>, alg: Algorithm, pem: &str) -> Result<Self> {
let key = DecodingKey::from_ec_pem(pem.as_bytes())
.map_err(|e| IdentityError::Internal(format!("id_token key: {e}")))?;
self.keys.push(KeyEntry { kid, alg, key });
Ok(self)
}
fn select_key(&self, kid: Option<&str>) -> Option<&KeyEntry> {
self.keys
.iter()
.find(|k| k.kid.as_deref() == kid && kid.is_some())
.or_else(|| {
if self.keys.len() == 1 {
self.keys.first()
} else {
self.keys.iter().find(|k| k.kid.is_none())
}
})
}
}
#[async_trait]
impl IdTokenVerifier for JwtIdTokenVerifier {
async fn verify(
&self,
id_token: &str,
expected_nonce: Option<&str>,
) -> Result<VerifiedIdToken> {
let header = decode_header(id_token).map_err(|_| IdentityError::InvalidToken)?;
let entry = self
.select_key(header.kid.as_deref())
.ok_or(IdentityError::InvalidToken)?;
let mut validation = Validation::new(entry.alg);
validation.set_issuer(&[self.issuer.as_str()]);
validation.set_audience(&[self.audience.as_str()]);
validation.validate_exp = true;
validation.leeway = self.leeway_secs;
let data = decode::<RawIdClaims>(id_token, &entry.key, &validation)
.map_err(|_| IdentityError::InvalidToken)?;
let claims = data.claims;
if let Some(expected) = expected_nonce {
match &claims.nonce {
Some(n) if n == expected => {}
_ => return Err(IdentityError::InvalidToken),
}
}
let email_verified = matches!(
claims.email_verified,
Some(serde_json::Value::Bool(true)) | Some(serde_json::Value::String(_)) ) && !matches!(
claims.email_verified,
Some(serde_json::Value::String(ref s)) if s != "true"
);
Ok(VerifiedIdToken {
iss: claims.iss,
sub: claims.sub,
aud: self.audience.clone(),
email: claims.email,
email_verified,
name: claims.name,
nonce: claims.nonce,
extra: claims.extra,
})
}
}