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