1use core::fmt;
5use core::str::FromStr;
6
7use chrono::{DateTime, Utc};
8use serde::de::Error as _;
9use serde::{Deserialize, Deserializer, Serialize};
10
11#[cfg(feature = "status-list")]
12use crate::jose::numeric_date;
13use crate::jose::{CompactJws, JwsError, JwsHeader, JwtClaims, Signer, encode_compact};
14
15#[derive(Clone, PartialEq, Eq, Hash, Serialize)]
35#[serde(transparent)]
36pub struct Jwt(String);
37
38impl Jwt {
39 pub fn new(token: impl Into<String>) -> Result<Self, JwsError> {
45 let token = token.into();
46 CompactJws::decode(&token)?;
47 Ok(Self(token))
48 }
49
50 pub async fn sign(
63 header: &JwsHeader,
64 claims: &JwtClaims,
65 signer: &dyn Signer,
66 ) -> Result<Self, JwsError> {
67 if let Some(name) = registered_in_additional(claims) {
68 return Err(JwsError::InvalidPayload {
69 reason: format!(
70 "registered claim `{name}` is set under `additional`; set its field instead"
71 ),
72 source: None,
73 });
74 }
75 let payload = serde_json::to_vec(claims).expect("a claims set serializes");
76 encode_compact(header, &payload, signer).await.map(Self)
77 }
78
79 pub fn as_str(&self) -> &str {
81 &self.0
82 }
83
84 pub fn into_string(self) -> String {
86 self.0
87 }
88
89 pub fn decode(&self) -> Result<CompactJws, JwsError> {
95 CompactJws::decode(&self.0)
96 }
97}
98
99impl fmt::Debug for Jwt {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.debug_tuple("Jwt").field(&self.0).finish()
102 }
103}
104
105impl fmt::Display for Jwt {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 f.write_str(&self.0)
108 }
109}
110
111impl AsRef<str> for Jwt {
112 fn as_ref(&self) -> &str {
113 &self.0
114 }
115}
116
117impl FromStr for Jwt {
118 type Err = JwsError;
119
120 fn from_str(s: &str) -> Result<Self, Self::Err> {
121 Self::new(s)
122 }
123}
124
125impl TryFrom<String> for Jwt {
126 type Error = JwsError;
127
128 fn try_from(token: String) -> Result<Self, Self::Error> {
129 Self::new(token)
130 }
131}
132
133impl<'de> Deserialize<'de> for Jwt {
134 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
135 Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
136 }
137}
138
139fn registered_in_additional(claims: &JwtClaims) -> Option<&'static str> {
141 JwtClaims::REGISTERED
142 .into_iter()
143 .find(|name| claims.additional.contains_key(*name))
144}
145
146#[cfg(feature = "status-list")]
148pub(crate) fn required_numeric_date<'de, D: Deserializer<'de>>(
149 deserializer: D,
150) -> Result<i64, D::Error> {
151 numeric_date(deserializer)?.ok_or_else(|| serde::de::Error::custom("a NumericDate is required"))
152}
153
154#[cfg_attr(docsrs, doc(cfg(feature = "vc")))]
157impl JwtClaims {
158 pub fn numeric_date(time: DateTime<Utc>) -> i64 {
160 time.timestamp()
161 }
162
163 pub fn timestamp(numeric_date: i64) -> Option<DateTime<Utc>> {
165 DateTime::from_timestamp(numeric_date, 0)
166 }
167
168 #[must_use]
170 pub fn with_exp(mut self, time: DateTime<Utc>) -> Self {
171 self.exp = Some(Self::numeric_date(time));
172 self
173 }
174
175 #[must_use]
177 pub fn with_nbf(mut self, time: DateTime<Utc>) -> Self {
178 self.nbf = Some(Self::numeric_date(time));
179 self
180 }
181
182 #[must_use]
184 pub fn with_iat(mut self, time: DateTime<Utc>) -> Self {
185 self.iat = Some(Self::numeric_date(time));
186 self
187 }
188}