1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
use alloc::{borrow::Cow, string::String};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::{NumericDate, StringOrStrings};
#[derive(Debug, PartialEq, Eq, Clone, Default, Serialize, Deserialize, Builder)]
#[cfg_attr(not(feature = "std"), builder(no_std))]
pub struct Claims {
/// The `"iss"` (issuer) claim identifies the principal that issued the
/// JWT. The processing of this claim is generally application specific.
/// The `"iss"` value is a case-sensitive string containing a StringOrURI
/// value.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1>
#[serde(rename = "iss", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub issuer: Option<String>,
/// The `"sub"` (subject) claim identifies the principal that is the
/// subject of the JWT. The claims in a JWT are normally statements
/// about the subject. The subject value MUST either be scoped to be
/// locally unique in the context of the issuer or be globally unique.
/// The processing of this claim is generally application specific. The
/// `"sub"` value is a case-sensitive string containing a StringOrURI
/// value.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2>
#[serde(rename = "sub", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub subject: Option<String>,
/// The `"aud"` (audience) claim identifies the recipients that the JWT is
/// intended for. Each principal intended to process the JWT MUST
/// identify itself with a value in the audience claim. If the principal
/// processing the claim does not identify itself with a value in the
/// "aud" claim when this claim is present, then the JWT MUST be
/// rejected. In the general case, the `"aud"` value is an array of case-
/// sensitive strings, each containing a StringOrURI value. In the
/// special case when the JWT has one audience, the `"aud"` value MAY be a
/// single case-sensitive string containing a StringOrURI value. The
/// interpretation of audience values is generally application specific.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3
#[serde(rename = "aud", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub audience: Option<StringOrStrings>,
/// The `"exp"` (expiration time) claim identifies the expiration time on
/// or after which the JWT MUST NOT be accepted for processing. The
/// processing of the `"exp"` claim requires that the current date/time
/// MUST be before the expiration date/time listed in the `"exp"` claim.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4>
#[serde(rename = "exp", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub expiration_time: Option<NumericDate>,
/// The `"nbf"` (not before) claim identifies the time before which the JWT
/// MUST NOT be accepted for processing. The processing of the `"nbf"`
/// claim requires that the current date/time MUST be after or equal to
/// the not-before date/time listed in the `"nbf"` claim. Implementers MAY
/// provide for some small leeway, usually no more than a few minutes, to
/// account for clock skew. Its value MUST be a number containing a
/// NumericDate value.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5>
#[serde(rename = "nbf", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub not_before: Option<NumericDate>,
/// The `"iat"` (issued at) claim identifies the time at which the JWT was
/// issued. This claim can be used to determine the age of the JWT. Its
/// value MUST be a number containing a NumericDate value.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6>
#[serde(rename = "iat", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub issued_at: Option<NumericDate>,
/// The `"jti"` (JWT ID) claim provides a unique identifier for the JWT.
/// The identifier value MUST be assigned in a manner that ensures that
/// there is a negligible probability that the same value will be
/// accidentally assigned to a different data object; if the application
/// uses multiple issuers, collisions MUST be prevented among values
/// produced by different issuers as well. The "jti" claim can be used
/// to prevent the JWT from being replayed. The "jti" value is a case-
/// sensitive string.
///
/// Use of this claim is OPTIONAL.
///
/// <https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7>
#[serde(rename = "jti", skip_serializing_if = "Option::is_none")]
#[builder(default, setter(into, strip_option))]
pub jwt_id: Option<String>,
#[serde(flatten)]
#[builder(default, try_setter, setter)]
pub additional_claims: serde_json::Map<String, Value>,
}
impl Claims {
pub fn builder() -> ClaimsBuilder {
ClaimsBuilder::default()
}
}
impl From<Claims> for Cow<'static, Claims> {
fn from(claims: Claims) -> Self {
Cow::Owned(claims)
}
}
impl ClaimsBuilder {
pub fn try_additional_claim<K, V>(
&mut self,
key: K,
value: V,
) -> Result<&mut Self, serde_json::Error>
where
K: Into<String>,
V: Serialize,
{
let key = key.into();
let value = serde_json::to_value(value)?;
Ok(self.additional_claim(key, value))
}
pub fn try_issuer<T: TryInto<String>>(mut self, issuer: T) -> Result<Self, T::Error> {
self.issuer = Some(Some(issuer.try_into()?));
Ok(self)
}
pub fn try_audience<T: TryInto<StringOrStrings>>(
&mut self,
aud: T,
) -> Result<&mut Self, T::Error> {
let aud = aud.try_into()?;
Ok(self.audience(aud))
}
pub fn try_expiration_time<T: TryInto<NumericDate>>(
&mut self,
exp: T,
) -> Result<&mut Self, T::Error> {
let exp = exp.try_into()?;
Ok(self.expiration_time(exp))
}
pub fn try_not_before<T: TryInto<NumericDate>>(
&mut self,
not_before: T,
) -> Result<&mut Self, T::Error> {
Ok(self.expiration_time(not_before.try_into()?))
}
pub fn try_issued_at<T: TryInto<NumericDate>>(
&mut self,
issued_at: T,
) -> Result<&mut Self, T::Error> {
Ok(self.expiration_time(issued_at.try_into()?))
}
pub fn try_jwt_id<T: TryInto<String>>(&mut self, jwt_id: T) -> Result<&mut Self, T::Error> {
Ok(self.jwt_id(jwt_id.try_into()?))
}
pub fn try_subject<T: TryInto<String>>(&mut self, sub: T) -> Result<&mut Self, T::Error> {
let sub = sub.try_into()?;
Ok(self.subject(sub))
}
pub fn add_audience<T: Into<String>>(mut self, audience: T) -> Self {
let mut audiences = self.audience.take().flatten();
if let Some(audiences) = audiences.as_mut() {
audiences.push(audience.into());
} else {
audiences = Some(audience.into().into());
}
self.audience = Some(audiences);
self
}
pub fn try_add_audience<T: TryInto<String>>(self, audience: T) -> Result<Self, T::Error> {
let audience = audience.try_into()?;
Ok(self.add_audience(audience))
}
pub fn additional_claim<K, V>(&mut self, key: K, value: V) -> &mut Self
where
K: Into<String>,
V: Into<Value>,
{
let key = key.into();
let mut additional_claims = self
.additional_claims
.take()
.unwrap_or(serde_json::Map::new());
additional_claims.insert(key, value.into());
self.additional_claims = Some(additional_claims);
self
}
}
#[cfg(test)]
mod tests {
use alloc::{string::ToString, vec};
use super::*;
#[test]
fn test_builder_additional_claim() {
let claims = Claims::builder()
.additional_claim("key1", "example value")
.additional_claim("key2", 3)
.audience(vec!["aud1", "aud2"])
.build()
.unwrap();
let mut expected = serde_json::Map::new();
expected.insert("key1".to_string(), "example value".into());
expected.insert("key2".to_string(), 3.into());
assert_eq!(claims.additional_claims, expected);
assert_eq!(
claims.audience,
Some(StringOrStrings::Strings(vec![
"aud1".to_string(),
"aud2".to_string()
]))
);
}
}