use chrono::Utc;
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serializer};
use serde_json::Value;
use std::collections::HashMap;
use crate::util::generate_jti;
pub const DEFAULT_TOKEN_LIFESPAN: i64 = 60 * 60;
pub const REQUIRED_CLAIMS: [&str; 5] = ["aud", "iss", "jti", "iat", "exp"];
pub type ExtraClaims = HashMap<String, Value>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum Aud {
One(String),
Many(Vec<String>),
}
impl Aud {
#[allow(clippy::should_implement_trait)]
pub fn from_str<S: Into<String>>(aud: S) -> Aud {
Aud::One(aud.into())
}
pub fn from_vec(list: Vec<&str>) -> Aud {
Aud::Many(list.iter().map(|s| (*s).to_string()).collect())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
pub aud: Aud,
pub iss: String,
pub jti: String,
pub iat: i64,
pub exp: i64,
#[serde(
flatten,
deserialize_with = "de_extra_claims",
serialize_with = "ser_extra_claims"
)]
pub extra_claims: Option<ExtraClaims>,
}
fn de_extra_claims<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Option<ExtraClaims>, D::Error> {
let extra_claims = ExtraClaims::deserialize(deserializer)?;
if !extra_claims.is_empty() {
Ok(Some(extra_claims))
} else {
Ok(None)
}
}
fn ser_extra_claims<S: Serializer>(
extra_claims: &Option<ExtraClaims>,
s: S,
) -> Result<S::Ok, S::Error> {
if let Some(claims) = extra_claims {
let mut map = s.serialize_map(Some(claims.len()))?;
for (k, v) in claims {
if !REQUIRED_CLAIMS.contains(&k.as_str()) {
map.serialize_entry(k, v)?;
}
}
map.end()
} else {
s.serialize_none()
}
}
impl Claims {
pub fn cache_key(&self) -> String {
serde_json::to_string(&json!({
"aud": json!(self.aud),
"iss": json!(self.iss),
"iat": json!(self.iat),
"exp": json!(self.exp),
"extra_claims": json!(self.extra_claims)
}))
.unwrap()
}
}
pub(crate) struct ClaimsBuilder {
iss: String,
lifespan: i64,
}
impl ClaimsBuilder {
pub(crate) fn new(iss: String) -> ClaimsBuilder {
let lifespan = DEFAULT_TOKEN_LIFESPAN;
ClaimsBuilder { iss, lifespan }
}
pub(crate) fn lifespan(&mut self, lifespan: i64) -> &mut ClaimsBuilder {
self.lifespan = lifespan;
self
}
pub(crate) fn build(&self, aud: Aud, extra_claims: Option<ExtraClaims>) -> Claims {
let iss = self.iss.clone();
let jti = generate_jti();
let iat = Utc::now().timestamp();
let exp = iat + self.lifespan;
Claims {
aud,
iss,
iat,
exp,
jti,
extra_claims,
}
}
}