use chrono::{DateTime, Utc};
use openidconnect::core::CoreIdToken;
use serde::Deserialize;
use base64::{Engine as _, engine::general_purpose::STANDARD_NO_PAD as base64};
use crate::errors::SigstoreError;
#[derive(Deserialize)]
pub struct Claims {
pub aud: String,
#[serde(with = "chrono::serde::ts_seconds")]
pub exp: DateTime<Utc>,
#[serde(with = "chrono::serde::ts_seconds_option")]
#[serde(default)]
pub nbf: Option<DateTime<Utc>>,
pub email: String,
}
pub type UnverifiedClaims = Claims;
pub struct IdentityToken {
original_token: String,
claims: UnverifiedClaims,
}
impl IdentityToken {
pub fn unverified_claims(&self) -> &UnverifiedClaims {
&self.claims
}
pub fn in_validity_period(&self) -> bool {
let now = Utc::now();
if let Some(nbf) = self.claims.nbf {
nbf <= now && now < self.claims.exp
} else {
now < self.claims.exp
}
}
}
impl TryFrom<&str> for IdentityToken {
type Error = SigstoreError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let parts: [&str; 3] = value.split('.').collect::<Vec<_>>().try_into().or(Err(
SigstoreError::IdentityTokenError("Malformed JWT".into()),
))?;
let claims = base64
.decode(parts[1])
.or(Err(SigstoreError::IdentityTokenError(
"Malformed JWT: Unable to decode claims".into(),
)))?;
let claims: Claims = serde_json::from_slice(&claims).or(Err(
SigstoreError::IdentityTokenError("Malformed JWT: claims JSON malformed".into()),
))?;
if claims.aud != "sigstore" {
return Err(SigstoreError::IdentityTokenError(
"Not a Sigstore JWT".into(),
));
}
Ok(IdentityToken {
original_token: value.to_owned(),
claims,
})
}
}
impl From<CoreIdToken> for IdentityToken {
fn from(value: CoreIdToken) -> Self {
value
.to_string()
.as_str()
.try_into()
.expect("Token conversion failed")
}
}
impl std::fmt::Display for IdentityToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.original_token.clone())
}
}