use crate::access_token::{AuthError, Claims, VerifyConfig};
use crate::engine::raw::parse_payload_json;
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum AudOnWire {
One(String),
Many(Vec<String>),
}
impl AudOnWire {
fn matches(&self, expected: &str) -> bool {
match self {
Self::One(s) => s == expected,
Self::Many(v) => v.iter().any(|s| s == expected),
}
}
}
fn require_numeric(
payload: &serde_json::Value,
key: &str,
missing: AuthError,
) -> Result<i64, AuthError> {
let value = payload.get(key).ok_or(missing)?;
value.as_i64().ok_or(AuthError::InvalidNumericType)
}
fn optional_numeric(payload: &serde_json::Value, key: &str) -> Result<Option<i64>, AuthError> {
match payload.get(key) {
None => Ok(None),
Some(v) => v.as_i64().map(Some).ok_or(AuthError::InvalidNumericType),
}
}
pub(crate) fn run(token: &str, cfg: &VerifyConfig) -> Result<Claims, AuthError> {
let payload = parse_payload_json(token)?;
let exp = require_numeric(&payload, "exp", AuthError::ExpMissing)?;
let now = time::OffsetDateTime::now_utc().unix_timestamp();
if exp < now {
return Err(AuthError::Expired);
}
let iat = require_numeric(&payload, "iat", AuthError::IatMissing)?;
const ACCESS_EXP_MAX_SECS: i64 = 24 * 3600;
if exp.saturating_sub(iat) > ACCESS_EXP_MAX_SECS {
return Err(AuthError::ExpUpperBound);
}
let aud_value = payload.get("aud").ok_or(AuthError::AudMissing)?;
let aud: AudOnWire =
serde_json::from_value(aud_value.clone()).map_err(|_| AuthError::AudMismatch)?;
if !aud.matches(&cfg.shared.audience) {
return Err(AuthError::AudMismatch);
}
let iss = payload
.get("iss")
.and_then(|v| v.as_str())
.ok_or(AuthError::IssMismatch)?;
if iss != cfg.shared.issuer {
return Err(AuthError::IssMismatch);
}
const IAT_LEEWAY_SECS: i64 = 60;
if iat > now + IAT_LEEWAY_SECS {
return Err(AuthError::IatFuture);
}
let nbf = optional_numeric(&payload, "nbf")?;
if let Some(n) = nbf
&& n > now
{
return Err(AuthError::NotYetValid);
}
let jti = payload
.get("jti")
.and_then(|v| v.as_str())
.ok_or(AuthError::JtiMissing)?;
let sub = payload
.get("sub")
.and_then(|v| v.as_str())
.ok_or(AuthError::SubMissing)?;
let client_id = payload
.get("client_id")
.and_then(|v| v.as_str())
.ok_or(AuthError::ClientIdMissing)?;
let cat = payload.get("cat").and_then(|v| v.as_str()).unwrap_or("");
if cat != "access" {
return Err(AuthError::TokenTypeMismatch);
}
Ok(Claims {
iss: iss.to_string(),
sub: sub.to_string(),
exp,
iat,
nbf,
jti: jti.to_string(),
client_id: client_id.to_string(),
account_type: None,
caps: Vec::new(),
scopes: Vec::new(),
admin: false,
active_ppnum: None,
delegator: None,
cid: None,
sid: None,
})
}