Listen to git commits for a specific repo and run a shell command
at master 5.5 kB view raw
1use trust_dns_resolver::Resolver; 2use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; 3 4#[derive(Debug)] 5pub struct DidDoc { 6 pub did: String, 7 pub pds: String, 8} 9 10fn get_txt_did(handle: &String) -> Result<String, ()> { 11 println!(" Trying dns TXT for _atproto.{}", handle); 12 // create a txt resolver 13 let resolver = match Resolver::new(ResolverConfig::default(), ResolverOpts::default()) { 14 Ok(val) => val, 15 Err(_) => { 16 println!(" Couldn't create a DNS resolver"); 17 return Err(()); 18 } 19 }; 20 21 // resolve _atproto.handle to a TXT record 22 let txt_res = match resolver.txt_lookup("_atproto.".to_owned() + &handle) { 23 Ok(val) => val, 24 Err(_) => { 25 println!(" Couldn't resolve to a TXT record"); 26 return Err(()); 27 } // collect all entries and convert to strings 28 } 29 .into_iter() 30 .map(|x| x.to_string()) 31 .collect::<Vec<_>>(); 32 33 // filter entries which do not start with `did=` 34 let did_res = txt_res 35 .clone() 36 .extract_if(.., |x| x.starts_with("did=")) 37 .collect::<Vec<_>>(); 38 // only 1 did= can exist 39 // https://atproto.com/specs/handle#:~:text=If%20multiple%20valid%20records%20with%20different%20DIDs%20are%20present,%20resolution%20should%20fail. 40 if did_res.len() != 1 { 41 println!(" Found too many DIDs for this handle"); 42 return Err(()); 43 } 44 45 return Ok(did_res[0][4..].to_string()); 46} 47 48fn get_http_did(handle: &String) -> Result<String, ()> { 49 println!( 50 " Trying https for https://{}/.well-known/atproto-did", 51 handle 52 ); 53 let res = 54 match reqwest::blocking::get("https://".to_owned() + handle + "/.well-known/atproto-did") { 55 Ok(val) => val, 56 Err(_) => { 57 println!(" GET request failed"); 58 return Err(()); 59 } 60 }; 61 62 // as per spec, non 2xx code means failure 63 if !res.status().is_success() { 64 println!( 65 " Got non 2xx status code: {}", 66 res.status().as_str() 67 ); 68 return Err(()); 69 } 70 71 let did_unparsed = match res.text() { 72 Ok(val) => val, 73 Err(_) => { 74 println!(" Missing or malformed body response"); 75 return Err(()); 76 } 77 }; 78 79 let did = did_unparsed.trim(); 80 81 if !did.starts_with("did:") { 82 println!(" Did not find a DID"); 83 return Err(()); 84 }; 85 return Ok(String::from(did)); 86} 87 88fn parse_doc(did: String, text: String) -> Result<DidDoc, ()> { 89 let text_json = match json::parse(&text) { 90 Ok(val) => val, 91 Err(_) => { 92 println!(" Malformed DID document"); 93 return Err(()); 94 } 95 }; 96 97 for service in text_json["service"].members() { 98 if service["id"] 99 .as_str() 100 .is_some_and(|x| x.ends_with("#atproto_pds")) 101 && service["type"] 102 .as_str() 103 .is_some_and(|x| x == "AtprotoPersonalDataServer") 104 && let Some(pds) = service["serviceEndpoint"].as_str() 105 { 106 return Ok(DidDoc { 107 did: did, 108 pds: pds.to_string(), 109 }); 110 } 111 } 112 113 println!(" Missing fields from DID document"); 114 return Err(()); 115} 116 117fn get_plc_doc(plc: &str) -> Result<DidDoc, ()> { 118 let res = match reqwest::blocking::get("https://plc.directory/did:plc:".to_owned() + plc) { 119 Ok(val) => val, 120 Err(_) => { 121 println!(" GET request failed"); 122 return Err(()); 123 } 124 }; 125 126 if !res.status().is_success() { 127 println!(" Got non 2xx status code: {}", res.status().as_str()); 128 129 return Err(()); 130 } 131 132 return parse_doc( 133 "did:plc:".to_owned() + plc, 134 match res.text() { 135 Ok(val) => val, 136 Err(_) => { 137 println!(" Missing or malformed body response"); 138 return Err(()); 139 } 140 }, 141 ); 142} 143 144fn get_web_doc(web: &str) -> Result<DidDoc, ()> { 145 let res = match reqwest::blocking::get("https://".to_owned() + web + "/.well-known/did.json") { 146 Ok(val) => val, 147 Err(_) => { 148 println!(" GET request failed"); 149 return Err(()); 150 } 151 }; 152 153 if !res.status().is_success() { 154 println!(" Got non 2xx status code: {}", res.status().as_str()); 155 return Err(()); 156 } 157 158 return parse_doc( 159 "did:web:".to_owned() + web, 160 match res.text() { 161 Ok(val) => val, 162 Err(_) => { 163 println!(" Missing or malformed body response"); 164 return Err(()); 165 } 166 }, 167 ); 168} 169 170pub fn get_did(handle: &String) -> Result<DidDoc, ()> { 171 println!(" Getting DID for {}", handle); 172 let did = if let Ok(did) = get_txt_did(&handle) { 173 did 174 } else { 175 if let Ok(did) = get_http_did(&handle) { 176 did 177 } else { 178 println!(" Could not get a DID"); 179 return Err(()); 180 } 181 }; 182 183 println!(" Getting DID document for {}", did); 184 let did_doc = if did.starts_with("did:plc:") { 185 get_plc_doc(&did[8..]) 186 } else if did.starts_with("did:web:") { 187 get_web_doc(&did[8..]) 188 } else { 189 println!(" Could not get a DID document"); 190 Err(()) 191 }; 192 193 return did_doc; 194}