1use chrono::{DateTime, Duration, Utc};
4use hex;
5use sha2::{Digest, Sha256};
6use uuid::Uuid;
7
8#[derive(Debug, Clone)]
10pub struct ApiKeyHash {
11 pub id: String,
12 pub hash: String,
13 pub name: String,
14 pub created_at: DateTime<Utc>,
15 pub expires_at: Option<DateTime<Utc>>,
16 pub last_used: Option<DateTime<Utc>>,
17 pub active: bool,
18}
19
20pub struct ApiKeyManager {
22 secret_salt: String,
23}
24
25impl ApiKeyManager {
26 pub fn new(secret_salt: String) -> Self {
28 Self { secret_salt }
29 }
30
31 pub fn generate_key(&self) -> (String, String) {
33 let key = format!("ggen_{}", Uuid::new_v4().to_string().replace("-", ""));
34 let hash = self.hash_key(&key);
35 (key, hash)
36 }
37
38 pub fn hash_key(&self, key: &str) -> String {
40 let mut hasher = Sha256::new();
41 hasher.update(format!("{}{}", key, self.secret_salt).as_bytes());
42 hex::encode(hasher.finalize())
43 }
44
45 pub fn verify_key(&self, key: &str, hash: &str) -> bool {
47 let computed_hash = self.hash_key(key);
48 computed_hash.as_bytes().len() == hash.len()
50 && computed_hash
51 .as_bytes()
52 .iter()
53 .zip(hash.as_bytes().iter())
54 .all(|(a, b)| a == b)
55 }
56
57 pub fn is_expired(key_hash: &ApiKeyHash) -> bool {
59 if let Some(expires_at) = key_hash.expires_at {
60 expires_at < Utc::now()
61 } else {
62 false
63 }
64 }
65
66 pub fn is_valid(key_hash: &ApiKeyHash) -> bool {
68 key_hash.active && !Self::is_expired(key_hash)
69 }
70
71 pub fn create_hash(
73 &self,
74 key: &str,
75 name: String,
76 expires_in_days: Option<u32>,
77 ) -> ApiKeyHash {
78 let expires_at = expires_in_days.map(|days| {
79 Utc::now() + Duration::days(days as i64)
80 });
81
82 ApiKeyHash {
83 id: Uuid::new_v4().to_string(),
84 hash: self.hash_key(key),
85 name,
86 created_at: Utc::now(),
87 expires_at,
88 last_used: None,
89 active: true,
90 }
91 }
92
93 pub fn revoke(&self, key_hash: &mut ApiKeyHash) {
95 key_hash.active = false;
96 }
97
98 pub fn record_usage(key_hash: &mut ApiKeyHash) {
100 key_hash.last_used = Some(Utc::now());
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn test_generate_and_verify_key() {
110 let manager = ApiKeyManager::new("salt".to_string());
111 let (key, _hash) = manager.generate_key();
112 assert!(key.starts_with("ggen_"));
113 }
114
115 #[test]
116 fn test_hash_verification() {
117 let manager = ApiKeyManager::new("salt".to_string());
118 let key = "test_key";
119 let hash = manager.hash_key(key);
120 assert!(manager.verify_key(key, &hash));
121 }
122
123 #[test]
124 fn test_key_expiration() {
125 let manager = ApiKeyManager::new("salt".to_string());
126 let key = "test";
127 let mut key_hash = manager.create_hash(key, "test".to_string(), Some(0));
128 assert!(ApiKeyManager::is_expired(&key_hash));
129 }
130}