a simple rust terminal ui (tui) for setting up alternative plc rotation keys driven by: secure enclave hardware (not synced) or software-based keys (synced to icloud)
plc
secure-enclave
touchid
icloud
atproto
1use anyhow::{Context, Result};
2use crate::plc::PlcState;
3
4const DEFAULT_PLC_DIRECTORY: &str = "https://plc.directory";
5
6pub struct PlcDirectoryClient {
7 client: reqwest::Client,
8 base_url: String,
9}
10
11impl PlcDirectoryClient {
12 pub fn new() -> Self {
13 Self {
14 client: reqwest::Client::new(),
15 base_url: DEFAULT_PLC_DIRECTORY.to_string(),
16 }
17 }
18
19 /// Fetch the current PLC state for a DID.
20 pub async fn get_state(&self, did: &str) -> Result<PlcState> {
21 let url = format!("{}/{}/data", self.base_url, did);
22 let resp = self
23 .client
24 .get(&url)
25 .send()
26 .await
27 .context("Failed to fetch PLC state")?;
28
29 if !resp.status().is_success() {
30 let status = resp.status();
31 let body = resp.text().await.unwrap_or_default();
32 anyhow::bail!("PLC directory returned {}: {}", status, body);
33 }
34
35 let mut state: PlcState = resp
36 .json()
37 .await
38 .context("Failed to parse PLC state")?;
39
40 // The /data endpoint doesn't include the DID in the response body,
41 // so set it from the request
42 if state.did.is_empty() {
43 state.did = did.to_string();
44 }
45
46 Ok(state)
47 }
48
49 /// Fetch the audit log for a DID.
50 pub async fn get_audit_log(&self, did: &str) -> Result<Vec<serde_json::Value>> {
51 let url = format!("{}/{}/log/audit", self.base_url, did);
52 let resp = self
53 .client
54 .get(&url)
55 .send()
56 .await
57 .context("Failed to fetch audit log")?;
58
59 if !resp.status().is_success() {
60 let status = resp.status();
61 let body = resp.text().await.unwrap_or_default();
62 anyhow::bail!("PLC directory returned {}: {}", status, body);
63 }
64
65 let log: Vec<serde_json::Value> = resp
66 .json()
67 .await
68 .context("Failed to parse audit log")?;
69
70 Ok(log)
71 }
72
73 /// Submit a signed PLC operation.
74 pub async fn submit_operation(
75 &self,
76 did: &str,
77 operation: &serde_json::Value,
78 ) -> Result<String> {
79 let url = format!("{}/{}", self.base_url, did);
80 let resp = self
81 .client
82 .post(&url)
83 .json(operation)
84 .send()
85 .await
86 .context("Failed to submit PLC operation")?;
87
88 if !resp.status().is_success() {
89 let status = resp.status();
90 let body = resp.text().await.unwrap_or_default();
91 anyhow::bail!("PLC directory returned {}: {}", status, body);
92 }
93
94 Ok("Operation submitted successfully".to_string())
95 }
96}