key_management.rs (12891B)
1 use super::RadrootsRuntime; 2 use crate::RadrootsAppError; 3 #[cfg(feature = "nostr-client")] 4 use radroots_identity::{RadrootsIdentity, RadrootsIdentityId}; 5 6 #[derive(uniffi::Record, Debug, Clone)] 7 pub struct NostrIdentityRecord { 8 pub id: String, 9 pub public_key_hex: String, 10 pub public_key_npub: String, 11 pub label: Option<String>, 12 pub is_selected: bool, 13 } 14 15 #[derive(uniffi::Record, Debug, Clone)] 16 pub struct NostrIdentitySnapshot { 17 pub has_selected_signing_identity: bool, 18 pub selected_identity_id: Option<String>, 19 pub selected_npub: Option<String>, 20 pub identities: Vec<NostrIdentityRecord>, 21 } 22 23 #[derive(uniffi::Record, Debug, Clone)] 24 pub struct NostrHostCustodyIdentity { 25 pub id: String, 26 pub public_key_hex: String, 27 pub public_key_npub: String, 28 } 29 30 #[cfg(feature = "nostr-client")] 31 fn account_record( 32 net: &radroots_net_core::Net, 33 account_id: &RadrootsIdentityId, 34 ) -> Result<NostrIdentityRecord, RadrootsAppError> { 35 let selected_identity_id = net 36 .accounts 37 .default_account_id() 38 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 39 let account = net 40 .accounts 41 .list_accounts() 42 .map_err(|e| RadrootsAppError::identity(format!("{e}")))? 43 .into_iter() 44 .find(|account| &account.account_id == account_id) 45 .ok_or_else(|| RadrootsAppError::identity(format!("identity not found: {account_id}")))?; 46 let is_selected = selected_identity_id 47 .as_ref() 48 .map(|selected| selected == &account.account_id) 49 .unwrap_or(false); 50 51 Ok(NostrIdentityRecord { 52 id: account.account_id.to_string(), 53 public_key_hex: account.public_identity.public_key_hex, 54 public_key_npub: account.public_identity.public_key_npub, 55 label: account.label, 56 is_selected, 57 }) 58 } 59 60 #[cfg(feature = "nostr-client")] 61 fn invalidate_nostr_runtime(net: &mut radroots_net_core::Net) { 62 net.set_nostr_signer(None); 63 net.nostr = None; 64 } 65 66 #[cfg(feature = "nostr-client")] 67 fn identity_from_secret(secret_key: &str) -> Result<RadrootsIdentity, RadrootsAppError> { 68 RadrootsIdentity::from_secret_key_str(secret_key) 69 .map_err(|e| RadrootsAppError::identity(format!("{e}"))) 70 } 71 72 #[cfg(feature = "nostr-client")] 73 fn host_custody_identity_from_secret( 74 secret_key: &str, 75 ) -> Result<(RadrootsIdentity, NostrHostCustodyIdentity), RadrootsAppError> { 76 let identity = identity_from_secret(secret_key)?; 77 let record = NostrHostCustodyIdentity { 78 id: identity.id().to_string(), 79 public_key_hex: identity.public_key_hex(), 80 public_key_npub: identity.public_key_npub(), 81 }; 82 Ok((identity, record)) 83 } 84 85 #[cfg(feature = "nostr-client")] 86 fn restore_host_custody_identity( 87 net: &mut radroots_net_core::Net, 88 identity: &RadrootsIdentity, 89 label: Option<String>, 90 make_selected: bool, 91 ) -> Result<NostrIdentityRecord, RadrootsAppError> { 92 let account_id = net 93 .accounts 94 .upsert_identity(identity, label, make_selected) 95 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 96 invalidate_nostr_runtime(net); 97 account_record(net, &account_id) 98 } 99 100 #[cfg_attr(not(coverage_nightly), uniffi::export)] 101 impl RadrootsRuntime { 102 pub fn nostr_identity_has_selected_signing_identity(&self) -> bool { 103 #[cfg(feature = "nostr-client")] 104 { 105 if let Ok(guard) = self.net.lock() { 106 return guard 107 .accounts 108 .default_signing_identity() 109 .ok() 110 .flatten() 111 .is_some(); 112 } 113 } 114 115 #[cfg(not(feature = "nostr-client"))] 116 { 117 false 118 } 119 120 #[cfg(feature = "nostr-client")] 121 false 122 } 123 124 pub fn nostr_identity_selected_npub(&self) -> Option<String> { 125 #[cfg(feature = "nostr-client")] 126 { 127 if let Ok(guard) = self.net.lock() { 128 return guard 129 .accounts 130 .default_public_identity() 131 .ok() 132 .flatten() 133 .map(|identity| identity.public_key_npub); 134 } 135 } 136 137 #[cfg(not(feature = "nostr-client"))] 138 { 139 None 140 } 141 142 #[cfg(feature = "nostr-client")] 143 None 144 } 145 146 pub fn nostr_identity_list(&self) -> Result<Vec<NostrIdentityRecord>, RadrootsAppError> { 147 #[cfg(feature = "nostr-client")] 148 { 149 let guard = match self.net.lock() { 150 Ok(guard) => guard, 151 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 152 }; 153 let selected_identity_id = guard 154 .accounts 155 .default_account_id() 156 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 157 let accounts = guard 158 .accounts 159 .list_accounts() 160 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 161 return Ok(accounts 162 .into_iter() 163 .map(|account| { 164 let is_selected = selected_identity_id 165 .as_ref() 166 .map(|selected| selected == &account.account_id) 167 .unwrap_or(false); 168 NostrIdentityRecord { 169 id: account.account_id.to_string(), 170 public_key_hex: account.public_identity.public_key_hex, 171 public_key_npub: account.public_identity.public_key_npub, 172 label: account.label, 173 is_selected, 174 } 175 }) 176 .collect()); 177 } 178 #[cfg(not(feature = "nostr-client"))] 179 { 180 Err(RadrootsAppError::unsupported("nostr disabled")) 181 } 182 } 183 184 pub fn nostr_identity_list_ids(&self) -> Result<Vec<String>, RadrootsAppError> { 185 Ok(self 186 .nostr_identity_list()? 187 .into_iter() 188 .map(|identity| identity.id) 189 .collect()) 190 } 191 192 pub fn nostr_identity_snapshot(&self) -> Result<NostrIdentitySnapshot, RadrootsAppError> { 193 #[cfg(feature = "nostr-client")] 194 { 195 let guard = match self.net.lock() { 196 Ok(guard) => guard, 197 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 198 }; 199 let selected_identity_id = guard 200 .accounts 201 .default_account_id() 202 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 203 let selected_npub = guard 204 .accounts 205 .default_public_identity() 206 .map_err(|e| RadrootsAppError::identity(format!("{e}")))? 207 .map(|identity| identity.public_key_npub); 208 let has_selected_signing_identity = guard 209 .accounts 210 .default_signing_identity() 211 .ok() 212 .flatten() 213 .is_some(); 214 let identities = guard 215 .accounts 216 .list_accounts() 217 .map_err(|e| RadrootsAppError::identity(format!("{e}")))? 218 .into_iter() 219 .map(|account| { 220 let is_selected = selected_identity_id 221 .as_ref() 222 .map(|selected| selected == &account.account_id) 223 .unwrap_or(false); 224 NostrIdentityRecord { 225 id: account.account_id.to_string(), 226 public_key_hex: account.public_identity.public_key_hex, 227 public_key_npub: account.public_identity.public_key_npub, 228 label: account.label, 229 is_selected, 230 } 231 }) 232 .collect(); 233 return Ok(NostrIdentitySnapshot { 234 has_selected_signing_identity, 235 selected_identity_id: selected_identity_id.map(|id| id.to_string()), 236 selected_npub, 237 identities, 238 }); 239 } 240 #[cfg(not(feature = "nostr-client"))] 241 { 242 Err(RadrootsAppError::unsupported("nostr disabled")) 243 } 244 } 245 246 pub fn nostr_identity_validate_host_custody_secret( 247 &self, 248 secret_key: String, 249 ) -> Result<NostrHostCustodyIdentity, RadrootsAppError> { 250 #[cfg(feature = "nostr-client")] 251 { 252 return host_custody_identity_from_secret(secret_key.as_str()) 253 .map(|(_, identity)| identity); 254 } 255 #[cfg(not(feature = "nostr-client"))] 256 { 257 let _ = secret_key; 258 Err(RadrootsAppError::unsupported("nostr disabled")) 259 } 260 } 261 262 pub fn nostr_identity_restore_host_custody_secret( 263 &self, 264 secret_key: String, 265 label: Option<String>, 266 make_selected: bool, 267 ) -> Result<NostrIdentityRecord, RadrootsAppError> { 268 #[cfg(feature = "nostr-client")] 269 { 270 let mut guard = match self.net.lock() { 271 Ok(guard) => guard, 272 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 273 }; 274 let (identity, _) = host_custody_identity_from_secret(secret_key.as_str())?; 275 return restore_host_custody_identity(&mut guard, &identity, label, make_selected); 276 } 277 #[cfg(not(feature = "nostr-client"))] 278 { 279 let _ = (secret_key, label, make_selected); 280 Err(RadrootsAppError::unsupported("nostr disabled")) 281 } 282 } 283 284 pub fn nostr_identity_select(&self, identity_id: String) -> Result<(), RadrootsAppError> { 285 #[cfg(feature = "nostr-client")] 286 { 287 let mut guard = match self.net.lock() { 288 Ok(guard) => guard, 289 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 290 }; 291 let account_id = RadrootsIdentityId::parse(identity_id.as_str()) 292 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 293 guard 294 .accounts 295 .set_default_account(&account_id) 296 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 297 invalidate_nostr_runtime(&mut guard); 298 Ok(()) 299 } 300 #[cfg(not(feature = "nostr-client"))] 301 { 302 let _ = identity_id; 303 Err(RadrootsAppError::unsupported("nostr disabled")) 304 } 305 } 306 307 pub fn nostr_identity_remove(&self, identity_id: String) -> Result<(), RadrootsAppError> { 308 #[cfg(feature = "nostr-client")] 309 { 310 let mut guard = match self.net.lock() { 311 Ok(guard) => guard, 312 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 313 }; 314 let account_id = RadrootsIdentityId::parse(identity_id.as_str()) 315 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 316 guard 317 .accounts 318 .remove_account(&account_id) 319 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 320 invalidate_nostr_runtime(&mut guard); 321 Ok(()) 322 } 323 #[cfg(not(feature = "nostr-client"))] 324 { 325 let _ = identity_id; 326 Err(RadrootsAppError::unsupported("nostr disabled")) 327 } 328 } 329 330 pub fn nostr_identity_lock_host_custody_runtime(&self) -> Result<(), RadrootsAppError> { 331 #[cfg(feature = "nostr-client")] 332 { 333 let mut guard = match self.net.lock() { 334 Ok(guard) => guard, 335 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 336 }; 337 let accounts = guard 338 .accounts 339 .list_accounts() 340 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 341 for account in accounts { 342 guard 343 .accounts 344 .remove_account(&account.account_id) 345 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 346 } 347 guard 348 .accounts 349 .clear_default_account() 350 .map_err(|e| RadrootsAppError::identity(format!("{e}")))?; 351 invalidate_nostr_runtime(&mut guard); 352 if let Ok(mut rx_guard) = self.post_events_rx.lock() { 353 *rx_guard = None; 354 } 355 Ok(()) 356 } 357 #[cfg(not(feature = "nostr-client"))] 358 { 359 Err(RadrootsAppError::unsupported("nostr disabled")) 360 } 361 } 362 363 pub fn nostr_identity_reset_host_custody_runtime(&self) -> Result<(), RadrootsAppError> { 364 self.nostr_identity_lock_host_custody_runtime() 365 } 366 }