use uuid::Uuid;
use crate::config::{TokenStyle, SaTokenConfig};
use crate::token::TokenValue;
use crate::token::jwt::{JwtManager, JwtClaims, JwtAlgorithm};
use chrono::Utc;
use sha2::{Sha256, Sha512, Digest};
pub struct TokenGenerator;
impl TokenGenerator {
pub fn generate_with_login_id(config: &SaTokenConfig, login_id: &str) -> TokenValue {
match config.token_style {
TokenStyle::Uuid => Self::generate_uuid(),
TokenStyle::SimpleUuid => Self::generate_simple_uuid(),
TokenStyle::Random32 => Self::generate_random(32),
TokenStyle::Random64 => Self::generate_random(64),
TokenStyle::Random128 => Self::generate_random(128),
TokenStyle::Jwt => Self::generate_jwt(config, login_id),
TokenStyle::Hash => Self::generate_hash(login_id),
TokenStyle::Timestamp => Self::generate_timestamp(),
TokenStyle::Tik => Self::generate_tik(),
}
}
pub fn generate_with_login_id_and_extra(
config: &SaTokenConfig,
login_id: &str,
extra_data: &serde_json::Value,
) -> TokenValue {
match config.token_style {
TokenStyle::Jwt => Self::generate_jwt_with_extra(config, login_id, extra_data),
_ => Self::generate_with_login_id(config, login_id),
}
}
pub fn generate(config: &SaTokenConfig) -> TokenValue {
Self::generate_with_login_id(config, "")
}
pub fn generate_uuid() -> TokenValue {
TokenValue::new(Uuid::new_v4().to_string())
}
pub fn generate_simple_uuid() -> TokenValue {
TokenValue::new(Uuid::new_v4().simple().to_string())
}
pub fn generate_random(length: usize) -> TokenValue {
let uuid = Uuid::new_v4();
let random_bytes = uuid.as_bytes();
let hash = Sha512::digest(random_bytes);
let hex_string = hex::encode(hash);
TokenValue::new(hex_string[..length.min(hex_string.len())].to_string())
}
pub fn generate_jwt(config: &SaTokenConfig, login_id: &str) -> TokenValue {
let effective_login_id = if login_id.is_empty() {
Utc::now().timestamp_millis().to_string()
} else {
login_id.to_string()
};
let secret = config.jwt_secret_key.as_ref()
.expect("JWT secret key is required when using JWT token style");
let algorithm = config.jwt_algorithm.as_ref()
.and_then(|alg| Self::parse_jwt_algorithm(alg))
.unwrap_or(JwtAlgorithm::HS256);
let mut jwt_manager = JwtManager::with_algorithm(secret, algorithm);
if let Some(ref issuer) = config.jwt_issuer {
jwt_manager = jwt_manager.set_issuer(issuer);
}
if let Some(ref audience) = config.jwt_audience {
jwt_manager = jwt_manager.set_audience(audience);
}
let mut claims = JwtClaims::new(effective_login_id);
if config.timeout > 0 {
claims.set_expiration(config.timeout);
}
match jwt_manager.generate(&claims) {
Ok(token) => TokenValue::new(token),
Err(e) => {
eprintln!("Failed to generate JWT token: {:?}", e);
Self::generate_uuid()
}
}
}
pub fn generate_jwt_with_extra(
config: &SaTokenConfig,
login_id: &str,
extra_data: &serde_json::Value,
) -> TokenValue {
let effective_login_id = if login_id.is_empty() {
Utc::now().timestamp_millis().to_string()
} else {
login_id.to_string()
};
let secret = config.jwt_secret_key.as_ref()
.expect("JWT secret key is required when using JWT token style");
let algorithm = config.jwt_algorithm.as_ref()
.and_then(|alg| Self::parse_jwt_algorithm(alg))
.unwrap_or(JwtAlgorithm::HS256);
let mut jwt_manager = JwtManager::with_algorithm(secret, algorithm);
if let Some(ref issuer) = config.jwt_issuer {
jwt_manager = jwt_manager.set_issuer(issuer);
}
if let Some(ref audience) = config.jwt_audience {
jwt_manager = jwt_manager.set_audience(audience);
}
let mut claims = JwtClaims::new(effective_login_id);
if config.timeout > 0 {
claims.set_expiration(config.timeout);
}
match extra_data {
serde_json::Value::Object(map) => {
for (key, value) in map {
claims.add_claim(key.clone(), value.clone());
}
}
serde_json::Value::Null => {
}
other => {
claims.add_claim("extra", other.clone());
}
}
match jwt_manager.generate(&claims) {
Ok(token) => TokenValue::new(token),
Err(e) => {
eprintln!("Failed to generate JWT token with extra: {:?}", e);
Self::generate_uuid()
}
}
}
fn parse_jwt_algorithm(alg: &str) -> Option<JwtAlgorithm> {
match alg.to_uppercase().as_str() {
"HS256" => Some(JwtAlgorithm::HS256),
"HS384" => Some(JwtAlgorithm::HS384),
"HS512" => Some(JwtAlgorithm::HS512),
"RS256" => Some(JwtAlgorithm::RS256),
"RS384" => Some(JwtAlgorithm::RS384),
"RS512" => Some(JwtAlgorithm::RS512),
"ES256" => Some(JwtAlgorithm::ES256),
"ES384" => Some(JwtAlgorithm::ES384),
_ => None,
}
}
pub fn generate_hash(login_id: &str) -> TokenValue {
let login_id_value = if login_id.is_empty() {
Utc::now().timestamp_millis().to_string()
} else {
login_id.to_string()
};
let timestamp = Utc::now().timestamp_millis();
let uuid = Uuid::new_v4();
let data = format!("{}{}{}", login_id_value, timestamp, uuid);
let mut hasher = Sha256::new();
hasher.update(data.as_bytes());
let result = hasher.finalize();
let hash = hex::encode(result);
TokenValue::new(hash)
}
pub fn generate_timestamp() -> TokenValue {
use chrono::Utc;
use sha2::{Sha256, Digest};
let timestamp = Utc::now().timestamp_millis();
let uuid = Uuid::new_v4();
let mut hasher = Sha256::new();
hasher.update(uuid.as_bytes());
let result = hasher.finalize();
let suffix = hex::encode(&result[..8]);
TokenValue::new(format!("{}_{}", timestamp, suffix))
}
pub fn generate_tik() -> TokenValue {
use sha2::{Sha256, Digest};
const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const TOKEN_LENGTH: usize = 8;
let uuid = Uuid::new_v4();
let mut hasher = Sha256::new();
hasher.update(uuid.as_bytes());
let hash = hasher.finalize();
let mut token = String::with_capacity(TOKEN_LENGTH);
for i in 0..TOKEN_LENGTH {
let idx = (hash[i] as usize) % CHARSET.len();
token.push(CHARSET[idx] as char);
}
TokenValue::new(token)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::{SaTokenConfig, TokenStyle};
use crate::token::jwt::JwtManager;
fn jwt_config() -> SaTokenConfig {
SaTokenConfig {
token_style: TokenStyle::Jwt,
jwt_secret_key: Some("test-secret-key-for-jwt".to_string()),
timeout: 3600,
..SaTokenConfig::default()
}
}
#[test]
fn test_generate_jwt_with_extra_object() {
let config = jwt_config();
let extra = serde_json::json!({
"role": "admin",
"tenant_id": 42,
"permissions": ["read", "write"]
});
let token = TokenGenerator::generate_jwt_with_extra(&config, "user_123", &extra);
assert!(!token.as_str().is_empty());
let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
let claims = jwt_manager.validate(token.as_str()).unwrap();
assert_eq!(claims.login_id, "user_123");
assert_eq!(claims.get_claim("role"), Some(&serde_json::json!("admin")));
assert_eq!(claims.get_claim("tenant_id"), Some(&serde_json::json!(42)));
assert_eq!(
claims.get_claim("permissions"),
Some(&serde_json::json!(["read", "write"]))
);
}
#[test]
fn test_generate_jwt_with_extra_non_object() {
let config = jwt_config();
let extra = serde_json::json!("simple_string_value");
let token = TokenGenerator::generate_jwt_with_extra(&config, "user_456", &extra);
let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
let claims = jwt_manager.validate(token.as_str()).unwrap();
assert_eq!(claims.login_id, "user_456");
assert_eq!(
claims.get_claim("extra"),
Some(&serde_json::json!("simple_string_value"))
);
}
#[test]
fn test_generate_jwt_with_extra_null() {
let config = jwt_config();
let extra = serde_json::Value::Null;
let token = TokenGenerator::generate_jwt_with_extra(&config, "user_789", &extra);
let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
let claims = jwt_manager.validate(token.as_str()).unwrap();
assert_eq!(claims.login_id, "user_789");
assert!(claims.extra.is_empty());
}
#[test]
fn test_generate_with_login_id_and_extra_jwt_style() {
let config = jwt_config();
let extra = serde_json::json!({"key": "value"});
let token = TokenGenerator::generate_with_login_id_and_extra(&config, "user_jwt", &extra);
assert!(token.as_str().contains('.'));
let jwt_manager = JwtManager::new("test-secret-key-for-jwt");
let claims = jwt_manager.validate(token.as_str()).unwrap();
assert_eq!(claims.get_claim("key"), Some(&serde_json::json!("value")));
}
#[test]
fn test_generate_with_login_id_and_extra_non_jwt_style() {
let config = SaTokenConfig {
token_style: TokenStyle::Uuid,
..SaTokenConfig::default()
};
let extra = serde_json::json!({"key": "value"});
let token = TokenGenerator::generate_with_login_id_and_extra(&config, "user_uuid", &extra);
assert!(!token.as_str().is_empty());
assert!(!token.as_str().contains('.'));
}
#[test]
fn test_random_32_length() {
let config = SaTokenConfig {
token_style: TokenStyle::Random32,
..SaTokenConfig::default()
};
let token = TokenGenerator::generate_with_login_id(&config, "user_random");
assert!(!token.as_str().is_empty());
assert_eq!(token.as_str().len(), 32);
}
#[test]
fn test_random_64_length() {
let config = SaTokenConfig {
token_style: TokenStyle::Random64,
..SaTokenConfig::default()
};
let token = TokenGenerator::generate_with_login_id(&config, "user_random");
assert!(!token.as_str().is_empty());
assert_eq!(token.as_str().len(), 64);
}
#[test]
fn test_random_128_length() {
let config = SaTokenConfig {
token_style: TokenStyle::Random128,
..SaTokenConfig::default()
};
let token = TokenGenerator::generate_with_login_id(&config, "user_random");
assert!(!token.as_str().is_empty());
assert_eq!(token.as_str().len(), 128);
}
}