backbone_auth/
token_generator.rs1use anyhow::Result;
7use rand::Rng;
8use sha2::{Digest, Sha256};
9use uuid::Uuid;
10use std::sync::Arc;
11
12use crate::jwt::{JwtService, Claims};
13use crate::traits::RefreshTokenClaims;
14
15pub struct TokenGenerator {
20 jwt_service: Arc<JwtService>,
21}
22
23impl TokenGenerator {
24 pub fn new(jwt_service: Arc<JwtService>) -> Self {
26 Self { jwt_service }
27 }
28
29 pub fn generate_otp() -> String {
33 let code: u32 = rand::thread_rng().gen_range(100_000..1_000_000);
34 format!("{:06}", code)
35 }
36
37 pub fn hash_token(token: &str) -> String {
39 let result = Sha256::digest(token.as_bytes());
40 result.iter().map(|b| format!("{:02x}", b)).collect()
41 }
42
43 pub fn generate_refresh_token() -> String {
45 Uuid::new_v4().to_string()
46 }
47
48 pub fn constant_time_eq(a: &str, b: &str) -> bool {
57 if a.len() != b.len() {
58 return false;
59 }
60 a.bytes()
61 .zip(b.bytes())
62 .fold(0u8, |acc, (x, y)| acc | (x ^ y))
63 == 0
64 }
65
66 pub fn create_access_token(&self, claims: &Claims) -> Result<String> {
70 self.jwt_service.create_token(claims)
71 }
72
73 pub fn create_refresh_token_jwt(&self, claims: &RefreshTokenClaims) -> Result<String> {
75 self.jwt_service.create_refresh_token(claims)
76 }
77
78 pub fn validate_access_token(&self, token: &str) -> Result<Claims> {
80 self.jwt_service.validate_token(token)
81 }
82
83 pub fn validate_refresh_token(&self, token: &str) -> Result<RefreshTokenClaims> {
85 self.jwt_service.validate_refresh_token(token)
86 }
87
88 pub fn decode_access_token(&self, token: &str) -> Result<Claims> {
90 self.jwt_service.decode_token(token)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn test_generate_otp_length() {
100 let otp = TokenGenerator::generate_otp();
101 assert_eq!(otp.len(), 6);
102 assert!(otp.chars().all(|c| c.is_ascii_digit()));
103 }
104
105 #[test]
106 fn test_hash_token_deterministic() {
107 let hash1 = TokenGenerator::hash_token("test-token");
108 let hash2 = TokenGenerator::hash_token("test-token");
109 assert_eq!(hash1, hash2);
110 assert_eq!(hash1.len(), 64); }
112
113 #[test]
114 fn test_hash_token_different_inputs() {
115 let hash1 = TokenGenerator::hash_token("token-a");
116 let hash2 = TokenGenerator::hash_token("token-b");
117 assert_ne!(hash1, hash2);
118 }
119
120 #[test]
121 fn test_generate_refresh_token_is_uuid() {
122 let token = TokenGenerator::generate_refresh_token();
123 assert!(Uuid::parse_str(&token).is_ok());
124 }
125
126 #[test]
127 fn test_constant_time_eq_same() {
128 assert!(TokenGenerator::constant_time_eq("abc", "abc"));
129 }
130
131 #[test]
132 fn test_constant_time_eq_different() {
133 assert!(!TokenGenerator::constant_time_eq("abc", "abd"));
134 }
135
136 #[test]
137 fn test_constant_time_eq_different_length() {
138 assert!(!TokenGenerator::constant_time_eq("abc", "abcd"));
139 }
140
141 #[test]
142 fn test_jwt_roundtrip() {
143 let jwt = Arc::new(JwtService::new("test-secret-key"));
144 let gen = TokenGenerator::new(jwt);
145
146 let claims = Claims {
147 sub: "user-123".to_string(),
148 exp: (chrono::Utc::now() + chrono::Duration::hours(1)).timestamp() as usize,
149 iat: chrono::Utc::now().timestamp() as usize,
150 iss: "backbone".to_string(),
151 };
152
153 let token = gen.create_access_token(&claims).unwrap();
154 let decoded = gen.validate_access_token(&token).unwrap();
155 assert_eq!(decoded.sub, "user-123");
156 }
157}