rust_serv/auto_tls/
challenge.rs1use std::collections::HashMap;
4use std::sync::Arc;
5use tokio::sync::RwLock;
6
7#[derive(Debug, Clone)]
9pub struct ChallengeToken {
10 pub token: String,
12 pub key_authorization: String,
14}
15
16#[derive(Debug, Clone, Default)]
21pub struct ChallengeHandler {
22 challenges: Arc<RwLock<HashMap<String, String>>>,
24}
25
26impl ChallengeHandler {
27 pub fn new() -> Self {
29 Self {
30 challenges: Arc::new(RwLock::new(HashMap::new())),
31 }
32 }
33
34 pub async fn add_challenge(&self, token: String, key_authorization: String) {
36 let mut challenges = self.challenges.write().await;
37 challenges.insert(token, key_authorization);
38 }
39
40 pub async fn remove_challenge(&self, token: &str) {
42 let mut challenges = self.challenges.write().await;
43 challenges.remove(token);
44 }
45
46 pub async fn get_key_authorization(&self, token: &str) -> Option<String> {
48 let challenges = self.challenges.read().await;
49 challenges.get(token).cloned()
50 }
51
52 pub fn is_challenge_path(path: &str) -> bool {
54 path.starts_with("/.well-known/acme-challenge/")
55 }
56
57 pub fn extract_token(path: &str) -> Option<&str> {
60 path.strip_prefix("/.well-known/acme-challenge/")
61 }
62
63 pub async fn handle_challenge(&self, path: &str) -> Option<String> {
66 if !Self::is_challenge_path(path) {
67 return None;
68 }
69
70 let token = Self::extract_token(path)?;
71 self.get_key_authorization(token).await
72 }
73
74 pub async fn clear(&self) {
76 let mut challenges = self.challenges.write().await;
77 challenges.clear();
78 }
79
80 pub async fn challenge_count(&self) -> usize {
82 let challenges = self.challenges.read().await;
83 challenges.len()
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[tokio::test]
92 async fn test_challenge_handler_basic() {
93 let handler = ChallengeHandler::new();
94
95 handler.add_challenge("token123".to_string(), "key_auth_456".to_string()).await;
96
97 let result = handler.get_key_authorization("token123").await;
98 assert_eq!(result, Some("key_auth_456".to_string()));
99
100 handler.remove_challenge("token123").await;
101 let result = handler.get_key_authorization("token123").await;
102 assert_eq!(result, None);
103 }
104
105 #[tokio::test]
106 async fn test_is_challenge_path() {
107 assert!(ChallengeHandler::is_challenge_path("/.well-known/acme-challenge/abc123"));
108 assert!(!ChallengeHandler::is_challenge_path("/health"));
109 assert!(!ChallengeHandler::is_challenge_path("/.well-known/acme-challenge"));
110 assert!(!ChallengeHandler::is_challenge_path("/other/path"));
111 }
112
113 #[test]
114 fn test_extract_token() {
115 assert_eq!(
116 ChallengeHandler::extract_token("/.well-known/acme-challenge/abc123"),
117 Some("abc123")
118 );
119 assert_eq!(
120 ChallengeHandler::extract_token("/.well-known/acme-challenge/"),
121 Some("")
122 );
123 assert_eq!(
124 ChallengeHandler::extract_token("/other/path"),
125 None
126 );
127 }
128
129 #[tokio::test]
130 async fn test_handle_challenge() {
131 let handler = ChallengeHandler::new();
132 handler.add_challenge("abc123".to_string(), "xyz789".to_string()).await;
133
134 let result = handler.handle_challenge("/.well-known/acme-challenge/abc123").await;
135 assert_eq!(result, Some("xyz789".to_string()));
136
137 let result = handler.handle_challenge("/.well-known/acme-challenge/notfound").await;
138 assert_eq!(result, None);
139
140 let result = handler.handle_challenge("/health").await;
141 assert_eq!(result, None);
142 }
143
144 #[tokio::test]
145 async fn test_clear_challenges() {
146 let handler = ChallengeHandler::new();
147 handler.add_challenge("token1".to_string(), "auth1".to_string()).await;
148 handler.add_challenge("token2".to_string(), "auth2".to_string()).await;
149
150 assert_eq!(handler.challenge_count().await, 2);
151
152 handler.clear().await;
153 assert_eq!(handler.challenge_count().await, 0);
154 }
155
156 #[tokio::test]
157 async fn test_challenge_handler_clone() {
158 let handler1 = ChallengeHandler::new();
159 handler1.add_challenge("token".to_string(), "auth".to_string()).await;
160
161 let handler2 = handler1.clone();
162 let result = handler2.get_key_authorization("token").await;
163 assert_eq!(result, Some("auth".to_string()));
164 }
165
166 #[tokio::test]
167 async fn test_concurrent_access() {
168 let handler = Arc::new(ChallengeHandler::new());
169 let mut handles = vec![];
170
171 for i in 0..10 {
172 let h = handler.clone();
173 handles.push(tokio::spawn(async move {
174 h.add_challenge(format!("token{}", i), format!("auth{}", i)).await;
175 }));
176 }
177
178 for handle in handles {
179 handle.await.unwrap();
180 }
181
182 assert_eq!(handler.challenge_count().await, 10);
183 }
184}