use std::sync::Arc;
use serde::Serialize;
use super::agent_identities::AgentKind;
use super::external::ExternalIdentity;
use crate::server::http::Request;
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct EdgeIdentity {
pub kind: AgentKind,
pub subject: String,
pub name: String,
pub email: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub node: Option<String>,
pub can_claim: bool,
}
pub type EdgeFn = Arc<dyn Fn(&Request) -> Option<EdgeIdentity> + Send + Sync>;
impl EdgeIdentity {
pub fn provider(&self) -> &'static str {
self.kind.as_str()
}
pub fn label(&self) -> &'static str {
match self.kind {
AgentKind::Tailnet => "Tailscale",
AgentKind::Access => "Cloudflare Access",
}
}
pub fn external(&self) -> ExternalIdentity {
ExternalIdentity {
provider: self.provider().into(),
subject: self.subject.clone(),
email: self.email.clone(),
email_verified: self.email.is_some(),
name: None,
}
}
}
pub fn login_email(login: &str) -> Option<String> {
let (local, domain) = login.split_once('@')?;
let ok = !local.is_empty()
&& domain.contains('.')
&& !domain.starts_with('.')
&& !domain.ends_with('.')
&& !login.contains(char::is_whitespace);
ok.then(|| login.to_ascii_lowercase())
}
pub fn provider_label(provider: &str) -> Option<&'static str> {
match provider {
"tailnet" => Some("Tailscale"),
"access" => Some("Cloudflare Access"),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn only_addresses_are_emails() {
assert_eq!(
login_email("Ada@Example.com").as_deref(),
Some("ada@example.com")
);
assert_eq!(login_email("ada@github"), None);
assert_eq!(login_email("tagged-devices"), None);
assert_eq!(login_email("@example.com"), None);
assert_eq!(login_email("a@example."), None);
}
}