1use core::fmt;
5use core::str::FromStr;
6
7use chrono::{DateTime, Utc};
8use indexmap::IndexMap;
9use serde::de::Error as _;
10use serde::{Deserialize, Deserializer, Serialize};
11use serde_json::Value;
12
13use crate::did::OneOrMany;
14use crate::jose::{CompactJws, JwsError, JwsHeader, Signer, encode_compact};
15
16#[derive(Clone, PartialEq, Eq, Hash, Serialize)]
36#[serde(transparent)]
37pub struct Jwt(String);
38
39impl Jwt {
40 pub fn new(token: impl Into<String>) -> Result<Self, JwsError> {
46 let token = token.into();
47 CompactJws::decode(&token)?;
48 Ok(Self(token))
49 }
50
51 pub async fn sign(
60 header: &JwsHeader,
61 claims: &JwtClaims,
62 signer: &dyn Signer,
63 ) -> Result<Self, JwsError> {
64 if let Some(name) = claims.registered_in_additional() {
65 return Err(JwsError::InvalidPayload(format!(
66 "registered claim `{name}` is set under `additional`; set its field instead"
67 )));
68 }
69 let payload = serde_json::to_vec(claims).expect("a claims set serializes");
70 encode_compact(header, &payload, signer).await.map(Self)
71 }
72
73 pub fn as_str(&self) -> &str {
75 &self.0
76 }
77
78 pub fn into_string(self) -> String {
80 self.0
81 }
82
83 pub fn decode(&self) -> Result<CompactJws, JwsError> {
89 CompactJws::decode(&self.0)
90 }
91}
92
93impl fmt::Debug for Jwt {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 f.debug_tuple("Jwt").field(&self.0).finish()
96 }
97}
98
99impl fmt::Display for Jwt {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.write_str(&self.0)
102 }
103}
104
105impl AsRef<str> for Jwt {
106 fn as_ref(&self) -> &str {
107 &self.0
108 }
109}
110
111impl FromStr for Jwt {
112 type Err = JwsError;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 Self::new(s)
116 }
117}
118
119impl TryFrom<String> for Jwt {
120 type Error = JwsError;
121
122 fn try_from(token: String) -> Result<Self, Self::Error> {
123 Self::new(token)
124 }
125}
126
127impl<'de> Deserialize<'de> for Jwt {
128 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
129 Self::new(String::deserialize(deserializer)?).map_err(D::Error::custom)
130 }
131}
132
133#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
156#[non_exhaustive]
157pub struct JwtClaims {
158 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub iss: Option<String>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
163 pub sub: Option<String>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub aud: Option<OneOrMany<String>>,
167 #[serde(
169 default,
170 skip_serializing_if = "Option::is_none",
171 deserialize_with = "numeric_date"
172 )]
173 pub exp: Option<i64>,
174 #[serde(
176 default,
177 skip_serializing_if = "Option::is_none",
178 deserialize_with = "numeric_date"
179 )]
180 pub nbf: Option<i64>,
181 #[serde(
183 default,
184 skip_serializing_if = "Option::is_none",
185 deserialize_with = "numeric_date"
186 )]
187 pub iat: Option<i64>,
188 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub jti: Option<String>,
191 #[serde(flatten)]
193 pub additional: IndexMap<String, Value>,
194}
195
196pub(crate) fn numeric_date<'de, D: Deserializer<'de>>(
199 deserializer: D,
200) -> Result<Option<i64>, D::Error> {
201 let Some(number) = Option::<serde_json::Number>::deserialize(deserializer)? else {
202 return Ok(None);
203 };
204 number
205 .as_i64()
206 .or_else(|| {
207 number
208 .as_f64()
209 .filter(|seconds| (i64::MIN as f64..i64::MAX as f64).contains(seconds))
210 .map(|seconds| seconds.floor() as i64)
211 })
212 .map(Some)
213 .ok_or_else(|| D::Error::custom(format!("NumericDate {number} is out of range")))
214}
215
216#[cfg(feature = "sd-jwt")]
218pub(crate) fn required_numeric_date<'de, D: Deserializer<'de>>(
219 deserializer: D,
220) -> Result<i64, D::Error> {
221 numeric_date(deserializer)?.ok_or_else(|| serde::de::Error::custom("a NumericDate is required"))
222}
223
224impl JwtClaims {
225 pub const REGISTERED: [&'static str; 7] = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"];
227
228 pub fn new() -> Self {
230 Self::default()
231 }
232
233 fn registered_in_additional(&self) -> Option<&str> {
235 Self::REGISTERED
236 .into_iter()
237 .find(|name| self.additional.contains_key(*name))
238 }
239
240 #[must_use]
242 pub fn with_iss(mut self, issuer: impl Into<String>) -> Self {
243 self.iss = Some(issuer.into());
244 self
245 }
246
247 #[must_use]
249 pub fn with_sub(mut self, subject: impl Into<String>) -> Self {
250 self.sub = Some(subject.into());
251 self
252 }
253
254 #[must_use]
256 pub fn with_jti(mut self, id: impl Into<String>) -> Self {
257 self.jti = Some(id.into());
258 self
259 }
260
261 pub fn numeric_date(time: DateTime<Utc>) -> i64 {
263 time.timestamp()
264 }
265
266 pub fn timestamp(numeric_date: i64) -> Option<DateTime<Utc>> {
268 DateTime::from_timestamp(numeric_date, 0)
269 }
270
271 pub fn audiences(&self) -> impl Iterator<Item = &str> {
273 self.aud
274 .iter()
275 .flat_map(OneOrMany::iter)
276 .map(String::as_str)
277 }
278
279 #[must_use]
281 pub fn with_exp(mut self, time: DateTime<Utc>) -> Self {
282 self.exp = Some(Self::numeric_date(time));
283 self
284 }
285
286 #[must_use]
288 pub fn with_nbf(mut self, time: DateTime<Utc>) -> Self {
289 self.nbf = Some(Self::numeric_date(time));
290 self
291 }
292
293 #[must_use]
295 pub fn with_iat(mut self, time: DateTime<Utc>) -> Self {
296 self.iat = Some(Self::numeric_date(time));
297 self
298 }
299
300 #[must_use]
302 pub fn with_aud(mut self, audience: impl Into<String>) -> Self {
303 self.aud = Some(OneOrMany::One(audience.into()));
304 self
305 }
306
307 #[must_use]
312 pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
313 self.additional.insert(name.into(), value.into());
314 self
315 }
316}