[Fix] Connection Management

This commit is contained in:
Alex Emmet 2026-09-13 20:58:41 +02:00
commit 3f2ac18333
No known key found for this signature in database
122 changed files with 19970 additions and 5263 deletions

View file

@ -0,0 +1,30 @@
use iota_identity::{
AuthorityDiscoveryDocument, AuthorityLocator, DiscoveredAuthority, IdentityError,
};
pub async fn discover_authority(
client: &reqwest::Client,
locator: &AuthorityLocator,
) -> Result<DiscoveredAuthority, IdentityError> {
let url = format!("https://{}/.well-known/tensamin", locator.as_str());
let response = client
.get(&url)
.send()
.await
.map_err(|error| {
IdentityError::Unavailable(format!("authority discovery failed for {url}: {error}"))
})?
.error_for_status()
.map_err(|error| {
IdentityError::Unavailable(format!("authority discovery rejected {url}: {error}"))
})?;
let document = response
.json::<AuthorityDiscoveryDocument>()
.await
.map_err(|error| {
IdentityError::InvalidDescriptor(format!(
"invalid authority discovery document from {url}: {error}"
))
})?;
document.verify()
}

View file

@ -1,4 +1,5 @@
pub mod atomic_file;
pub mod authority_discovery;
pub mod crypto_helper;
pub mod crypto_util;
pub mod file_util;

View file

@ -3,6 +3,7 @@
* names the file after its owner's username. */
use crate::crypto_helper::{keyring_from_base64, keyring_to_base64};
use iota_identity::{AuthorityId, IotaNodeId, PrincipalId};
use mtp::crypto::{Keyring, PublicKeyBundle};
use std::fmt;
@ -13,6 +14,7 @@ pub enum TuError {
InvalidFormat,
InvalidUserId,
InvalidKeyring,
InvalidAuthority,
}
impl fmt::Display for TuError {
@ -21,15 +23,22 @@ impl fmt::Display for TuError {
Self::InvalidFormat => "invalid .tu credential format",
Self::InvalidUserId => "invalid .tu user id",
Self::InvalidKeyring => "invalid .tu keyring",
Self::InvalidAuthority => "invalid .tu authority",
})
}
}
impl std::error::Error for TuError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum TuAuthority {
Omega(String),
Iota(IotaNodeId),
}
pub struct TuCredential {
pub user_id: i64,
pub omega_host: String,
pub authority: TuAuthority,
pub keyring: Keyring,
}
@ -37,7 +46,7 @@ impl fmt::Debug for TuCredential {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TuCredential")
.field("user_id", &self.user_id)
.field("omega_host", &self.omega_host)
.field("authority", &self.authority)
.field("keyring", &"<redacted>")
.finish()
}
@ -45,6 +54,13 @@ impl fmt::Debug for TuCredential {
impl TuCredential {
pub fn parse(input: &str) -> Result<Self, TuError> {
if let Some(versioned) = input.trim().strip_prefix("tensamin-tu:v2:") {
return Self::parse_v2(versioned);
}
Self::parse_v1(input)
}
fn parse_v1(input: &str) -> Result<Self, TuError> {
let (identity, encoded_keyring) = input
.trim()
.split_once("::")
@ -63,22 +79,70 @@ impl TuCredential {
let keyring = keyring_from_base64(encoded_keyring).ok_or(TuError::InvalidKeyring)?;
Ok(Self {
user_id,
omega_host: omega_host.trim().to_owned(),
authority: TuAuthority::Omega(omega_host.trim().to_owned()),
keyring,
})
}
fn parse_v2(input: &str) -> Result<Self, TuError> {
let (identity, encoded_keyring) = input.split_once("::").ok_or(TuError::InvalidFormat)?;
if encoded_keyring.is_empty() || encoded_keyring.contains("::") {
return Err(TuError::InvalidFormat);
}
let (user_id, node_id) = identity.split_once('@').ok_or(TuError::InvalidFormat)?;
if node_id.contains('@') {
return Err(TuError::InvalidFormat);
}
let user_id = user_id.parse::<i64>().map_err(|_| TuError::InvalidUserId)?;
if !(1..=MAX_PROTOCOL_ID).contains(&user_id) {
return Err(TuError::InvalidUserId);
}
let node_id = IotaNodeId::new(node_id).map_err(|_| TuError::InvalidAuthority)?;
let keyring = keyring_from_base64(encoded_keyring).ok_or(TuError::InvalidKeyring)?;
Ok(Self {
user_id,
authority: TuAuthority::Iota(node_id),
keyring,
})
}
pub fn omega_host(&self) -> Option<&str> {
match &self.authority {
TuAuthority::Omega(host) => Some(host),
TuAuthority::Iota(_) => None,
}
}
pub fn principal(&self) -> Result<PrincipalId, TuError> {
let user_id = u64::try_from(self.user_id).map_err(|_| TuError::InvalidUserId)?;
let authority = match &self.authority {
TuAuthority::Omega(host) => {
AuthorityId::omega_legacy(host).map_err(|_| TuError::InvalidAuthority)?
}
TuAuthority::Iota(node) => AuthorityId::for_iota(node),
};
Ok(PrincipalId { authority, user_id })
}
pub fn public_key_bundle(&self) -> PublicKeyBundle {
self.keyring.public_key_bundle()
}
pub fn to_canonical_string(&self) -> String {
format!(
"{}@{}::{}",
self.user_id,
self.omega_host,
keyring_to_base64(&self.keyring)
)
match &self.authority {
TuAuthority::Omega(host) => format!(
"{}@{}::{}",
self.user_id,
host,
keyring_to_base64(&self.keyring)
),
TuAuthority::Iota(node) => format!(
"tensamin-tu:v2:{}@{}::{}",
self.user_id,
node.as_str(),
keyring_to_base64(&self.keyring)
),
}
}
}
@ -91,18 +155,37 @@ mod tests {
fn round_trip_is_canonical() {
let credential = TuCredential {
user_id: 42,
omega_host: "omega.example:443".into(),
authority: TuAuthority::Omega("omega.example:443".into()),
keyring: generate_keyring(),
};
let parsed = TuCredential::parse(&credential.to_canonical_string()).unwrap();
assert_eq!(parsed.user_id, 42);
assert_eq!(parsed.omega_host, "omega.example:443");
assert_eq!(parsed.omega_host(), Some("omega.example:443"));
assert_eq!(
parsed.to_canonical_string(),
credential.to_canonical_string()
);
}
#[test]
fn decentralized_round_trip_preserves_principal() {
let node = iota_identity::LocalNodeIdentity::from_keyring(generate_keyring())
.unwrap()
.node_id()
.clone();
let credential = TuCredential {
user_id: 7,
authority: TuAuthority::Iota(node.clone()),
keyring: generate_keyring(),
};
let parsed = TuCredential::parse(&credential.to_canonical_string()).unwrap();
assert_eq!(parsed.authority, TuAuthority::Iota(node.clone()));
assert_eq!(
parsed.principal().unwrap().authority,
AuthorityId::for_iota(&node)
);
}
#[test]
fn rejects_malformed_credentials() {
for value in [