use hickory_proto::rr::RecordData; use http::HeaderMap; use maud::{Markup, Render, html}; use serde::{Deserialize, Serialize}; use std::{ collections::HashSet, sync::{Arc, Mutex}, }; use std::{ fmt::Display, hash::Hash, time::{SystemTime, UNIX_EPOCH}, }; #[derive(Debug, Default)] pub struct AppState { pub auths: Arc>>, pub domains: Arc>>, } impl AppState { pub fn is_allowed(&self, user: &User, domain: impl Into) -> bool { let domain = domain.into(); self.auths.lock().is_ok_and(|a| { a.iter().any(|u| { u.login == user.login && u.key == user.key && u.scope.iter().any(|s| domain.contains(s)) }) }) } } #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, PartialEq, Eq, Hash, Default, Deserialize, Serialize)] pub enum DnsType { A, AAAA, #[default] TXT, } impl Display for DnsType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { DnsType::A => write!(f, "A"), DnsType::AAAA => write!(f, "AAAA"), DnsType::TXT => write!(f, "TXT"), } } } #[derive(Debug, Deserialize)] pub struct JsonDomain { pub subdomain: String, pub rtype: Option, pub rdata: String, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DnsRecord { domain: String, qname: String, time: u64, rtype: DnsType, rdata: String, } impl DnsRecord { pub fn now() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() } /// Split a full domain into subdomain and domain. pub fn split(fulldomain: &str) -> (String, String) { let parts: Vec<&str> = fulldomain.split('.').collect(); if parts.len() < 2 { ("_".to_owned(), fulldomain.to_owned()) } else { let l = parts.len() - 2; (parts[0..l].join("."), parts[l..].join(".")) } } /// Create a new domain with a lifetime of one hour. pub fn new_txt(subdomain: String, txt: String) -> Self { let (qname, domain) = Self::split(&subdomain); DnsRecord { domain, qname, time: Self::now() + 3600, rtype: DnsType::TXT, rdata: txt, } } pub fn age(&self) -> u64 { Self::now() + 3600 - self.time } } impl Display for DnsRecord { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}: {} {} {}", self.domain, self.qname, self.rtype, self.rdata ) } } impl Render for DnsRecord { fn render(&self) -> Markup { html! { div .cell { (self.domain) } div .cell { (self.qname) } div .cell { (self.rtype) } div .cell { (self.rdata) } } } } impl From for DnsRecord { fn from(json: JsonDomain) -> Self { let (qname, domain) = Self::split(&json.subdomain); DnsRecord { domain, qname, time: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_secs() + 3600, rtype: json.rtype.unwrap_or_default(), rdata: json.rdata, } } } impl Hash for DnsRecord { fn hash(&self, state: &mut H) { self.domain.hash(state); self.qname.hash(state); self.rtype.hash(state); self.rdata.hash(state); } } impl Eq for DnsRecord {} impl PartialEq for DnsRecord { fn eq(&self, other: &Self) -> bool { self.domain == other.domain && self.qname == other.qname && self.rtype == other.rtype && self.rdata == other.rdata } } impl RecordData for DnsRecord { fn into_rdata(self) -> hickory_proto::rr::RData { todo!() } fn is_update(&self) -> bool { true } fn record_type(&self) -> hickory_proto::rr::RecordType { todo!() } fn try_borrow(data: &hickory_proto::rr::RData) -> Option<&Self> { todo!() } fn try_from_rdata(data: hickory_proto::rr::RData) -> Result { todo!() } } #[derive(Debug, Deserialize, Clone)] pub struct User { pub login: String, pub key: String, pub scope: Vec, } impl From<&HeaderMap> for User { fn from(headers: &HeaderMap) -> Self { let login = headers .get("x-api-user") .and_then(|v| v.to_str().ok()) .unwrap_or_default() .to_string(); let key = headers .get("x-api-key") .and_then(|v| v.to_str().ok()) .unwrap_or_default() .to_string(); User { login, key, scope: Vec::new(), } } } impl Eq for User {} impl PartialEq for User { fn eq(&self, other: &Self) -> bool { self.login == other.login && self.key == other.key } } impl Hash for User { fn hash(&self, state: &mut H) { self.login.hash(state); self.key.hash(state); } } #[test] fn domain_split() { let (subdomain, domain) = DnsRecord::split("example.com"); assert_eq!(subdomain, ""); assert_eq!(domain, "example.com"); let (subdomain, domain) = DnsRecord::split("sub.example.com"); assert_eq!(subdomain, "sub"); assert_eq!(domain, "example.com"); let (subdomain, domain) = DnsRecord::split("sub.sub.example.com"); assert_eq!(subdomain, "sub.sub"); assert_eq!(domain, "example.com"); }