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