use anyhow::Result;
use rand::Rng;
use sha2::{Digest, Sha256};
use uuid::Uuid;
use std::sync::Arc;
use crate::jwt::{JwtService, Claims};
use crate::traits::RefreshTokenClaims;
pub struct TokenGenerator {
jwt_service: Arc<JwtService>,
}
impl TokenGenerator {
pub fn new(jwt_service: Arc<JwtService>) -> Self {
Self { jwt_service }
}
pub fn generate_otp() -> String {
let code: u32 = rand::thread_rng().gen_range(100_000..1_000_000);
format!("{:06}", code)
}
pub fn hash_token(token: &str) -> String {
let result = Sha256::digest(token.as_bytes());
result.iter().map(|b| format!("{:02x}", b)).collect()
}
pub fn generate_refresh_token() -> String {
Uuid::new_v4().to_string()
}
pub fn constant_time_eq(a: &str, b: &str) -> bool {
if a.len() != b.len() {
return false;
}
a.bytes()
.zip(b.bytes())
.fold(0u8, |acc, (x, y)| acc | (x ^ y))
== 0
}
pub fn create_access_token(&self, claims: &Claims) -> Result<String> {
self.jwt_service.create_token(claims)
}
pub fn create_refresh_token_jwt(&self, claims: &RefreshTokenClaims) -> Result<String> {
self.jwt_service.create_refresh_token(claims)
}
pub fn validate_access_token(&self, token: &str) -> Result<Claims> {
self.jwt_service.validate_token(token)
}
pub fn validate_refresh_token(&self, token: &str) -> Result<RefreshTokenClaims> {
self.jwt_service.validate_refresh_token(token)
}
pub fn decode_access_token(&self, token: &str) -> Result<Claims> {
self.jwt_service.decode_token(token)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generate_otp_length() {
let otp = TokenGenerator::generate_otp();
assert_eq!(otp.len(), 6);
assert!(otp.chars().all(|c| c.is_ascii_digit()));
}
#[test]
fn test_hash_token_deterministic() {
let hash1 = TokenGenerator::hash_token("test-token");
let hash2 = TokenGenerator::hash_token("test-token");
assert_eq!(hash1, hash2);
assert_eq!(hash1.len(), 64); }
#[test]
fn test_hash_token_different_inputs() {
let hash1 = TokenGenerator::hash_token("token-a");
let hash2 = TokenGenerator::hash_token("token-b");
assert_ne!(hash1, hash2);
}
#[test]
fn test_generate_refresh_token_is_uuid() {
let token = TokenGenerator::generate_refresh_token();
assert!(Uuid::parse_str(&token).is_ok());
}
#[test]
fn test_constant_time_eq_same() {
assert!(TokenGenerator::constant_time_eq("abc", "abc"));
}
#[test]
fn test_constant_time_eq_different() {
assert!(!TokenGenerator::constant_time_eq("abc", "abd"));
}
#[test]
fn test_constant_time_eq_different_length() {
assert!(!TokenGenerator::constant_time_eq("abc", "abcd"));
}
#[test]
fn test_jwt_roundtrip() {
let jwt = Arc::new(JwtService::new("test-secret-key"));
let gen = TokenGenerator::new(jwt);
let claims = Claims {
sub: "user-123".to_string(),
exp: (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp() as usize,
iat: chrono::Utc::now().timestamp() as usize,
iss: "backbone".to_string(),
};
let token = gen.create_access_token(&claims).unwrap();
let decoded = gen.validate_access_token(&token).unwrap();
assert_eq!(decoded.sub, "user-123");
}
}