1use crate::config::{SaTokenConfig, TokenStyle};
8use crate::error::{SaTokenError, SaTokenResult};
9use crate::token::TokenValue;
10use crate::token::csprng::{fill_bytes, random_hex, random_tik};
11use crate::token::jwt::{JwtAlgorithm, JwtClaims, JwtManager};
12use chrono::Utc;
13use sha2::{Digest, Sha256};
14use uuid::Uuid;
15
16pub struct TokenGenerator;
18
19impl TokenGenerator {
20 pub fn generate_with_login_id(
22 config: &SaTokenConfig,
23 login_id: &str,
24 ) -> SaTokenResult<TokenValue> {
25 match config.token_style {
26 TokenStyle::Uuid => Ok(Self::generate_uuid()),
27 TokenStyle::SimpleUuid => Ok(Self::generate_simple_uuid()),
28 TokenStyle::Random32 => Self::generate_random_csprng(32),
29 TokenStyle::Random64 => Self::generate_random_csprng(64),
30 TokenStyle::Random128 => Self::generate_random_csprng(128),
31 TokenStyle::Jwt => Self::generate_jwt(config, login_id),
32 TokenStyle::Hash => Self::generate_hash(login_id),
33 TokenStyle::Timestamp => Self::generate_timestamp(),
34 TokenStyle::Tik => Self::generate_tik(),
35 }
36 }
37
38 pub fn generate_with_login_id_and_extra(
40 config: &SaTokenConfig,
41 login_id: &str,
42 extra_data: &serde_json::Value,
43 ) -> SaTokenResult<TokenValue> {
44 match config.token_style {
45 TokenStyle::Jwt => Self::generate_jwt_with_extra(config, login_id, extra_data),
46 _ => Self::generate_with_login_id(config, login_id),
47 }
48 }
49
50 pub fn generate(config: &SaTokenConfig) -> SaTokenResult<TokenValue> {
52 Self::generate_with_login_id(config, "")
53 }
54
55 pub fn generate_uuid() -> TokenValue {
57 TokenValue::new(Uuid::new_v4().to_string())
58 }
59
60 pub fn generate_simple_uuid() -> TokenValue {
62 TokenValue::new(Uuid::new_v4().simple().to_string())
63 }
64
65 pub fn generate_random_csprng(length: usize) -> SaTokenResult<TokenValue> {
68 Ok(TokenValue::new(random_hex(length)?))
69 }
70
71 pub fn generate_random(length: usize) -> SaTokenResult<TokenValue> {
74 Self::generate_random_csprng(length)
75 }
76
77 pub fn generate_jwt(config: &SaTokenConfig, login_id: &str) -> SaTokenResult<TokenValue> {
79 let secret = require_jwt_secret(config)?;
80 let effective_login_id = if login_id.is_empty() {
81 Utc::now().timestamp_millis().to_string()
82 } else {
83 login_id.to_string()
84 };
85 let algorithm = config
86 .jwt_algorithm
87 .as_ref()
88 .and_then(|alg| Self::parse_jwt_algorithm(alg))
89 .unwrap_or(JwtAlgorithm::HS256);
90 let mut jwt_manager = JwtManager::with_algorithm(secret, algorithm);
91 if let Some(ref issuer) = config.jwt_issuer {
92 jwt_manager = jwt_manager.set_issuer(issuer);
93 }
94 if let Some(ref audience) = config.jwt_audience {
95 jwt_manager = jwt_manager.set_audience(audience);
96 }
97 let mut claims = JwtClaims::new(effective_login_id);
98 if config.timeout > 0 {
99 claims.set_expiration(config.timeout);
100 }
101 match jwt_manager.generate(&claims) {
102 Ok(token) => Ok(TokenValue::new(token)),
103 Err(e) if config.jwt_fallback_on_error => {
104 tracing::warn!(error = %e, "JWT generation failed, falling back to UUID");
105 Ok(Self::generate_uuid())
106 }
107 Err(e) => Err(SaTokenError::ConfigError(format!(
108 "JWT generation failed: {e}"
109 ))),
110 }
111 }
112
113 pub fn generate_jwt_with_extra(
115 config: &SaTokenConfig,
116 login_id: &str,
117 extra_data: &serde_json::Value,
118 ) -> SaTokenResult<TokenValue> {
119 let secret = require_jwt_secret(config)?;
120 let effective_login_id = if login_id.is_empty() {
121 Utc::now().timestamp_millis().to_string()
122 } else {
123 login_id.to_string()
124 };
125 let algorithm = config
126 .jwt_algorithm
127 .as_ref()
128 .and_then(|alg| Self::parse_jwt_algorithm(alg))
129 .unwrap_or(JwtAlgorithm::HS256);
130 let mut jwt_manager = JwtManager::with_algorithm(secret, algorithm);
131 if let Some(ref issuer) = config.jwt_issuer {
132 jwt_manager = jwt_manager.set_issuer(issuer);
133 }
134 if let Some(ref audience) = config.jwt_audience {
135 jwt_manager = jwt_manager.set_audience(audience);
136 }
137 let mut claims = JwtClaims::new(effective_login_id);
138 if config.timeout > 0 {
139 claims.set_expiration(config.timeout);
140 }
141 match extra_data {
142 serde_json::Value::Object(map) => {
143 for (key, value) in map {
144 claims.add_claim(key.clone(), value.clone());
145 }
146 }
147 serde_json::Value::Null => {}
148 other => {
149 claims.add_claim("extra", other.clone());
150 }
151 }
152 match jwt_manager.generate(&claims) {
153 Ok(token) => Ok(TokenValue::new(token)),
154 Err(e) if config.jwt_fallback_on_error => {
155 tracing::warn!(error = %e, "JWT generation with extra failed, falling back to UUID");
156 Ok(Self::generate_uuid())
157 }
158 Err(e) => Err(SaTokenError::ConfigError(format!(
159 "JWT generation failed: {e}"
160 ))),
161 }
162 }
163
164 pub fn generate_hash(login_id: &str) -> SaTokenResult<TokenValue> {
166 let login_id_value = if login_id.is_empty() {
167 Utc::now().timestamp_millis().to_string()
168 } else {
169 login_id.to_string()
170 };
171 let mut salt = [0u8; 16];
172 fill_bytes(&mut salt)?;
173 let data = format!(
174 "{}{}{}",
175 login_id_value,
176 Utc::now().timestamp_millis(),
177 hex::encode(salt)
178 );
179 let mut hasher = Sha256::new();
180 hasher.update(data.as_bytes());
181 Ok(TokenValue::new(hex::encode(hasher.finalize())))
182 }
183
184 pub fn generate_timestamp() -> SaTokenResult<TokenValue> {
186 let timestamp = Utc::now().timestamp_millis();
187 let suffix = random_hex(16)?;
188 Ok(TokenValue::new(format!("{timestamp}_{suffix}")))
189 }
190
191 pub fn generate_tik() -> SaTokenResult<TokenValue> {
193 Ok(TokenValue::new(random_tik(8)?))
194 }
195
196 fn parse_jwt_algorithm(alg: &str) -> Option<JwtAlgorithm> {
197 match alg.to_uppercase().as_str() {
198 "HS256" => Some(JwtAlgorithm::HS256),
199 "HS384" => Some(JwtAlgorithm::HS384),
200 "HS512" => Some(JwtAlgorithm::HS512),
201 "RS256" => Some(JwtAlgorithm::RS256),
202 "RS384" => Some(JwtAlgorithm::RS384),
203 "RS512" => Some(JwtAlgorithm::RS512),
204 "ES256" => Some(JwtAlgorithm::ES256),
205 "ES384" => Some(JwtAlgorithm::ES384),
206 _ => None,
207 }
208 }
209}
210
211impl std::fmt::Debug for TokenGenerator {
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 f.write_str("TokenGenerator { .. }")
214 }
215}
216
217fn require_jwt_secret(config: &SaTokenConfig) -> SaTokenResult<&str> {
218 match config.jwt_secret_key.as_deref() {
219 Some(s) if !s.trim().is_empty() => Ok(s),
220 _ => Err(SaTokenError::ConfigError(
221 "jwt_secret_key is required when token_style=Jwt".into(),
222 )),
223 }
224}
225
226pub async fn generate_unique<C, F, Fut>(
234 max_try_times: i32,
235 mut create: C,
236 mut occupied: F,
237) -> SaTokenResult<TokenValue>
238where
239 C: FnMut() -> SaTokenResult<TokenValue>,
240 F: FnMut(&str) -> Fut,
241 Fut: Future<Output = SaTokenResult<bool>>,
242{
243 if max_try_times <= 0 {
244 return create();
245 }
246 let mut last = create()?;
247 for _ in 0..max_try_times {
248 if !occupied(last.as_str()).await? {
249 return Ok(last);
250 }
251 last = create()?;
252 }
253 if !occupied(last.as_str()).await? {
254 return Ok(last);
255 }
256 Err(SaTokenError::ConfigError(format!(
257 "failed to generate a unique token after {max_try_times} attempts"
258 )))
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::config::{SaTokenConfig, TokenStyle};
265 use crate::token::jwt::JwtManager;
266
267 fn jwt_config() -> SaTokenConfig {
268 SaTokenConfig {
269 token_style: TokenStyle::Jwt,
270 jwt_secret_key: Some("test-secret-key-for-jwt".to_string()),
271 timeout: 3600,
272 ..SaTokenConfig::default()
273 }
274 }
275
276 #[test]
277 fn test_generate_jwt_with_extra_object() {
278 let config = jwt_config();
279 let extra = serde_json::json!({
280 "role": "admin",
281 "tenant_id": 42,
282 "permissions": ["read", "write"]
283 });
284
285 let token = TokenGenerator::generate_jwt_with_extra(&config, "user_123", &extra).unwrap();
286 assert!(!token.as_str().is_empty());
287
288 let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
289 let claims = jwt_manager.validate(token.as_str()).unwrap();
290
291 assert_eq!(claims.login_id, "user_123");
292 assert_eq!(claims.get_claim("role"), Some(&serde_json::json!("admin")));
293 assert_eq!(claims.get_claim("tenant_id"), Some(&serde_json::json!(42)));
294 assert_eq!(
295 claims.get_claim("permissions"),
296 Some(&serde_json::json!(["read", "write"]))
297 );
298 }
299
300 #[test]
301 fn test_generate_jwt_with_extra_non_object() {
302 let config = jwt_config();
303 let extra = serde_json::json!("simple_string_value");
304
305 let token = TokenGenerator::generate_jwt_with_extra(&config, "user_456", &extra).unwrap();
306
307 let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
308 let claims = jwt_manager.validate(token.as_str()).unwrap();
309
310 assert_eq!(claims.login_id, "user_456");
311 assert_eq!(
312 claims.get_claim("extra"),
313 Some(&serde_json::json!("simple_string_value"))
314 );
315 }
316
317 #[test]
318 fn test_generate_jwt_with_extra_null() {
319 let config = jwt_config();
320 let extra = serde_json::Value::Null;
321
322 let token = TokenGenerator::generate_jwt_with_extra(&config, "user_789", &extra).unwrap();
323
324 let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
325 let claims = jwt_manager.validate(token.as_str()).unwrap();
326
327 assert_eq!(claims.login_id, "user_789");
328 assert!(claims.extra.is_empty());
329 }
330
331 #[test]
332 fn test_generate_with_login_id_and_extra_jwt_style() {
333 let config = jwt_config();
334 let extra = serde_json::json!({"key": "value"});
335
336 let token =
337 TokenGenerator::generate_with_login_id_and_extra(&config, "user_jwt", &extra).unwrap();
338
339 assert!(token.as_str().contains('.'));
340
341 let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
342 let claims = jwt_manager.validate(token.as_str()).unwrap();
343 assert_eq!(claims.get_claim("key"), Some(&serde_json::json!("value")));
344 }
345
346 #[test]
347 fn test_generate_with_login_id_and_extra_non_jwt_style() {
348 let config = SaTokenConfig {
349 token_style: TokenStyle::Uuid,
350 ..SaTokenConfig::default()
351 };
352 let extra = serde_json::json!({"key": "value"});
353
354 let token =
355 TokenGenerator::generate_with_login_id_and_extra(&config, "user_uuid", &extra).unwrap();
356 assert!(!token.as_str().is_empty());
357 assert!(!token.as_str().contains('.'));
358 }
359
360 #[test]
361 fn test_random_32_length() {
362 let config = SaTokenConfig {
363 token_style: TokenStyle::Random32,
364 ..SaTokenConfig::default()
365 };
366 let token = TokenGenerator::generate_with_login_id(&config, "user_random").unwrap();
367 assert!(!token.as_str().is_empty());
368 assert_eq!(token.as_str().len(), 32);
369 }
370
371 #[test]
372 fn test_random_64_length() {
373 let config = SaTokenConfig {
374 token_style: TokenStyle::Random64,
375 ..SaTokenConfig::default()
376 };
377 let token = TokenGenerator::generate_with_login_id(&config, "user_random").unwrap();
378 assert!(!token.as_str().is_empty());
379 assert_eq!(token.as_str().len(), 64);
380 }
381
382 #[test]
383 fn test_random_128_length() {
384 let config = SaTokenConfig {
385 token_style: TokenStyle::Random128,
386 ..SaTokenConfig::default()
387 };
388 let token = TokenGenerator::generate_with_login_id(&config, "user_random").unwrap();
389 assert!(!token.as_str().is_empty());
390 assert_eq!(token.as_str().len(), 128);
391 }
392}