1use std::{
2 collections::{HashMap, HashSet},
3 sync::Arc,
4 time::SystemTime,
5};
6
7use async_trait::async_trait;
8use dashmap::DashMap;
9pub use papaleguas::OrderStatus;
10use rustls_pki_types::{CertificateDer, PrivateKeyDer};
11use serde::{Deserialize, Serialize};
12use time::OffsetDateTime;
13use tokio::{
14 io,
15 sync::{OwnedRwLockWriteGuard, RwLock},
16 try_join,
17};
18use tracing::trace;
19use x509_cert::{der::Decode, Certificate as X509Certificate};
20
21pub use boxed::*;
22#[cfg(feature = "dynamodb-store")]
23pub use dynamodb::*;
24pub use fs::*;
25
26mod boxed;
27#[cfg(feature = "dynamodb-store")]
28mod dynamodb;
29mod fs;
30
31pub type PrivateKey = PrivateKeyDer<'static>;
32pub type Certificate = CertificateDer<'static>;
33
34#[async_trait]
35pub trait CertStore: Send + Sync {
36 async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>>;
37 async fn put_cert(
38 &self,
39 domain: &str,
40 key: PrivateKey,
41 cert: Vec<Certificate>,
42 ) -> io::Result<()>;
43}
44
45#[derive(Debug, Clone, Eq, Serialize, Deserialize)]
46#[serde(rename_all = "camelCase")]
47pub struct Order {
48 pub url: String,
49 pub status: OrderStatus,
50 #[serde(default, with = "time::serde::iso8601::option")]
51 pub expires: Option<OffsetDateTime>,
52}
53
54impl std::hash::Hash for Order {
55 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
56 self.url.hash(state)
57 }
58}
59
60impl PartialEq for Order {
61 fn eq(&self, other: &Self) -> bool {
62 self.url.eq(&other.url)
63 }
64}
65
66impl From<&papaleguas::Order> for Order {
67 fn from(value: &papaleguas::Order) -> Self {
68 Self {
69 url: value.url().to_owned(),
70 status: *value.status(),
71 expires: value.expires(),
72 }
73 }
74}
75
76#[async_trait]
77pub trait OrderStore: Send + Sync {
78 async fn list_orders(&self, domain: &str) -> io::Result<HashSet<Order>>;
79 async fn upsert_order(&self, domain: &str, order: Order) -> io::Result<()>;
80 async fn remove_order(&self, domain: &str, order_url: &str) -> io::Result<()>;
81}
82
83#[async_trait]
84pub trait AccountStore: Send + Sync {
85 async fn get_account(&self, directory: &str) -> io::Result<Option<PrivateKey>>;
86 async fn put_account(&self, directory: &str, key: PrivateKey) -> io::Result<()>;
87}
88
89#[derive(Debug, Clone, Default, Serialize, Deserialize)]
90pub struct AuthChallenge {
91 challenges: Vec<ChallengeKind>,
92}
93
94#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize)]
95enum ChallengeKind {
96 TlsAlpn(String),
97 Http01 { token: String, challenge: String },
98}
99
100impl AuthChallenge {
101 pub fn new() -> Self {
102 Default::default()
103 }
104
105 pub fn with_http01(mut self, token: impl Into<String>, challenge: impl Into<String>) -> Self {
106 self.add_http01(token, challenge);
107 self
108 }
109
110 pub fn with_tls_alpn01(mut self, challenge: impl Into<String>) -> Self {
111 self.add_tls_alpn01(challenge);
112 self
113 }
114
115 pub fn add_http01(&mut self, token: impl Into<String>, challenge: impl Into<String>) {
116 self.challenges.push(ChallengeKind::Http01 {
117 token: token.into(),
118 challenge: challenge.into(),
119 });
120 }
121
122 pub fn add_tls_alpn01(&mut self, challenge: impl Into<String>) {
123 self.challenges
124 .push(ChallengeKind::TlsAlpn(challenge.into()));
125 }
126
127 pub fn http01_challenge(&self) -> Option<(&str, &str)> {
128 self.challenges
129 .iter()
130 .find_map(|challenge| match challenge {
131 ChallengeKind::Http01 { token, challenge } => {
132 Some((token.as_str(), challenge.as_str()))
133 }
134 _ => None,
135 })
136 }
137
138 pub fn tls_alpn01_challenge(&self) -> Option<&str> {
139 self.challenges
140 .iter()
141 .find_map(|challenge| match challenge {
142 ChallengeKind::TlsAlpn(challenge) => Some(challenge.as_str()),
143 _ => None,
144 })
145 }
146
147 pub fn is_empty(&self) -> bool {
148 self.challenges.is_empty()
149 }
150}
151
152#[async_trait]
153pub trait AuthChallengeStore: Send + Sync {
154 type LockGuard: AuthChallengeDomainLock + Send;
155 async fn get_challenge(&self, domain: &str) -> io::Result<Option<AuthChallenge>>;
156 async fn lock(&self, domain: &str) -> io::Result<Self::LockGuard>;
157 async fn unlock(&self, domain: &str) -> io::Result<()>;
158}
159
160#[async_trait]
161pub trait AuthChallengeDomainLock: Send + Sync {
162 async fn put_challenge(&mut self, challenge: AuthChallenge) -> io::Result<()>;
163}
164
165#[async_trait]
166impl CertStore for RwLock<HashMap<String, (PrivateKey, Vec<Certificate>)>> {
167 async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
168 Ok(self
169 .read()
170 .await
171 .get(domain)
172 .map(|(key, certs)| (key.clone_key(), certs.clone())))
173 }
174
175 async fn put_cert(
176 &self,
177 domain: &str,
178 key: PrivateKey,
179 cert: Vec<Certificate>,
180 ) -> io::Result<()> {
181 self.write().await.insert(domain.to_owned(), (key, cert));
182 Ok(())
183 }
184}
185
186#[async_trait]
187impl CertStore for DashMap<String, (PrivateKey, Vec<Certificate>)> {
188 async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
189 Ok(self.get(domain).map(|item| {
190 let (key, certs) = item.value();
191 (key.clone_key(), certs.clone())
192 }))
193 }
194
195 async fn put_cert(
196 &self,
197 domain: &str,
198 key: PrivateKey,
199 cert: Vec<Certificate>,
200 ) -> io::Result<()> {
201 self.insert(domain.to_owned(), (key, cert));
202 Ok(())
203 }
204}
205
206#[derive(Debug, Default)]
207pub struct MemoryCertStore(DashMap<String, (PrivateKey, Vec<Certificate>)>);
208
209#[async_trait]
210impl CertStore for MemoryCertStore {
211 async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
212 self.0.get_cert(domain).await
213 }
214
215 async fn put_cert(
216 &self,
217 domain: &str,
218 key: PrivateKey,
219 cert: Vec<Certificate>,
220 ) -> io::Result<()> {
221 self.0.put_cert(domain, key, cert).await
222 }
223}
224
225#[async_trait]
226impl OrderStore for DashMap<String, HashSet<Order>> {
227 async fn list_orders(&self, domain: &str) -> io::Result<HashSet<Order>> {
228 let orders = self
229 .get(domain)
230 .map(|item| item.value().clone())
231 .unwrap_or_default();
232 Ok(orders)
233 }
234
235 async fn upsert_order(&self, domain: &str, order: Order) -> io::Result<()> {
236 let mut orders = self.entry(domain.to_string()).or_default();
237 orders.replace(order);
238 Ok(())
239 }
240
241 async fn remove_order(&self, domain: &str, order_url: &str) -> io::Result<()> {
242 self.entry(domain.to_string())
243 .and_modify(|orders| {
244 orders.remove(&Order {
245 url: order_url.to_string(),
246 status: OrderStatus::Ready,
247 expires: None,
248 });
249 })
250 .or_default();
251 Ok(())
252 }
253}
254
255#[derive(Debug, Default)]
256pub struct MemoryOrderStore(DashMap<String, HashSet<Order>>);
257
258#[async_trait]
259impl OrderStore for MemoryOrderStore {
260 async fn list_orders(&self, domain: &str) -> io::Result<HashSet<Order>> {
261 self.0.list_orders(domain).await
262 }
263
264 async fn upsert_order(&self, domain: &str, order: Order) -> io::Result<()> {
265 self.0.upsert_order(domain, order).await
266 }
267
268 async fn remove_order(&self, domain: &str, order_url: &str) -> io::Result<()> {
269 self.0.remove_order(domain, order_url).await
270 }
271}
272
273#[async_trait]
274impl AccountStore for DashMap<String, PrivateKey> {
275 async fn get_account(&self, directory: &str) -> io::Result<Option<PrivateKey>> {
276 Ok(self.get(directory).map(|item| item.clone_key()))
277 }
278
279 async fn put_account(&self, directory: &str, key: PrivateKey) -> io::Result<()> {
280 self.insert(directory.to_owned(), key);
281 Ok(())
282 }
283}
284
285#[derive(Debug, Default, Clone)]
286pub struct MemoryAccountStore(Arc<DashMap<String, PrivateKey>>);
287
288#[async_trait]
289impl AccountStore for MemoryAccountStore {
290 async fn get_account(&self, directory: &str) -> io::Result<Option<PrivateKey>> {
291 self.0.get_account(directory).await
292 }
293
294 async fn put_account(&self, directory: &str, key: PrivateKey) -> io::Result<()> {
295 self.0.put_account(directory, key).await
296 }
297}
298
299#[derive(Debug, Default)]
300pub struct MemoryAuthChallengeStore {
301 store: DashMap<String, Arc<RwLock<AuthChallenge>>>,
302}
303
304pub struct MemoryAuthChallengeStoreGuard(OwnedRwLockWriteGuard<AuthChallenge>);
305
306#[async_trait]
307impl AuthChallengeStore for MemoryAuthChallengeStore {
308 type LockGuard = MemoryAuthChallengeStoreGuard;
309
310 async fn get_challenge(&self, domain: &str) -> io::Result<Option<AuthChallenge>> {
311 match self.store.get(domain) {
312 Some(entry) => Ok(Some(entry.value().clone().read().await.clone())),
313 None => Ok(None),
314 }
315 }
316
317 async fn lock(&self, domain: &str) -> io::Result<Self::LockGuard> {
318 self.store
319 .entry(domain.to_owned())
320 .or_default()
321 .clone()
322 .try_write_owned()
323 .map(MemoryAuthChallengeStoreGuard)
324 .map_err(|_| io::Error::other("could not arquire lock"))
325 }
326
327 async fn unlock(&self, domain: &str) -> io::Result<()> {
328 self.store.remove(domain);
329 Ok(())
330 }
331}
332
333#[async_trait]
334impl AuthChallengeDomainLock for MemoryAuthChallengeStoreGuard {
335 async fn put_challenge(&mut self, challenge: AuthChallenge) -> io::Result<()> {
336 *self.0 = challenge;
337 Ok(())
338 }
339}
340
341pub struct CachedCertStore<S> {
342 store: S,
343 cache: MemoryCertStore,
344}
345
346impl<S: CertStore> CachedCertStore<S> {
347 pub fn new(store: S) -> Self {
348 CachedCertStore {
349 store,
350 cache: MemoryCertStore::default(),
351 }
352 }
353}
354
355pub trait CachedCertStoreExt {
356 fn cached(self) -> CachedCertStore<Self>
357 where
358 Self: Sized;
359}
360
361impl<C: CertStore + 'static> CachedCertStoreExt for C {
362 fn cached(self) -> CachedCertStore<Self> {
363 CachedCertStore::new(self)
364 }
365}
366
367#[async_trait]
368impl<S: CertStore> CertStore for CachedCertStore<S> {
369 async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
370 if let Some(cached) = self.cache.get_cert(domain).await? {
371 return Ok(Some(cached));
372 }
373
374 if let Some((key, cert)) = self.store.get_cert(domain).await? {
375 trace!(domain, "cert not cached, caching now");
376 self.cache
377 .put_cert(domain, key.clone_key(), cert.clone())
378 .await?;
379 return Ok(Some((key, cert)));
380 }
381
382 Ok(None)
383 }
384
385 async fn put_cert(
386 &self,
387 domain: &str,
388 key: PrivateKey,
389 cert: Vec<Certificate>,
390 ) -> io::Result<()> {
391 trace!(domain, "caching cert");
392 try_join!(
393 self.store.put_cert(domain, key.clone_key(), cert.clone()),
394 self.cache.put_cert(domain, key, cert),
395 )?;
396 Ok(())
397 }
398}
399
400pub struct CertExpirationTimeStore<S> {
401 store: S,
402}
403
404fn cert_validity(cert: &[Certificate]) -> Option<SystemTime> {
405 cert.iter()
406 .filter_map(|cert| X509Certificate::from_der(cert).ok())
407 .map(|cert| cert.tbs_certificate.validity)
408 .map(|val| val.not_after.to_system_time())
409 .min()
410}
411
412impl<S: CertStore> CertExpirationTimeStore<S> {
413 pub fn new(store: S) -> Self {
414 CertExpirationTimeStore { store }
415 }
416}
417
418pub trait CertExpirationTimeStoreExt {
419 fn with_validity_check(self) -> CertExpirationTimeStore<Self>
420 where
421 Self: Sized;
422}
423
424impl<C: CertStore + 'static> CertExpirationTimeStoreExt for C {
425 fn with_validity_check(self) -> CertExpirationTimeStore<Self> {
426 CertExpirationTimeStore::new(self)
427 }
428}
429
430#[async_trait]
431impl<S: CertStore> CertStore for CertExpirationTimeStore<S> {
432 async fn get_cert(&self, domain: &str) -> io::Result<Option<(PrivateKey, Vec<Certificate>)>> {
433 match self.store.get_cert(domain).await? {
434 Some(cert) => match cert_validity(&cert.1) {
435 Some(validity) if validity < SystemTime::now() => {
436 let valid_until = validity
437 .duration_since(SystemTime::UNIX_EPOCH)
438 .unwrap_or_default()
439 .as_secs();
440 trace!(domain, until = valid_until, "cert is expired");
441 Ok(None)
442 }
443 Some(_validity) => Ok(Some(cert)),
444 None => Ok(None),
445 },
446 None => Ok(None),
447 }
448 }
449
450 async fn put_cert(
451 &self,
452 domain: &str,
453 key: PrivateKey,
454 cert: Vec<Certificate>,
455 ) -> io::Result<()> {
456 self.store.put_cert(domain, key, cert).await
457 }
458}
459
460pub struct SingleAccountStore(PrivateKey);
461
462impl SingleAccountStore {
463 pub fn new(key: PrivateKey) -> Self {
464 SingleAccountStore(key)
465 }
466}
467
468#[async_trait]
469impl AccountStore for SingleAccountStore {
470 async fn get_account(&self, _directory: &str) -> io::Result<Option<PrivateKey>> {
471 Ok(Some(self.0.clone_key()))
472 }
473
474 async fn put_account(&self, _directory: &str, _key: PrivateKey) -> io::Result<()> {
475 Ok(())
476 }
477}
478
479#[cfg(test)]
480mod test {
481 use indoc::indoc;
482 use papaleguas::OrderStatus;
483 use rustls_pki_types::pem::{PemObject, SectionKind};
484
485 use crate::store::{
486 AuthChallenge, AuthChallengeDomainLock, AuthChallengeStore, CertStore,
487 MemoryAuthChallengeStore, MemoryCertStore, MemoryOrderStore, OrderStore,
488 };
489
490 use super::{Certificate, Order, PrivateKey};
491
492 #[tokio::test]
493 async fn test_memory_store() {
494 let pkey = indoc! {"
495 -----BEGIN PRIVATE KEY-----
496 MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCq1WvODxLHgRNw
497 Fq7rHh9gCfCEtbN7iE2W6arQ+zYPVWiNQrKNyBqe9n2Ao/77EBnhKzJ3YrVBesGs
498 b+DE/mMXIR/2skchNTX314zaZ13fIn/QnQBtsnh3uzwfk9dFe2Z2v9WSWzumPXoP
499 UyEVt8OShW3kfjRM7WNu8IDubU1SiskRUym86fJCqIEPIwf0EkXN9Lt7fU+00X2A
500 A3Bay3uepg0uaPDmdiWwnTSXYaY4JxIVJ6V1ntzjpuRHaVEfXzCpcaWLBpJbr+uT
501 Le2RBfa6Pa7QjlY6moYAwaDfoF0Kk8U4tpV5X4Fx4wWNhPqUg8Y6NY3WJYXlB6fu
502 uDn+DPFrAgMBAAECggEASjJHkEebsGqvNo+jiRqcJeorPHhua8jXaiQyvHFfGWnO
503 7wt44Xt3lHMaLzULGZ/0nYdVc+S7NKVMWMh+pxCVmQYaC9uCaTnjJrHHy1P5wWAK
504 g2CtPve0usvnYQ+k/9iIuCq5Z8eYMKuix+UjCXu2xXyOLh9iN8ci2Jw8Y1G1s5M9
505 vk5MW4lvtb/WTAh6jTRXHMdx1RHjY8nGtf+eYu52uYm0ZWMh+H7zGzApCX1mpPKr
506 lMwwzGLIUcrBZ1Q98yRsdnOr5ErzWoRH44k0+CfmpWsnoWtWUbWM4WUeQDEywx6k
507 8aAkuVQRKvKem3ifoPG8mAjij6sfV/v19ffusn/KAQKBgQDG5z8JTUGTxYar4H/D
508 Gi1bMI9atzDdbsE499rWpPZ5BuwtSDmMACS5lWu8pT/YSELNOu5PtKK6H41ZADLW
509 kGItzCOcPQTOAAdnD5T29+jVG3hnfQCIBEksa97uWGamX00P21qUAeRStWaKf//O
510 dL6h9W7zP04tYah/zwQ5n4EcMQKBgQDb3234Di8/RQPLAJm2VoXCT/1+cRcgfKCm
511 YvmmNOzPlGmYrSHx9khlZUXdTy2aZj0NGHaWJPbE5sCVnsw7dqXPibJ1TYN/PIHJ
512 X1MYQjnHRkFZDpk/fSd8xl6ZcRHTjehhd8qbyZHTFIUHX59oC3e+uwNg862fkoYH
513 TAsp3OesWwKBgQDDKtDNncLE7sKwD/8NP7hVjBZ92tbV0AFElt9iUkeOhd5kqEPf
514 PZzLhPRMDJHS9USnADYqe4JYwvD87Zb0toO/kFk4yx7Vy214EPAITUVnJidE1IEa
515 9amfLtF2acN/aG/DKWd9Z0XUai6No/8rY55SaPNPN0TMftDJaCYrLHmRYQKBgFX9
516 kQWljobhF/Wp23P7bL6tCAgOdKwI8c+BAAAnzMH2WkIS3CbEWlYFgIhoMf6jo5be
517 jWp1NGmXkZQykc9jvL9pK/lCgn4djOjTtizTocM0z9PjqL2y1eGvt0mtdfpWEp8j
518 +YJqF/UEnm5e0HohmghnHZAqXSn+ZRqve+I4egbnAoGBALeuy9MMaLQEL4HzGyvy
519 C2U5FYAohdbw05WSfuvE8weluvND2DbQvNXq/0MAt3D0AviGTR9k0zpO+OS/nLnv
520 nuRgPCQl8N7RLKQqKf/grE9LAlZGj8pajn7ARhutjVs9z7CYQU8zthyUIvrqTLqQ
521 b41I4V1EVPutE18LGpgWFRfJ
522 -----END PRIVATE KEY-----
523 "};
524
525 let cert = indoc! {"
526 -----BEGIN CERTIFICATE-----
527 MIIDazCCAlOgAwIBAgIUVE8Tzvqz/Sd9VdOf94+FygbEMeIwDQYJKoZIhvcNAQEL
528 BQAwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
529 GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNTAzMDkxMzUyMjFaFw0yNjAz
530 MDkxMzUyMjFaMEUxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
531 HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
532 AQUAA4IBDwAwggEKAoIBAQCq1WvODxLHgRNwFq7rHh9gCfCEtbN7iE2W6arQ+zYP
533 VWiNQrKNyBqe9n2Ao/77EBnhKzJ3YrVBesGsb+DE/mMXIR/2skchNTX314zaZ13f
534 In/QnQBtsnh3uzwfk9dFe2Z2v9WSWzumPXoPUyEVt8OShW3kfjRM7WNu8IDubU1S
535 iskRUym86fJCqIEPIwf0EkXN9Lt7fU+00X2AA3Bay3uepg0uaPDmdiWwnTSXYaY4
536 JxIVJ6V1ntzjpuRHaVEfXzCpcaWLBpJbr+uTLe2RBfa6Pa7QjlY6moYAwaDfoF0K
537 k8U4tpV5X4Fx4wWNhPqUg8Y6NY3WJYXlB6fuuDn+DPFrAgMBAAGjUzBRMB0GA1Ud
538 DgQWBBSXhnoVxEQENyRiCooUeIov7R7yLDAfBgNVHSMEGDAWgBSXhnoVxEQENyRi
539 CooUeIov7R7yLDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCB
540 4oNLXCd6gP8MlOyaYA9NZEfihNOZ/lg24UAtTs92btWYpsERqIm3cuRQ/mhpUnYR
541 rr4yzIHY3LzG2pK1LjbEIStRjCsPb/fCLcxx9tffxweiwpY+AxjdO4R/v9bFjxk4
542 sfb8h0ls7idqJOzU43PfTbHLaiKaPITw3TBNi5tn88bGag4iWIUdFTXbInL603Pz
543 R/g27O0Q3ohsA07C0i+GZbdtR1mghSlXj3m7bnAVDyCac670AE33c5dYKiyudRXB
544 17dFJhag9cNIXgCIaoEGMmqByuZVCbZshJb1ac3sP3bk7LR35TPm0DmL4ReiycJg
545 0W3rtqDKWRuaPAS8WMZw
546 -----END CERTIFICATE-----
547 "};
548
549 let store = MemoryCertStore::default();
550
551 let pkey = PrivateKey::from_pem(SectionKind::PrivateKey, pkey.as_bytes().to_vec()).unwrap();
552 let cert = vec![Certificate::from_pem_slice(cert.as_bytes()).unwrap()];
553
554 assert!(store.get_cert("lol.wut").await.unwrap().is_none());
555
556 store
557 .put_cert("lol.wut", pkey.clone_key(), cert.clone())
558 .await
559 .unwrap();
560
561 let (stored_key, stored_cert) = store.get_cert("lol.wut").await.unwrap().unwrap();
562 assert_eq!(stored_key, pkey);
563 assert_eq!(stored_cert, cert);
564 }
565
566 #[tokio::test]
567 async fn test_order_memory_store() {
568 let store = MemoryOrderStore::default();
569
570 let order1 = Order {
571 url: "http://order/1".to_string(),
572 status: OrderStatus::Ready,
573 expires: None,
574 };
575
576 let order2 = Order {
577 url: "http://order/2".to_string(),
578 status: OrderStatus::Invalid,
579 expires: None,
580 };
581
582 let order3 = Order {
583 url: "http://order/3".to_string(),
584 status: OrderStatus::Invalid,
585 expires: None,
586 };
587
588 store.upsert_order("lol.com", order1.clone()).await.unwrap();
589 store.upsert_order("lol.com", order2.clone()).await.unwrap();
590 store.upsert_order("wut.com", order3.clone()).await.unwrap();
591
592 let orders = store.list_orders("lol.com").await.unwrap();
593 assert!(orders.contains(&order1));
594 assert!(orders.contains(&order2));
595 assert!(!orders.contains(&order3));
596
597 let orders = store.list_orders("wut.com").await.unwrap();
598 assert!(orders.contains(&order3));
599
600 store.remove_order("lol.com", &order1.url).await.unwrap();
601 let orders = store.list_orders("lol.com").await.unwrap();
602 assert!(!orders.contains(&order1));
603 assert!(orders.contains(&order2));
604
605 let orders = store.list_orders("wut.com").await.unwrap();
606 assert!(orders.contains(&order3));
607 store
608 .upsert_order(
609 "wut.com",
610 Order {
611 status: OrderStatus::Valid,
612 ..order3.clone()
613 },
614 )
615 .await
616 .unwrap();
617 let orders = store.list_orders("wut.com").await.unwrap();
618 assert_eq!(orders.get(&order3).unwrap().status, OrderStatus::Valid);
619 }
620
621 #[tokio::test]
622 async fn test_auth_store_lock() {
623 let store = MemoryAuthChallengeStore::default();
624
625 let mut lock1 = store.lock("lol.wut").await.unwrap();
626 assert!(store.lock("lol.wut").await.is_err());
627
628 let mut lock2 = store.lock("wtf.wut").await.unwrap();
629 assert!(store.lock("wtf.wut").await.is_err());
630
631 lock1
632 .put_challenge(AuthChallenge::new().with_tls_alpn01("1"))
633 .await
634 .unwrap();
635 lock2
636 .put_challenge(
637 AuthChallenge::new()
638 .with_http01("token", "chall")
639 .with_tls_alpn01("tls"),
640 )
641 .await
642 .unwrap();
643
644 drop(lock1);
645 assert_eq!(
646 store
647 .get_challenge("lol.wut")
648 .await
649 .unwrap()
650 .unwrap()
651 .tls_alpn01_challenge(),
652 Some("1")
653 );
654
655 drop(lock2);
656 assert_eq!(
657 store
658 .get_challenge("wtf.wut")
659 .await
660 .unwrap()
661 .unwrap()
662 .http01_challenge(),
663 Some(("token", "chall"))
664 );
665 assert_eq!(
666 store
667 .get_challenge("wtf.wut")
668 .await
669 .unwrap()
670 .unwrap()
671 .tls_alpn01_challenge(),
672 Some("tls")
673 );
674
675 assert!(store.get_challenge("other.wut").await.unwrap().is_none());
676 }
677
678 #[tokio::test]
679 async fn test_auth_store_unlock() {
680 let store = MemoryAuthChallengeStore::default();
681
682 let _lock = store.lock("lol.wut").await.unwrap();
683 store.unlock("lol.wut").await.unwrap();
684 let _lock = store.lock("lol.wut").await.unwrap();
685 }
686}