Skip to main content

relay_actions/
storage.rs

1use anyhow::{Context, Result};
2use base64::{Engine as _, prelude::BASE64_STANDARD};
3use serde::{Deserialize, Serialize};
4use std::{collections::HashMap, path::PathBuf};
5
6use crate::sinks::{progress::ProgressSink, result::ResultSink};
7use relay_lib::{
8    crypto::{SigningKey, StaticSecret, hex},
9    prelude::{Address, InboxId, KeyRecord},
10};
11
12#[derive(Clone, Serialize, Deserialize)]
13pub struct Identity {
14    pub pub_record: KeyRecord,
15    pub inboxes: Vec<InboxId>,
16    #[serde(with = "hex::serde")]
17    pub signing_key: [u8; 32],
18    #[serde(with = "hex::serde")]
19    pub static_secret: [u8; 32],
20}
21
22impl Identity {
23    pub fn signing_key(&self) -> SigningKey {
24        SigningKey::from_bytes(&self.signing_key)
25    }
26
27    pub fn static_secret(&self) -> StaticSecret {
28        StaticSecret::from(self.static_secret)
29    }
30
31    pub fn address(&self) -> Result<Address> {
32        Address::parse(&self.pub_record.id).context("Failed to parse address from identity")
33    }
34
35    pub fn print(&self, level: u32, result: &dyn ResultSink) {
36        result.section(level, &self.pub_record.id);
37        result.bullet_label(
38            level + 1,
39            "Created",
40            &self.pub_record.created_at.to_string(),
41        );
42
43        if let Some(expires_at) = self.pub_record.expires_at {
44            result.bullet_label(level + 1, "Expires", &expires_at.to_string());
45        } else {
46            result.bullet(level + 1, "Never Expires");
47        }
48
49        if !self.inboxes.is_empty() {
50            let mut inboxes = self.inboxes.clone();
51            inboxes.sort_by(|a, b| a.canonical().cmp(b.canonical()));
52
53            result.section(level + 1, "Inboxes");
54            for inbox in inboxes.iter() {
55                result.bullet(level + 2, &inbox.to_string());
56            }
57        }
58    }
59}
60
61#[derive(Clone, Serialize, Deserialize)]
62pub struct StorageRoot {
63    pub identities: HashMap<Address, Identity>,
64}
65
66impl StorageRoot {
67    pub fn get_identity(&self, address: &Address) -> Option<&Identity> {
68        let mut address = address.clone();
69        address.inbox = None;
70        self.identities.get(&address)
71    }
72
73    pub fn export(&self, progress: &mut dyn ProgressSink, addresses: &[Address]) -> Result<String> {
74        progress.step("Validating addresses", "Validated addresses");
75        for address in addresses.iter() {
76            let mut addr = address.clone();
77            addr.inbox = None;
78            if !self.identities.contains_key(&addr) {
79                progress.abort(&format!(
80                    "No identity found for address {}",
81                    address.canonical()
82                ));
83            }
84            if address.inbox().is_some() {
85                progress.abort("Please specify addresses without inbox IDs");
86            }
87        }
88
89        progress.step("Exporting identities", "Exported identities");
90        let identities = self
91            .identities
92            .iter()
93            .filter(|(addr, _)| addresses.contains(addr))
94            .map(|(_, identity)| identity.clone())
95            .collect::<Vec<_>>();
96        let json = serde_json::to_vec(&identities)
97            .unwrap_or_else(|_| progress.abort("Failed to serialize identities"));
98        Ok(BASE64_STANDARD.encode(json))
99    }
100
101    pub fn import(
102        &mut self,
103        progress: &mut dyn ProgressSink,
104        data: &str,
105        replace: bool,
106    ) -> Result<Vec<Address>> {
107        progress.step("Decoding identity data", "Decoded identity data");
108        let json = BASE64_STANDARD
109            .decode(data)
110            .unwrap_or_else(|_| progress.abort("Failed to decode base64 identity data"));
111        let identities: Vec<Identity> = serde_json::from_slice(&json)
112            .unwrap_or_else(|_| progress.abort("Failed to deserialize identity data"));
113        let identities = identities
114            .into_iter()
115            .map(|identity| {
116                let mut address = identity.address().unwrap_or_else(|_| {
117                    progress.abort(&format!(
118                        "Failed to parse address from identity with ID {}",
119                        identity.pub_record.id
120                    ));
121                });
122                address.inbox = None;
123                (address, identity)
124            })
125            .collect::<HashMap<_, _>>();
126
127        progress.step(
128            "Checking for existing identities",
129            "Checked for existing identities",
130        );
131        for (addr, _) in identities.iter() {
132            if self.identities.contains_key(addr) {
133                if replace {
134                    progress.warn(&format!("Replacing existing identity for address {}", addr));
135                } else {
136                    progress.abort(&format!(
137                        "Identity for address {} already exists. Use --replace to overwrite.",
138                        addr
139                    ));
140                }
141            }
142        }
143
144        progress.step("Importing identities", "Imported identities");
145        let addresses: Vec<Address> = identities.keys().cloned().collect();
146        self.identities.extend(identities);
147
148        Ok(addresses)
149    }
150
151    pub fn delete_identity(&mut self, address: &Address) -> Result<()> {
152        let mut address = address.clone();
153        address.inbox = None;
154        if self.identities.remove(&address).is_none() {
155            anyhow::bail!("No identity found for address {}", address.canonical());
156        }
157        Ok(())
158    }
159}
160
161#[derive(Clone)]
162pub struct Storage {
163    pub root: StorageRoot,
164}
165
166impl Default for Storage {
167    fn default() -> Self {
168        Self {
169            root: StorageRoot {
170                identities: HashMap::new(),
171            },
172        }
173    }
174}
175
176impl Storage {
177    pub fn path() -> Result<PathBuf> {
178        let mut path = dirs::data_dir().context("Could not find data directory")?;
179        path.push("relay_cli");
180        std::fs::create_dir_all(&path).context("Could not create data directory")?;
181        path.push("storage.ron");
182        Ok(path)
183    }
184
185    pub fn init() -> Result<Self> {
186        if !Self::path()?.exists() {
187            Self::default().save()?;
188        }
189
190        let ron = std::fs::read_to_string(Self::path()?).context("Could not read storage file")?;
191        let root: StorageRoot =
192            ron::de::from_str(&ron).context("Could not deserialize storage file")?;
193        Ok(Self { root })
194    }
195
196    pub fn save(&self) -> Result<()> {
197        let ron = ron::ser::to_string_pretty(&self.root, ron::ser::PrettyConfig::default())
198            .context("Could not serialize storage")?;
199        std::fs::write(Self::path()?, ron).context("Could not write storage file")?;
200        Ok(())
201    }
202
203    pub fn delete(&self) -> Result<()> {
204        std::fs::remove_file(Self::path()?).context("Could not delete storage file")?;
205
206        let path = Self::path()?;
207        let parent = path.parent().context("Could not get parent directory")?;
208        std::fs::remove_dir_all(parent).context("Could not delete storage directory")?;
209
210        Ok(())
211    }
212
213    pub fn reset(&mut self) {
214        *self = Self::default();
215    }
216}