rust_serv/auto_tls/
client.rs1use std::path::Path;
7use std::sync::Arc;
8
9use super::challenge::ChallengeHandler;
10use super::store::CertificateStore;
11use super::{AutoTlsConfig, AutoTlsError, AutoTlsResult};
12
13pub struct AcmeClient {
15 config: AutoTlsConfig,
16 challenge_handler: Arc<ChallengeHandler>,
17 store: CertificateStore,
18}
19
20impl AcmeClient {
21 pub fn new(config: AutoTlsConfig) -> Self {
23 let store = CertificateStore::new(Path::new(&config.cache_dir));
24 Self {
25 config,
26 challenge_handler: Arc::new(ChallengeHandler::new()),
27 store,
28 }
29 }
30
31 pub fn with_challenge_handler(
33 config: AutoTlsConfig,
34 challenge_handler: Arc<ChallengeHandler>,
35 ) -> Self {
36 let store = CertificateStore::new(Path::new(&config.cache_dir));
37 Self {
38 config,
39 challenge_handler,
40 store,
41 }
42 }
43
44 pub fn challenge_handler(&self) -> Arc<ChallengeHandler> {
46 self.challenge_handler.clone()
47 }
48
49 pub async fn initialize(&mut self) -> AutoTlsResult<()> {
51 self.store.init().await?;
53 tracing::info!("ACME client initialized");
54 Ok(())
55 }
56
57 pub async fn request_certificate(&mut self) -> AutoTlsResult<Vec<String>> {
62 if self.config.domains.is_empty() {
63 return Err(AutoTlsError::ConfigError("No domains configured".to_string()));
64 }
65
66 Err(AutoTlsError::AcmeError(
75 "Auto TLS not yet fully implemented. Please use manual certificate setup or certbot.".to_string()
76 ))
77 }
78
79 pub async fn needs_renewal(&self) -> AutoTlsResult<bool> {
81 let domain = match self.config.domains.first() {
82 Some(d) => d,
83 None => return Ok(false),
84 };
85
86 match self.store.load_certificate(domain).await? {
87 Some(cert) => Ok(cert.is_expired() || cert.days_until_expiry() <= self.config.renew_before_days as i64),
88 None => Ok(true),
89 }
90 }
91
92 pub async fn get_certificate(&self) -> AutoTlsResult<Option<super::store::StoredCertificate>> {
94 let domain = match self.config.domains.first() {
95 Some(d) => d,
96 None => return Ok(None),
97 };
98 self.store.load_certificate(domain).await
99 }
100
101 pub async fn import_certificate(
103 &self,
104 certificate: String,
105 private_key: String,
106 expires_at: Option<u64>,
107 ) -> AutoTlsResult<()> {
108 let domain = match self.config.domains.first() {
109 Some(d) => d,
110 None => return Err(AutoTlsError::ConfigError("No domains configured".to_string())),
111 };
112
113 let expires = expires_at.unwrap_or_else(|| {
114 std::time::SystemTime::now()
116 .duration_since(std::time::UNIX_EPOCH)
117 .unwrap()
118 .as_secs()
119 + (90 * 24 * 60 * 60)
120 });
121
122 self.store
123 .save_certificate_with_expiry(domain, &certificate, &private_key, expires)
124 .await?;
125
126 tracing::info!("Certificate imported for {}", domain);
127 Ok(())
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 #[tokio::test]
136 async fn test_acme_client_creation() {
137 let config = AutoTlsConfig {
138 enabled: true,
139 domains: vec!["example.com".to_string()],
140 email: "admin@example.com".to_string(),
141 cache_dir: std::env::temp_dir().to_string_lossy().to_string(),
142 ..Default::default()
143 };
144
145 let client = AcmeClient::new(config);
146 assert_eq!(client.challenge_handler().challenge_count().await, 0);
147 }
148
149 #[test]
150 fn test_acme_client_with_challenge_handler() {
151 let config = AutoTlsConfig::default();
152 let handler = Arc::new(ChallengeHandler::new());
153 let client = AcmeClient::with_challenge_handler(config, handler.clone());
154
155 assert!(Arc::ptr_eq(&client.challenge_handler(), &handler));
157 }
158
159 #[tokio::test]
160 async fn test_needs_renewal_no_certificate() {
161 let config = AutoTlsConfig {
162 domains: vec!["example.com".to_string()],
163 cache_dir: std::env::temp_dir().to_string_lossy().to_string(),
164 ..Default::default()
165 };
166 let client = AcmeClient::new(config);
167
168 let result = client.needs_renewal().await.unwrap();
170 assert!(result);
171 }
172
173 #[tokio::test]
174 async fn test_initialize() {
175 let temp_dir = tempfile::tempdir().unwrap();
176 let config = AutoTlsConfig {
177 cache_dir: temp_dir.path().to_string_lossy().to_string(),
178 ..Default::default()
179 };
180 let mut client = AcmeClient::new(config);
181
182 client.initialize().await.unwrap();
183 }
184}