clamber_core/token/
mod.rs1use crate::error::{ClamberError, Result};
2use chrono::{Duration, Utc};
3use hmac::{Hmac, Mac};
4use jwt::{SignWithKey, VerifyWithKey};
5use serde::{Serialize, de::DeserializeOwned};
6use sha2::Sha256;
7use std::collections::BTreeMap;
8
9#[derive(Debug, Clone)]
11pub struct JwtConfig {
12 pub secret: String,
14 pub expire_days: i64,
16}
17
18impl Default for JwtConfig {
19 fn default() -> Self {
20 Self {
21 secret: "default_jwt_secret".to_string(),
22 expire_days: 7,
23 }
24 }
25}
26
27impl JwtConfig {
28 pub fn new(secret: impl Into<String>, expire_days: i64) -> Self {
30 Self {
31 secret: secret.into(),
32 expire_days,
33 }
34 }
35}
36
37pub struct JwtManager {
39 config: JwtConfig,
40}
41
42impl JwtManager {
43 pub fn new(config: JwtConfig) -> Self {
45 Self { config }
46 }
47
48 pub fn default() -> Self {
50 Self {
51 config: JwtConfig::default(),
52 }
53 }
54
55 pub fn generate_token<T>(&self, payload: &T) -> Result<String>
57 where
58 T: Serialize,
59 {
60 let expire_time = Utc::now() + Duration::days(self.config.expire_days);
61
62 let payload_json = serde_json::to_string(payload)?;
64
65 let mut claims = BTreeMap::new();
66 claims.insert("payload".to_string(), payload_json);
67 claims.insert("exp".to_string(), expire_time.timestamp().to_string());
68 claims.insert("createAt".to_string(), Utc::now().timestamp().to_string());
69
70 let key: Hmac<Sha256> =
71 Hmac::new_from_slice(self.config.secret.as_bytes()).map_err(|e| {
72 ClamberError::JwtKeyError {
73 details: e.to_string(),
74 }
75 })?;
76
77 claims
78 .sign_with_key(&key)
79 .map_err(|e| ClamberError::JwtSignError {
80 details: e.to_string(),
81 })
82 }
83
84 pub fn verify_token<T>(&self, token: &str) -> Result<T>
86 where
87 T: DeserializeOwned,
88 {
89 let key: Hmac<Sha256> =
90 Hmac::new_from_slice(self.config.secret.as_bytes()).map_err(|e| {
91 ClamberError::JwtKeyError {
92 details: e.to_string(),
93 }
94 })?;
95
96 let claims: BTreeMap<String, String> =
97 token
98 .verify_with_key(&key)
99 .map_err(|e| ClamberError::JwtVerifyError {
100 details: e.to_string(),
101 })?;
102
103 if let Some(exp_str) = claims.get("exp") {
105 let exp_timestamp = exp_str.parse::<i64>().map_err(|_| ClamberError::JwtError {
106 message: "无效的过期时间格式".to_string(),
107 })?;
108
109 if exp_timestamp <= Utc::now().timestamp() {
110 return Err(ClamberError::JwtExpiredError);
111 }
112 } else {
113 return Err(ClamberError::JwtMissingFieldError {
114 field: "exp".to_string(),
115 });
116 }
117
118 if let Some(payload_str) = claims.get("payload") {
120 serde_json::from_str::<T>(payload_str).map_err(|e| ClamberError::DeserializationError {
121 details: e.to_string(),
122 })
123 } else {
124 Err(ClamberError::JwtMissingFieldError {
125 field: "payload".to_string(),
126 })
127 }
128 }
129
130 pub fn is_valid_token(&self, token: &str) -> bool {
132 let key = match Hmac::<Sha256>::new_from_slice(self.config.secret.as_bytes()) {
133 Ok(key) => key,
134 Err(_) => return false,
135 };
136
137 if let Ok(claims) = token.verify_with_key(&key) {
138 let claims: BTreeMap<String, String> = claims;
139 if let Some(exp_str) = claims.get("exp") {
140 if let Ok(exp_timestamp) = exp_str.parse::<i64>() {
141 return exp_timestamp > Utc::now().timestamp();
142 }
143 }
144 }
145 false
146 }
147}
148
149pub fn generate_token<T>(payload: &T, config: JwtConfig) -> Result<String>
151where
152 T: Serialize,
153{
154 let manager = JwtManager::new(config);
155 manager.generate_token(payload)
156}
157
158pub fn verify_token<T>(token: &str) -> Result<T>
159where
160 T: DeserializeOwned,
161{
162 let manager = JwtManager::default();
163 manager.verify_token(token)
164}
165
166pub fn is_valid_token(token: &str) -> bool {
167 let manager = JwtManager::default();
168 manager.is_valid_token(token)
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use serde::{Deserialize, Serialize};
175
176 #[derive(Debug, Serialize, Deserialize, PartialEq)]
177 struct TestUser {
178 pub id: String,
179 pub name: String,
180 pub role: String,
181 }
182
183 #[test]
184 fn test_jwt_generate_and_verify() {
185 let config = JwtConfig::new("test_secret", 1);
186 let manager = JwtManager::new(config);
187
188 let user = TestUser {
189 id: "123".to_string(),
190 name: "John Doe".to_string(),
191 role: "admin".to_string(),
192 };
193
194 let token = manager.generate_token(&user).unwrap();
196 assert!(!token.is_empty());
197
198 let decoded_user: TestUser = manager.verify_token(&token).unwrap();
200 assert_eq!(user, decoded_user);
201
202 assert!(manager.is_valid_token(&token));
204 }
205
206 #[test]
207 fn test_convenience_functions() {
208 let user = TestUser {
209 id: "456".to_string(),
210 name: "Jane Doe".to_string(),
211 role: "user".to_string(),
212 };
213
214 let token = generate_token(&user, JwtConfig::default()).unwrap();
215 let decoded_user: TestUser = verify_token(&token).unwrap();
216
217 assert_eq!(user, decoded_user);
218 assert!(is_valid_token(&token));
219 }
220
221 #[test]
222 fn test_invalid_token() {
223 let manager = JwtManager::default();
224
225 assert!(!manager.is_valid_token("invalid_token"));
227
228 let config1 = JwtConfig::new("secret1", 1);
230 let config2 = JwtConfig::new("secret2", 1);
231 let manager1 = JwtManager::new(config1);
232 let manager2 = JwtManager::new(config2);
233
234 let user = TestUser {
235 id: "789".to_string(),
236 name: "Test User".to_string(),
237 role: "test".to_string(),
238 };
239
240 let token = manager1.generate_token(&user).unwrap();
241 assert!(manager2.verify_token::<TestUser>(&token).is_err());
242 }
243}