use core::fmt;
use core::str::FromStr;
use chrono::{DateTime, Utc};
use serde::de::Error as _;
use serde::{Deserialize, Deserializer, Serialize};
#[cfg(feature = "status-list")]
use crate::jose::numeric_date;
use crate::jose::{CompactJws, JwsError, JwsHeader, JwtClaims, Signer, encode_compact};
#[derive(Clone, PartialEq, Eq, Hash, Serialize)]
#[serde(transparent)]
pub struct Jwt(String);
impl Jwt {
pub fn new(token: impl Into<String>) -> Result<Self, JwsError> {
let token = token.into();
CompactJws::decode(&token)?;
Ok(Self(token))
}
pub async fn sign(
header: &JwsHeader,
claims: &JwtClaims,
signer: &dyn Signer,
) -> Result<Self, JwsError> {
if let Some(name) = registered_in_additional(claims) {
return Err(JwsError::InvalidPayload {
reason: format!(
"registered claim `{name}` is set under `additional`; set its field instead"
),
source: None,
});
}
let payload = serde_json::to_vec(claims).expect("a claims set serializes");
encode_compact(header, &payload, signer).await.map(Self)
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn into_string(self) -> String {
self.0
}
pub fn decode(&self) -> Result<CompactJws, JwsError> {
CompactJws::decode(&self.0)
}
}
impl fmt::Debug for Jwt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Jwt").field(&self.0).finish()
}
}
impl fmt::Display for Jwt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl AsRef<str> for Jwt {
fn as_ref(&self) -> &str {
&self.0
}
}
impl FromStr for Jwt {
type Err = JwsError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
impl TryFrom<String> for Jwt {
type Error = JwsError;
fn try_from(token: String) -> Result<Self, Self::Error> {
Self::new(token)
}
}
impl<'de> Deserialize<'de> for Jwt {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
}
}
fn registered_in_additional(claims: &JwtClaims) -> Option<&'static str> {
JwtClaims::REGISTERED
.into_iter()
.find(|name| claims.additional.contains_key(*name))
}
#[cfg(feature = "status-list")]
pub(crate) fn required_numeric_date<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<i64, D::Error> {
numeric_date(deserializer)?.ok_or_else(|| serde::de::Error::custom("a NumericDate is required"))
}
#[cfg_attr(docsrs, doc(cfg(feature = "vc")))]
impl JwtClaims {
pub fn numeric_date(time: DateTime<Utc>) -> i64 {
time.timestamp()
}
pub fn timestamp(numeric_date: i64) -> Option<DateTime<Utc>> {
DateTime::from_timestamp(numeric_date, 0)
}
#[must_use]
pub fn with_exp(mut self, time: DateTime<Utc>) -> Self {
self.exp = Some(Self::numeric_date(time));
self
}
#[must_use]
pub fn with_nbf(mut self, time: DateTime<Utc>) -> Self {
self.nbf = Some(Self::numeric_date(time));
self
}
#[must_use]
pub fn with_iat(mut self, time: DateTime<Utc>) -> Self {
self.iat = Some(Self::numeric_date(time));
self
}
}