use std::{
collections::BTreeSet,
env::VarError,
fmt::{self, Display},
str::FromStr,
};
use anyhow::{Context, anyhow};
use iroh::{EndpointAddr, EndpointId, SecretKey, TransportAddr};
use iroh_tickets::{ParseError, Ticket};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone)]
pub struct ApiSecret {
pub secret: SecretKey,
pub remote: EndpointAddr,
}
impl Display for ApiSecret {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", Ticket::serialize(self))
}
}
#[derive(Serialize, Deserialize)]
struct Variant0EndpointAddr {
endpoint_id: EndpointId,
addrs: BTreeSet<TransportAddr>,
}
#[derive(Serialize, Deserialize)]
enum TicketWireFormat {
Variant0(Variant0ServicesTicket),
}
#[derive(Serialize, Deserialize)]
struct Variant0ServicesTicket {
secret: SecretKey,
addr: Variant0EndpointAddr,
}
impl Ticket for ApiSecret {
const KIND: &'static str = "services";
fn to_bytes(&self) -> Vec<u8> {
let data = TicketWireFormat::Variant0(Variant0ServicesTicket {
secret: self.secret.clone(),
addr: Variant0EndpointAddr {
endpoint_id: self.remote.id,
addrs: self.remote.addrs.clone(),
},
});
postcard::to_stdvec(&data).expect("postcard serialization failed")
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
let res: TicketWireFormat = postcard::from_bytes(bytes)?;
let TicketWireFormat::Variant0(Variant0ServicesTicket { secret, addr }) = res;
Ok(Self {
secret,
remote: EndpointAddr {
id: addr.endpoint_id,
addrs: addr.addrs.clone(),
},
})
}
}
impl FromStr for ApiSecret {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
iroh_tickets::Ticket::deserialize(s)
}
}
impl ApiSecret {
pub fn new(secret: SecretKey, remote: impl Into<EndpointAddr>) -> Self {
Self {
secret,
remote: remote.into(),
}
}
pub fn from_env_var(env_var: &str) -> anyhow::Result<Self> {
match std::env::var(env_var) {
Ok(ticket_string) => Self::from_str(&ticket_string)
.context(format!("invalid api secret at env var {env_var}")),
Err(VarError::NotPresent) => Err(anyhow!("{env_var} environment variable is not set")),
Err(VarError::NotUnicode(e)) => Err(anyhow!(
"{env_var} environment variable is not valid unicode: {:?}",
e
)),
}
}
pub fn addr(&self) -> &EndpointAddr {
&self.remote
}
}