1use chrono::{DateTime, Duration, Utc};
43use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
44use serde::{Deserialize, Serialize};
45use serde_json::Value;
46use std::collections::HashMap;
47
48use crate::error::{SaTokenError, SaTokenResult};
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
52pub enum JwtAlgorithm {
53 #[default]
55 HS256,
56 HS384,
58 HS512,
60 RS256,
62 RS384,
64 RS512,
66 ES256,
68 ES384,
70}
71
72impl From<JwtAlgorithm> for Algorithm {
73 fn from(alg: JwtAlgorithm) -> Self {
74 match alg {
75 JwtAlgorithm::HS256 => Algorithm::HS256,
76 JwtAlgorithm::HS384 => Algorithm::HS384,
77 JwtAlgorithm::HS512 => Algorithm::HS512,
78 JwtAlgorithm::RS256 => Algorithm::RS256,
79 JwtAlgorithm::RS384 => Algorithm::RS384,
80 JwtAlgorithm::RS512 => Algorithm::RS512,
81 JwtAlgorithm::ES256 => Algorithm::ES256,
82 JwtAlgorithm::ES384 => Algorithm::ES384,
83 }
84 }
85}
86
87#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct JwtClaims {
93 #[serde(rename = "sub")]
95 pub login_id: String,
96
97 #[serde(skip_serializing_if = "Option::is_none")]
99 pub iss: Option<String>,
100
101 #[serde(skip_serializing_if = "Option::is_none")]
103 pub aud: Option<String>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub exp: Option<i64>,
108
109 #[serde(skip_serializing_if = "Option::is_none")]
111 pub nbf: Option<i64>,
112
113 #[serde(skip_serializing_if = "Option::is_none")]
115 pub iat: Option<i64>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub jti: Option<String>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
124 pub login_type: Option<String>,
125
126 #[serde(skip_serializing_if = "Option::is_none")]
128 pub device: Option<String>,
129
130 #[serde(default)]
132 #[serde(skip_serializing_if = "HashMap::is_empty")]
133 pub extra: HashMap<String, Value>,
134}
135
136impl JwtClaims {
137 pub fn new(login_id: impl Into<String>) -> Self {
143 let now = Utc::now().timestamp();
144 Self {
145 login_id: login_id.into(),
146 iss: None,
147 aud: None,
148 exp: None,
149 nbf: None,
150 iat: Some(now),
151 jti: None,
152 login_type: Some("default".to_string()),
153 device: None,
154 extra: HashMap::new(),
155 }
156 }
157
158 pub fn set_expiration(&mut self, seconds: i64) -> &mut Self {
164 let exp_time = Utc::now() + Duration::seconds(seconds);
165 self.exp = Some(exp_time.timestamp());
166 self
167 }
168
169 pub fn set_expiration_at(&mut self, datetime: DateTime<Utc>) -> &mut Self {
171 self.exp = Some(datetime.timestamp());
172 self
173 }
174
175 pub fn set_issuer(&mut self, issuer: impl Into<String>) -> &mut Self {
177 self.iss = Some(issuer.into());
178 self
179 }
180
181 pub fn set_audience(&mut self, audience: impl Into<String>) -> &mut Self {
183 self.aud = Some(audience.into());
184 self
185 }
186
187 pub fn set_jti(&mut self, jti: impl Into<String>) -> &mut Self {
189 self.jti = Some(jti.into());
190 self
191 }
192
193 pub fn set_login_type(&mut self, login_type: impl Into<String>) -> &mut Self {
195 self.login_type = Some(login_type.into());
196 self
197 }
198
199 pub fn set_device(&mut self, device: impl Into<String>) -> &mut Self {
201 self.device = Some(device.into());
202 self
203 }
204
205 pub fn add_claim(&mut self, key: impl Into<String>, value: Value) -> &mut Self {
207 self.extra.insert(key.into(), value);
208 self
209 }
210
211 pub fn get_claim(&self, key: &str) -> Option<&Value> {
213 self.extra.get(key)
214 }
215
216 pub fn set_claims(&mut self, claims: HashMap<String, Value>) -> &mut Self {
218 self.extra = claims;
219 self
220 }
221
222 pub fn get_claims(&self) -> &HashMap<String, Value> {
224 &self.extra
225 }
226
227 pub fn is_expired(&self) -> bool {
229 if let Some(exp) = self.exp {
230 let now = Utc::now().timestamp();
231 now >= exp
232 } else {
233 false
234 }
235 }
236
237 pub fn remaining_time(&self) -> Option<i64> {
239 self.exp.map(|exp| {
240 let now = Utc::now().timestamp();
241 (exp - now).max(0)
242 })
243 }
244}
245
246#[derive(Clone)]
251pub struct JwtManager {
252 secret: String,
254
255 algorithm: JwtAlgorithm,
257
258 issuer: Option<String>,
260
261 audience: Option<String>,
263}
264
265impl std::fmt::Debug for JwtManager {
266 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 f.write_str("JwtManager { .. }")
268 }
269}
270
271impl JwtManager {
272 pub fn new(secret: impl Into<String>) -> Self {
278 Self {
279 secret: secret.into(),
280 algorithm: JwtAlgorithm::HS256,
281 issuer: None,
282 audience: None,
283 }
284 }
285
286 pub fn with_algorithm(secret: impl Into<String>, algorithm: JwtAlgorithm) -> Self {
288 Self {
289 secret: secret.into(),
290 algorithm,
291 issuer: None,
292 audience: None,
293 }
294 }
295
296 pub fn set_issuer(mut self, issuer: impl Into<String>) -> Self {
298 self.issuer = Some(issuer.into());
299 self
300 }
301
302 pub fn set_audience(mut self, audience: impl Into<String>) -> Self {
304 self.audience = Some(audience.into());
305 self
306 }
307
308 pub fn generate(&self, claims: &JwtClaims) -> SaTokenResult<String> {
318 let mut final_claims = claims.clone();
319
320 if self.issuer.is_some() && final_claims.iss.is_none() {
323 final_claims.iss = self.issuer.clone();
324 }
325 if self.audience.is_some() && final_claims.aud.is_none() {
326 final_claims.aud = self.audience.clone();
327 }
328
329 let header = Header::new(self.algorithm.into());
330 let encoding_key = EncodingKey::from_secret(self.secret.as_bytes());
331
332 encode(&header, &final_claims, &encoding_key)
333 .map_err(|e| SaTokenError::InvalidToken(format!("Failed to generate JWT: {}", e)))
334 }
335
336 pub fn validate(&self, token: &str) -> SaTokenResult<JwtClaims> {
346 let mut validation = Validation::new(self.algorithm.into());
347
348 validation.validate_exp = true;
350
351 validation.leeway = 0;
353
354 if let Some(ref iss) = self.issuer {
356 validation.set_issuer(&[iss]);
357 }
358 if let Some(ref aud) = self.audience {
359 validation.set_audience(&[aud]);
360 }
361
362 let decoding_key = DecodingKey::from_secret(self.secret.as_bytes());
363
364 let token_data =
365 decode::<JwtClaims>(token, &decoding_key, &validation).map_err(|e| match e.kind() {
366 jsonwebtoken::errors::ErrorKind::ExpiredSignature => SaTokenError::TokenExpired,
367 _ => SaTokenError::InvalidToken(format!("JWT validation failed: {}", e)),
368 })?;
369
370 Ok(token_data.claims)
371 }
372
373 pub fn decode_without_validation(&self, token: &str) -> SaTokenResult<JwtClaims> {
378 let token_data = jsonwebtoken::dangerous::insecure_decode::<JwtClaims>(token)
381 .map_err(|e| SaTokenError::InvalidToken(format!("Failed to decode JWT: {}", e)))?;
382
383 Ok(token_data.claims)
384 }
385
386 pub fn refresh(&self, token: &str, extend_seconds: i64) -> SaTokenResult<String> {
396 let mut claims = self.validate(token)?;
397
398 claims.set_expiration(extend_seconds);
400
401 claims.iat = Some(Utc::now().timestamp());
403
404 self.generate(&claims)
405 }
406
407 pub fn extract_login_id(&self, token: &str) -> SaTokenResult<String> {
412 let claims = self.decode_without_validation(token)?;
413 Ok(claims.login_id)
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_jwt_claims_creation() {
423 let mut claims = JwtClaims::new("user_123");
424 claims.set_expiration(3600);
425 claims.set_issuer("sa-token");
426 claims.add_claim("role", serde_json::json!("admin"));
427
428 assert_eq!(claims.login_id, "user_123");
429 assert!(claims.exp.is_some());
430 assert_eq!(claims.iss, Some("sa-token".to_string()));
431 assert_eq!(claims.get_claim("role"), Some(&serde_json::json!("admin")));
432 }
433
434 #[test]
435 fn test_jwt_generate_and_validate() {
436 let jwt_manager = JwtManager::new("test-secret-key");
437
438 let mut claims = JwtClaims::new("user_123");
439 claims.set_expiration(3600);
440
441 let token = jwt_manager.generate(&claims).unwrap();
443 assert!(!token.is_empty());
444
445 let decoded = jwt_manager.validate(&token).unwrap();
447 assert_eq!(decoded.login_id, "user_123");
448 assert!(!decoded.is_expired());
449 }
450
451 #[test]
452 fn test_jwt_expired() {
453 let jwt_manager = JwtManager::new("test-secret-key");
454
455 let mut claims = JwtClaims::new("user_123");
456 let exp_time = Utc::now() - Duration::seconds(10);
459 claims.set_expiration_at(exp_time);
460
461 let token = jwt_manager.generate(&claims).unwrap();
462
463 let result = jwt_manager.validate(&token);
465 assert!(result.is_err());
466
467 match result {
469 Err(SaTokenError::TokenExpired) => {} _ => panic!("Expected TokenExpired error"),
471 }
472 }
473
474 #[test]
475 fn test_jwt_refresh() {
476 let jwt_manager = JwtManager::new("test-secret-key");
477
478 let mut claims = JwtClaims::new("user_123");
479 claims.set_expiration(3600);
480
481 let original_token = jwt_manager.generate(&claims).unwrap();
482
483 let new_token = jwt_manager.refresh(&original_token, 7200).unwrap();
485 assert_ne!(original_token, new_token);
486
487 let decoded = jwt_manager.validate(&new_token).unwrap();
489 assert_eq!(decoded.login_id, "user_123");
490 }
491
492 #[test]
493 fn test_jwt_custom_claims() {
494 let jwt_manager = JwtManager::new("test-secret-key");
495
496 let mut claims = JwtClaims::new("user_123");
497 claims.set_expiration(3600);
498 claims.add_claim("role", serde_json::json!("admin"));
499 claims.add_claim("permissions", serde_json::json!(["read", "write"]));
500
501 let token = jwt_manager.generate(&claims).unwrap();
502 let decoded = jwt_manager.validate(&token).unwrap();
503
504 assert_eq!(decoded.get_claim("role"), Some(&serde_json::json!("admin")));
505 assert_eq!(
506 decoded.get_claim("permissions"),
507 Some(&serde_json::json!(["read", "write"]))
508 );
509 }
510
511 #[test]
512 fn test_extract_login_id() {
513 let jwt_manager = JwtManager::new("test-secret-key");
514
515 let mut claims = JwtClaims::new("user_123");
516 claims.set_expiration(3600);
517
518 let token = jwt_manager.generate(&claims).unwrap();
519 let login_id = jwt_manager.extract_login_id(&token).unwrap();
520
521 assert_eq!(login_id, "user_123");
522 }
523}