1pub mod access_token;
4pub mod payload;
5
6use crate::server::axum::response::ApiError;
7use crate::server::axum::security::jwt::access_token::AccessToken;
8use crate::value_objects::datetime::UtcDateTime;
9use jsonwebtoken::errors::ErrorKind::ExpiredSignature;
10use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Validation, decode, encode};
11use serde::{Deserialize, Serialize};
12use std::fmt::{Debug, Formatter};
13use thiserror::Error;
14
15const JWT_ACCESS_LIFETIME_IN_MINUTES: i64 = 15; const JWT_REFRESH_LIFETIME_IN_HOURS: i64 = 7 * 24; #[derive(Debug, Clone, PartialEq, Error)]
20pub enum JwtError {
21 #[error("Parse token error: {0}")]
22 ParseError(String),
23
24 #[error("Generate token error: {0}")]
25 GenerateError(String),
26
27 #[error("Invalid or unsupported algorithm: {0}")]
28 InvalidAlgorithm(String),
29
30 #[error("Encoding key error: {0}")]
31 EncodingKeyError(String),
32
33 #[error("Decoding key error: {0}")]
34 DecodingKeyError(String),
35
36 #[error("Expired token")]
37 ExpiredToken,
38}
39
40impl From<JwtError> for ApiError {
42 fn from(value: JwtError) -> Self {
43 Self::InternalServerError(value.to_string())
44 }
45}
46
47#[derive(Clone)]
49pub struct Jwt {
50 algorithm: Algorithm,
52
53 access_lifetime: i64,
56
57 refresh_lifetime: i64,
60
61 encoding_key: Option<EncodingKey>,
63
64 decoding_key: Option<DecodingKey>,
66}
67
68impl Default for Jwt {
69 fn default() -> Self {
70 Self {
71 algorithm: Algorithm::HS512,
72 access_lifetime: JWT_ACCESS_LIFETIME_IN_MINUTES,
73 refresh_lifetime: JWT_REFRESH_LIFETIME_IN_HOURS,
74 encoding_key: None,
75 decoding_key: None,
76 }
77 }
78}
79
80impl Debug for Jwt {
81 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
82 write!(
83 f,
84 "JWT => algo: {:?}, access_lifetime: {}, refresh_lifetime: {}",
85 self.algorithm, self.access_lifetime, self.refresh_lifetime
86 )
87 }
88}
89
90impl Jwt {
91 pub fn init(
93 algorithm: &str,
94 access_lifetime: i64,
95 refresh_lifetime: i64,
96 secret: Option<&str>,
97 private_key: Option<&str>,
98 public_key: Option<&str>,
99 ) -> Result<Self, JwtError> {
100 let mut jwt = Jwt {
101 algorithm: Self::algorithm_from_str(algorithm)?,
102 access_lifetime,
103 refresh_lifetime,
104 ..Default::default()
105 };
106
107 match (secret, private_key, jwt.use_secret()) {
109 (Some(secret), _, true) => jwt.set_encoding_key(secret.trim())?,
110 (_, Some(private_key), false) => jwt.set_encoding_key(private_key.trim())?,
111 _ => return Err(JwtError::EncodingKeyError("invalid JWT encoding key".to_owned())),
112 }
113
114 match (secret, public_key, jwt.use_secret()) {
116 (Some(secret), _, true) => jwt.set_decoding_key(secret.trim())?,
117 (_, Some(public_key), false) => jwt.set_decoding_key(public_key.trim())?,
118 _ => return Err(JwtError::DecodingKeyError("invalid JWT decoding key".to_owned())),
119 }
120
121 Ok(jwt)
122 }
123
124 pub fn access_lifetime(&self) -> i64 {
126 self.access_lifetime
127 }
128
129 pub fn refresh_lifetime(&self) -> i64 {
131 self.refresh_lifetime
132 }
133
134 pub fn set_access_lifetime(&mut self, duration: i64) {
136 self.access_lifetime = duration;
137 }
138
139 pub fn set_refresh_lifetime(&mut self, duration: i64) {
141 self.refresh_lifetime = duration;
142 }
143
144 pub fn set_encoding_key(&mut self, secret: &str) -> Result<(), JwtError> {
146 let key = match self.algorithm {
147 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => EncodingKey::from_secret(secret.as_bytes()),
148 Algorithm::ES256 | Algorithm::ES384 => EncodingKey::from_ec_pem(secret.as_bytes())
149 .map_err(|err| JwtError::EncodingKeyError(err.to_string()))?,
150 Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => EncodingKey::from_rsa_pem(secret.as_bytes())
151 .map_err(|err| JwtError::EncodingKeyError(err.to_string()))?,
152 Algorithm::PS256 | Algorithm::PS384 | Algorithm::PS512 => EncodingKey::from_rsa_pem(secret.as_bytes())
153 .map_err(|err| JwtError::EncodingKeyError(err.to_string()))?,
154 Algorithm::EdDSA => EncodingKey::from_ed_pem(secret.as_bytes())
155 .map_err(|err| JwtError::EncodingKeyError(err.to_string()))?,
156 };
157
158 self.encoding_key = Some(key);
159
160 Ok(())
161 }
162
163 pub fn set_decoding_key(&mut self, secret: &str) -> Result<(), JwtError> {
165 let key = match self.algorithm {
166 Algorithm::HS256 | Algorithm::HS384 | Algorithm::HS512 => DecodingKey::from_secret(secret.as_bytes()),
167 Algorithm::ES256 | Algorithm::ES384 => DecodingKey::from_ec_pem(secret.as_bytes())
168 .map_err(|err| JwtError::DecodingKeyError(err.to_string()))?,
169 Algorithm::RS256 | Algorithm::RS384 | Algorithm::RS512 => DecodingKey::from_rsa_pem(secret.as_bytes())
170 .map_err(|err| JwtError::DecodingKeyError(err.to_string()))?,
171 Algorithm::PS256 | Algorithm::PS384 | Algorithm::PS512 => DecodingKey::from_rsa_pem(secret.as_bytes())
172 .map_err(|err| JwtError::DecodingKeyError(err.to_string()))?,
173 Algorithm::EdDSA => DecodingKey::from_ed_pem(secret.as_bytes())
174 .map_err(|err| JwtError::DecodingKeyError(err.to_string()))?,
175 };
176
177 self.decoding_key = Some(key);
178
179 Ok(())
180 }
181
182 pub fn generate<P: Debug + Serialize>(&self, payload: P, expired_at: UtcDateTime) -> Result<AccessToken, JwtError> {
184 let header = jsonwebtoken::Header::new(self.algorithm);
185
186 match self.encoding_key.clone() {
187 Some(encoding_key) => {
188 let token = encode(&header, &payload, &encoding_key)
189 .map_err(|err| JwtError::EncodingKeyError(err.to_string()))?;
190
191 Ok(AccessToken { token, expired_at })
192 }
193 _ => Err(JwtError::EncodingKeyError("empty key".to_owned())),
194 }
195 }
196
197 pub fn parse<P: Clone + Debug + for<'de> Deserialize<'de>>(&self, token: &AccessToken) -> Result<P, JwtError> {
199 let validation = Validation::new(self.algorithm);
200
201 match self.decoding_key.clone() {
202 Some(decoding_key) => {
203 let token = decode::<P>(&token.token, &decoding_key, &validation).map_err(|err| match err.kind() {
204 ExpiredSignature => JwtError::ExpiredToken,
205 _ => JwtError::DecodingKeyError(err.to_string()),
206 })?;
207
208 Ok(token.claims)
209 }
210 _ => Err(JwtError::DecodingKeyError("empty key".to_owned())),
211 }
212 }
213
214 fn use_secret(&self) -> bool {
216 self.algorithm == Algorithm::HS256 || self.algorithm == Algorithm::HS384 || self.algorithm == Algorithm::HS512
217 }
218
219 fn algorithm_from_str(algo: &str) -> Result<Algorithm, JwtError> {
221 Ok(match algo {
222 "HS256" => Algorithm::HS256,
223 "HS384" => Algorithm::HS384,
224 "HS512" => Algorithm::HS512,
225 "ES256" => Algorithm::ES256,
226 "ES384" => Algorithm::ES384,
227 _ => {
228 return Err(JwtError::InvalidAlgorithm(algo.to_string()));
229 }
230 })
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237
238 #[test]
239 fn test_jwt_use_secret() {
240 let jwt = Jwt::default();
241 assert!(jwt.use_secret());
242
243 let mut jwt = Jwt {
244 algorithm: Algorithm::ES256,
245 ..Default::default()
246 };
247 assert!(!jwt.use_secret());
248
249 jwt.algorithm = Algorithm::HS256;
250 assert!(jwt.use_secret());
251 }
252
253 #[test]
254 fn test_jwt_algorithm_from_str() {
255 assert_eq!(Jwt::algorithm_from_str("HS256").unwrap(), Algorithm::HS256);
256 assert_eq!(Jwt::algorithm_from_str("HS384").unwrap(), Algorithm::HS384);
257 assert_eq!(Jwt::algorithm_from_str("HS512").unwrap(), Algorithm::HS512);
258 assert_eq!(Jwt::algorithm_from_str("ES256").unwrap(), Algorithm::ES256);
259 assert_eq!(Jwt::algorithm_from_str("ES384").unwrap(), Algorithm::ES384);
260
261 let invalid_algo = Jwt::algorithm_from_str("ES512");
262 assert!(invalid_algo.is_err());
263 if let Err(e) = invalid_algo {
264 assert_eq!(e, JwtError::InvalidAlgorithm("ES512".to_string()));
265 }
266 }
267
268 #[test]
269 fn test_jwt_default() {
270 let jwt = Jwt::default();
271 assert_eq!(jwt.algorithm, Algorithm::HS512);
272 assert_eq!(jwt.access_lifetime, JWT_ACCESS_LIFETIME_IN_MINUTES);
273 assert_eq!(jwt.refresh_lifetime, JWT_REFRESH_LIFETIME_IN_HOURS);
274 assert!(jwt.encoding_key.is_none());
275 assert!(jwt.decoding_key.is_none());
276 }
277
278 #[test]
279 fn test_jwt_debug() {
280 let jwt = Jwt::default();
281 let debug_str = format!("{:?}", jwt);
282
283 assert_eq!(
284 debug_str,
285 format!("JWT => algo: HS512, access_lifetime: 15, refresh_lifetime: {}", 7 * 24)
286 );
287 }
288
289 #[test]
290 fn test_jwt_use_secret_all_algorithms() {
291 for algo in [Algorithm::HS256, Algorithm::HS384, Algorithm::HS512] {
292 let jwt = Jwt {
293 algorithm: algo,
294 ..Default::default()
295 };
296 assert!(jwt.use_secret(), "{algo:?} must use secret");
297 }
298 for algo in [
299 Algorithm::ES256,
300 Algorithm::ES384,
301 Algorithm::RS256,
302 Algorithm::RS384,
303 Algorithm::RS512,
304 Algorithm::PS256,
305 Algorithm::PS384,
306 Algorithm::PS512,
307 Algorithm::EdDSA,
308 ] {
309 let jwt = Jwt {
310 algorithm: algo,
311 ..Default::default()
312 };
313 assert!(!jwt.use_secret(), "{algo:?} must use a key pair");
314 }
315 }
316
317 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
318 struct TestClaims {
319 sub: String,
320 exp: i64,
321 }
322
323 fn future_exp(seconds: i64) -> i64 {
324 chrono::Utc::now().timestamp() + seconds
325 }
326
327 #[test]
328 fn test_jwt_init_and_round_trip_hs512() {
329 let jwt = Jwt::init("HS512", 15, 7 * 24, Some("super-secret"), None, None).expect("init");
330 let claims = TestClaims {
331 sub: "user-42".to_string(),
332 exp: future_exp(60),
333 };
334 let token = jwt.generate(claims.clone(), UtcDateTime::now()).expect("generate");
335 assert!(!token.token.is_empty());
336
337 let parsed: TestClaims = jwt.parse(&token).expect("parse");
338 assert_eq!(parsed, claims);
339 }
340
341 #[test]
342 fn test_jwt_parse_expired_token() {
343 let jwt = Jwt::init("HS256", 15, 7 * 24, Some("secret"), None, None).expect("init");
344 let claims = TestClaims {
345 sub: "user".to_string(),
346 exp: future_exp(-300),
348 };
349 let token = jwt.generate(claims, UtcDateTime::now()).expect("generate");
350
351 let err = jwt.parse::<TestClaims>(&token).unwrap_err();
352 assert_eq!(err, JwtError::ExpiredToken);
353 }
354
355 #[test]
356 fn test_jwt_init_invalid_algorithm() {
357 let err = Jwt::init("FOO", 15, 7 * 24, Some("secret"), None, None).unwrap_err();
358 assert_eq!(err, JwtError::InvalidAlgorithm("FOO".to_string()));
359 }
360
361 #[test]
362 fn test_jwt_init_hs256_missing_secret() {
363 let err = Jwt::init("HS256", 15, 7 * 24, None, None, None).unwrap_err();
364 assert!(matches!(err, JwtError::EncodingKeyError(_)));
365 }
366
367 #[test]
368 fn test_jwt_init_es256_with_secret_only_fails() {
369 let err = Jwt::init("ES256", 15, 7 * 24, Some("secret"), None, None).unwrap_err();
372 assert!(matches!(err, JwtError::EncodingKeyError(_)));
373 }
374
375 #[test]
376 fn test_jwt_generate_without_encoding_key() {
377 let jwt = Jwt::default();
380 let claims = TestClaims {
381 sub: "user".to_string(),
382 exp: future_exp(60),
383 };
384 let err = jwt.generate(claims, UtcDateTime::now()).unwrap_err();
385 assert!(matches!(err, JwtError::EncodingKeyError(_)));
386 }
387
388 #[test]
389 fn jwt_error_into_api_error_maps_to_internal_server_error() {
390 let api_err: ApiError = JwtError::ExpiredToken.into();
391 assert!(matches!(api_err, ApiError::InternalServerError(_)));
392 let ApiError::InternalServerError(msg) = api_err else {
394 unreachable!()
395 };
396 assert_eq!(msg, "Expired token");
397 }
398
399 #[test]
400 fn lifetime_getters_and_setters_round_trip() {
401 let mut jwt = Jwt::default();
402 assert_eq!(jwt.access_lifetime(), JWT_ACCESS_LIFETIME_IN_MINUTES);
403 assert_eq!(jwt.refresh_lifetime(), JWT_REFRESH_LIFETIME_IN_HOURS);
404
405 jwt.set_access_lifetime(30);
406 jwt.set_refresh_lifetime(48);
407 assert_eq!(jwt.access_lifetime(), 30);
408 assert_eq!(jwt.refresh_lifetime(), 48);
409 }
410
411 #[test]
415 fn set_encoding_and_decoding_keys_reject_invalid_pem_for_each_algorithm() {
416 for algo in [
417 Algorithm::ES256,
418 Algorithm::ES384,
419 Algorithm::RS256,
420 Algorithm::RS384,
421 Algorithm::RS512,
422 Algorithm::PS256,
423 Algorithm::PS384,
424 Algorithm::PS512,
425 Algorithm::EdDSA,
426 ] {
427 let mut jwt = Jwt {
428 algorithm: algo,
429 ..Default::default()
430 };
431 assert!(
432 matches!(
433 jwt.set_encoding_key("not-a-real-pem").unwrap_err(),
434 JwtError::EncodingKeyError(_),
435 ),
436 "set_encoding_key({algo:?}) should report EncodingKeyError",
437 );
438 assert!(
439 matches!(
440 jwt.set_decoding_key("not-a-real-pem").unwrap_err(),
441 JwtError::DecodingKeyError(_),
442 ),
443 "set_decoding_key({algo:?}) should report DecodingKeyError",
444 );
445 }
446 }
447
448 #[test]
449 fn init_es256_with_invalid_private_key_fails_at_encoding_stage() {
450 let err = Jwt::init("ES256", 15, 7 * 24, None, Some("not-a-pem"), None).unwrap_err();
453 assert!(matches!(err, JwtError::EncodingKeyError(_)));
454 }
455
456 #[test]
457 fn parse_without_decoding_key_returns_decoding_error() {
458 let jwt = Jwt::default();
460 let dummy = AccessToken {
461 token: "irrelevant".to_string(),
462 expired_at: UtcDateTime::now(),
463 };
464 let err = jwt.parse::<TestClaims>(&dummy).unwrap_err();
465 assert!(matches!(err, JwtError::DecodingKeyError(_)));
466 }
467
468 #[test]
469 fn test_jwt_parse_with_wrong_secret_fails() {
470 let issuer = Jwt::init("HS256", 15, 7 * 24, Some("secret-A"), None, None).expect("init");
471 let verifier = Jwt::init("HS256", 15, 7 * 24, Some("secret-B"), None, None).expect("init");
472 let claims = TestClaims {
473 sub: "user".to_string(),
474 exp: future_exp(60),
475 };
476 let token = issuer.generate(claims, UtcDateTime::now()).expect("generate");
477
478 let err = verifier.parse::<TestClaims>(&token).unwrap_err();
479 assert!(matches!(err, JwtError::DecodingKeyError(_)));
480 }
481}