use crate::id_token::{AuthError, VerifyConfig};
const IAT_LEEWAY_SECS: i64 = 60;
pub(crate) struct RegisteredClaims {
pub(crate) iss: String,
pub(crate) sub: String,
pub(crate) aud: Vec<String>,
pub(crate) exp: i64,
pub(crate) iat: i64,
}
fn require_numeric(
payload: &serde_json::Value,
key: &'static str,
missing: AuthError,
) -> Result<i64, AuthError> {
let value = payload.get(key).ok_or(missing)?;
value.as_i64().ok_or(AuthError::InvalidNumericType(key))
}
pub(crate) fn run(
payload: &serde_json::Value,
cfg: &VerifyConfig,
now: i64,
) -> Result<RegisteredClaims, AuthError> {
let exp = require_numeric(payload, "exp", AuthError::ExpMissing)?;
if exp <= now {
return Err(AuthError::Expired);
}
let iat = require_numeric(payload, "iat", AuthError::IatMissing)?;
let aud = match payload.get("aud").ok_or(AuthError::AudMissing)? {
serde_json::Value::String(one) => vec![one.clone()],
serde_json::Value::Array(many) => many
.iter()
.map(|v| v.as_str().map(str::to_owned))
.collect::<Option<Vec<_>>>()
.ok_or(AuthError::AudMismatch)?,
_ => return Err(AuthError::AudMismatch),
};
if !aud.contains(&cfg.shared.audience) {
return Err(AuthError::AudMismatch);
}
let iss = payload
.get("iss")
.and_then(|v| v.as_str())
.filter(|iss| *iss == cfg.shared.issuer)
.ok_or(AuthError::IssMismatch)?;
if iat > now + IAT_LEEWAY_SECS {
return Err(AuthError::IatFuture);
}
let sub = payload
.get("sub")
.and_then(|v| v.as_str())
.filter(|sub| !sub.is_empty())
.ok_or(AuthError::SubMissing)?;
Ok(RegisteredClaims {
iss: iss.to_owned(),
sub: sub.to_owned(),
aud,
exp,
iat,
})
}