use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::Deserialize;
use crate::application::oauth::TokenClaims;
#[derive(Debug, Default, Deserialize)]
pub(super) struct JwtClaims {
#[serde(default)]
sub: Option<String>,
#[serde(default)]
iss: Option<String>,
#[serde(default)]
aud: Option<Audience>,
#[serde(default)]
exp: Option<f64>,
#[serde(default)]
nbf: Option<f64>,
#[serde(default)]
scope: Option<Scopes>,
#[serde(default)]
scp: Option<Scopes>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Audience {
One(String),
Many(Vec<String>),
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
enum Scopes {
Delimited(String),
Listed(Vec<String>),
}
const DELIMITER: char = ' ';
impl JwtClaims {
pub(super) fn into_token_claims(self) -> TokenClaims {
let mut claims = TokenClaims::new();
if let Some(subject) = self.sub {
claims = claims.with_subject(subject);
}
if let Some(issuer) = self.iss {
claims = claims.with_issuer(issuer);
}
for audience in self.aud.into_iter().flat_map(Audience::into_entries) {
claims = claims.with_audience_string(audience);
}
for scope in self.scope.into_iter().flat_map(Scopes::into_entries) {
claims = claims.with_scope(scope);
}
for scope in self.scp.into_iter().flat_map(Scopes::into_entries) {
claims = claims.with_scope(scope);
}
if let Some(exp) = self.exp.and_then(instant) {
claims = claims.with_expiry(exp);
}
if let Some(nbf) = self.nbf.and_then(instant) {
claims = claims.with_not_before(nbf);
}
claims
}
}
impl Audience {
fn into_entries(self) -> impl Iterator<Item = String> {
let (one, many) = match self {
Self::One(one) => (Some(one), Vec::new()),
Self::Many(many) => (None, many),
};
one.into_iter().chain(many)
}
}
impl Scopes {
fn into_entries(self) -> impl Iterator<Item = Arc<str>> {
let entries: Vec<Arc<str>> = match self {
Self::Delimited(delimited) => delimited.split(DELIMITER).map(Arc::from).collect(),
Self::Listed(listed) => listed.into_iter().map(Arc::from).collect(),
};
entries.into_iter().filter(|scope| !scope.is_empty())
}
}
pub(super) fn instant(seconds: f64) -> Option<SystemTime> {
if !seconds.is_finite() || seconds < 0.0 {
return None;
}
UNIX_EPOCH.checked_add(Duration::from_secs_f64(seconds))
}