Listen to git commits for a specific repo and run a shell command
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}