tuwunel_service/admin/
register.rs1use std::{
15 collections::BTreeMap,
16 fs,
17 time::{Duration, Instant},
18};
19
20use tuwunel_core::{Config, error, implement, utils};
21
22type Nonces = BTreeMap<String, Instant>;
23
24const NONCE_LENGTH: usize = 32;
25const NONCE_TTL: Duration = Duration::from_mins(1);
26const NONCE_CAP: usize = 2048;
27
28#[implement(super::Service)]
29pub fn issue_register_nonce(&self) -> String {
30 let nonce = utils::random_string(NONCE_LENGTH);
31 let mut nonces = self
32 .register_nonces
33 .lock()
34 .expect("nonce mutex not poisoned");
35
36 gc_expired(&mut nonces);
37 if nonces.len() >= NONCE_CAP {
38 drop_oldest(&mut nonces);
39 }
40
41 nonces.insert(nonce.clone(), Instant::now());
42 nonce
43}
44
45#[implement(super::Service)]
48pub fn consume_register_nonce(&self, nonce: &str) -> bool {
49 let mut nonces = self
50 .register_nonces
51 .lock()
52 .expect("nonce mutex not poisoned");
53
54 nonces
55 .remove(nonce)
56 .is_some_and(|issued| issued.elapsed() < NONCE_TTL)
57}
58
59#[implement(super::Service)]
60#[inline]
61pub fn register_shared_secret(&self) -> Option<&str> { self.register_shared_secret.as_deref() }
62
63#[implement(super::Service)]
64#[inline]
65pub fn register_is_enabled(&self) -> bool { self.register_shared_secret.is_some() }
66
67pub(super) fn resolve_shared_secret(config: &Config) -> Option<String> {
68 config
69 .registration_shared_secret_file
70 .as_ref()
71 .and_then(|path| {
72 fs::read_to_string(path)
73 .inspect_err(|e| {
74 error!("Failed to read the registration shared secret file: {e}");
75 })
76 .ok()
77 .as_deref()
78 .map(str::trim)
79 .map(ToOwned::to_owned)
80 })
81 .or_else(|| config.registration_shared_secret.clone())
82 .filter(|s| !s.is_empty())
83}
84
85fn drop_oldest(nonces: &mut Nonces) {
86 nonces
87 .iter()
88 .min_by_key(|(_, issued)| **issued)
89 .map(|(k, _)| k.clone())
90 .as_ref()
91 .map(|oldest| nonces.remove(oldest));
92}
93
94fn gc_expired(nonces: &mut Nonces) { nonces.retain(|_, issued| issued.elapsed() < NONCE_TTL); }