3
assets/icons/encryption.svg
Normal file
3
assets/icons/encryption.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8.75a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3Zm0 0v6m8.25-2.838v-4.97a2 2 0 0 0-1.367-1.898l-6.25-2.083a2 2 0 0 0-1.265 0l-6.25 2.083A2 2 0 0 0 3.75 6.942v4.97c0 4.973 4.25 7.338 8.25 9.496 4-2.158 8.25-4.523 8.25-9.496Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 425 B |
@@ -1408,8 +1408,8 @@ impl Render for Chat {
|
||||
)
|
||||
.child(TextInput::new(&self.input))
|
||||
.child(
|
||||
Button::new("options")
|
||||
.icon(IconName::Settings)
|
||||
Button::new("encryptions")
|
||||
.icon(IconName::Encryption)
|
||||
.ghost()
|
||||
.large()
|
||||
.popup_menu(move |this, _window, _cx| {
|
||||
|
||||
@@ -50,14 +50,12 @@ impl Default for SendOptions {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SendReport {
|
||||
pub receiver: PublicKey,
|
||||
|
||||
pub status: Option<Output<EventId>>,
|
||||
pub error: Option<SharedString>,
|
||||
|
||||
pub on_hold: Option<Event>,
|
||||
pub encryption: bool,
|
||||
pub relays_not_found: bool,
|
||||
pub device_not_found: bool,
|
||||
|
||||
pub on_hold: Option<Event>,
|
||||
}
|
||||
|
||||
impl SendReport {
|
||||
@@ -67,6 +65,7 @@ impl SendReport {
|
||||
status: None,
|
||||
error: None,
|
||||
on_hold: None,
|
||||
encryption: false,
|
||||
relays_not_found: false,
|
||||
device_not_found: false,
|
||||
}
|
||||
@@ -87,6 +86,11 @@ impl SendReport {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn encryption(mut self) -> Self {
|
||||
self.encryption = true;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn relays_not_found(mut self) -> Self {
|
||||
self.relays_not_found = true;
|
||||
self
|
||||
@@ -427,8 +431,15 @@ impl Room {
|
||||
|
||||
/// Create a new message event (unsigned)
|
||||
pub fn create_message(&self, content: &str, replies: &[EventId], cx: &App) -> UnsignedEvent {
|
||||
// Get the app state
|
||||
let state = app_state();
|
||||
let relay_cache = state.relay_cache.read_blocking();
|
||||
|
||||
// Get current user
|
||||
let registry = Registry::global(cx);
|
||||
let public_key = registry.read(cx).signer_pubkey().unwrap();
|
||||
|
||||
// Get room's subject
|
||||
let subject = self.subject.clone();
|
||||
|
||||
let mut tags = vec![];
|
||||
@@ -437,7 +448,20 @@ impl Room {
|
||||
//
|
||||
// NOTE: current user will be removed from the list of receivers
|
||||
for (member, _) in self.members.iter() {
|
||||
tags.push(Tag::public_key(member.to_owned()));
|
||||
// Get relay hint if available
|
||||
let relay_url = relay_cache
|
||||
.get(member)
|
||||
.and_then(|urls| urls.iter().nth(0).cloned());
|
||||
|
||||
// Construct a public key tag with relay hint
|
||||
let tag = TagStandard::PublicKey {
|
||||
public_key: member.to_owned(),
|
||||
relay_url,
|
||||
alias: None,
|
||||
uppercase: false,
|
||||
};
|
||||
|
||||
tags.push(Tag::from_standardized_without_cell(tag));
|
||||
}
|
||||
|
||||
// Add subject tag if it's present
|
||||
@@ -452,11 +476,12 @@ impl Room {
|
||||
tags.push(Tag::event(replies[0]))
|
||||
} else {
|
||||
for id in replies {
|
||||
tags.push(Tag::from_standardized_without_cell(TagStandard::Quote {
|
||||
let tag = TagStandard::Quote {
|
||||
event_id: id.to_owned(),
|
||||
relay_url: None,
|
||||
public_key: None,
|
||||
}))
|
||||
};
|
||||
tags.push(Tag::from_standardized_without_cell(tag))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,91 +510,47 @@ impl Room {
|
||||
let opts = opts.to_owned();
|
||||
|
||||
cx.background_spawn(async move {
|
||||
let states = app_state();
|
||||
let client = states.client();
|
||||
let device = states.device.read().await.encryption_keys.clone();
|
||||
let state = app_state();
|
||||
|
||||
let client = state.client();
|
||||
let relay_cache = state.relay_cache.read().await;
|
||||
let device = state.device.read().await.encryption.clone();
|
||||
|
||||
let user_signer = client.signer().await?;
|
||||
let user_pubkey = user_signer.get_public_key().await?;
|
||||
|
||||
// Collect relay hints for all participants (including current user)
|
||||
let mut participants: Vec<PublicKey> = members.keys().cloned().collect();
|
||||
|
||||
if !participants.contains(&user_pubkey) {
|
||||
participants.push(user_pubkey);
|
||||
}
|
||||
|
||||
// Initialize relay cache
|
||||
let mut relay_cache: HashMap<PublicKey, Vec<RelayUrl>> = HashMap::new();
|
||||
|
||||
for participant in participants.iter().cloned() {
|
||||
let urls = states.messaging_relays(participant).await;
|
||||
relay_cache.insert(participant, urls);
|
||||
}
|
||||
|
||||
// Update rumor with relay hints for each receiver
|
||||
let mut rumor = rumor;
|
||||
let mut tags_with_hints = Vec::new();
|
||||
|
||||
for tag in rumor.tags.into_iter() {
|
||||
if let Some(standard) = tag.as_standardized().cloned() {
|
||||
match standard {
|
||||
TagStandard::PublicKey {
|
||||
public_key,
|
||||
alias,
|
||||
uppercase,
|
||||
..
|
||||
} => {
|
||||
let relay_url = relay_cache
|
||||
.get(&public_key)
|
||||
.and_then(|urls| urls.first().cloned());
|
||||
|
||||
let updated = TagStandard::PublicKey {
|
||||
public_key,
|
||||
relay_url,
|
||||
alias,
|
||||
uppercase,
|
||||
};
|
||||
|
||||
tags_with_hints.push(Tag::from_standardized_without_cell(updated));
|
||||
}
|
||||
_ => tags_with_hints.push(tag),
|
||||
}
|
||||
} else {
|
||||
tags_with_hints.push(tag);
|
||||
}
|
||||
}
|
||||
rumor.tags = Tags::from_list(tags_with_hints);
|
||||
|
||||
// Remove the current user's public key from the list of receivers
|
||||
// Current user will be handled separately
|
||||
let (public_key, device_pubkey) = members.remove_entry(&user_pubkey).unwrap();
|
||||
|
||||
// Determine the signer will be used based on the provided options
|
||||
let signer = Self::select_signer(&opts.signer_kind, device, user_signer)?;
|
||||
let (signer, signer_kind) =
|
||||
Self::select_signer(&opts.signer_kind, device, user_signer)?;
|
||||
|
||||
// Collect the send reports
|
||||
let mut reports: Vec<SendReport> = vec![];
|
||||
|
||||
for (receiver, device_pubkey) in members.into_iter() {
|
||||
let urls = relay_cache.get(&receiver).cloned().unwrap_or_default();
|
||||
for (user, device_pubkey) in members.into_iter() {
|
||||
let urls = relay_cache.get(&user).cloned().unwrap_or_default();
|
||||
|
||||
// Check if there are any relays to send the message to
|
||||
if urls.is_empty() {
|
||||
reports.push(SendReport::new(receiver).relays_not_found());
|
||||
reports.push(SendReport::new(user).relays_not_found());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip sending if using encryption keys but device not found
|
||||
if device_pubkey.is_none() && matches!(opts.signer_kind, SignerKind::Encryption) {
|
||||
reports.push(SendReport::new(receiver).device_not_found());
|
||||
reports.push(SendReport::new(user).device_not_found());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine the receiver based on the signer kind
|
||||
let receiver = Self::select_receiver(&opts.signer_kind, user, device_pubkey)?;
|
||||
|
||||
// Construct the gift wrap event
|
||||
let rumor = rumor.clone();
|
||||
let target = Self::select_receiver(&opts.signer_kind, receiver, device_pubkey);
|
||||
let event = EventBuilder::gift_wrap(&signer, &target, rumor, vec![]).await?;
|
||||
let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?;
|
||||
|
||||
// Send the event to the messaging relays
|
||||
match client.send_event_to(urls, &event).await {
|
||||
@@ -581,7 +562,7 @@ impl Room {
|
||||
if auth {
|
||||
// Wait for authenticated and resent event successfully
|
||||
for attempt in 0..=SEND_RETRY {
|
||||
let retry_manager = states.tracker().read().await;
|
||||
let retry_manager = state.tracker().read().await;
|
||||
let ids = retry_manager.resent_ids();
|
||||
|
||||
// Check if event was successfully resent
|
||||
@@ -609,10 +590,12 @@ impl Room {
|
||||
}
|
||||
}
|
||||
|
||||
// Select a signer based on previous usage, not from the options
|
||||
let receiver = Self::select_receiver(&signer_kind, public_key, device_pubkey)?;
|
||||
|
||||
// Construct a gift wrap to back up to current user's owned messaging relays
|
||||
let rumor = rumor.clone();
|
||||
let target = Self::select_receiver(&opts.signer_kind, public_key, device_pubkey);
|
||||
let event = EventBuilder::gift_wrap(&signer, &target, rumor, vec![]).await?;
|
||||
let event = EventBuilder::gift_wrap(&signer, &receiver, rumor, []).await?;
|
||||
|
||||
// Only send a backup message to current user if sent successfully to others
|
||||
if opts.backup() && reports.iter().all(|r| r.is_sent_success()) {
|
||||
@@ -700,24 +683,37 @@ impl Room {
|
||||
})
|
||||
}
|
||||
|
||||
fn select_signer<T>(kind: &SignerKind, device: Option<T>, user: T) -> Result<T, Error>
|
||||
fn select_signer<T>(
|
||||
kind: &SignerKind,
|
||||
device: Option<T>,
|
||||
user: T,
|
||||
) -> Result<(T, SignerKind), Error>
|
||||
where
|
||||
T: NostrSigner,
|
||||
{
|
||||
match kind {
|
||||
SignerKind::Auto => device
|
||||
.map(|d| (d, SignerKind::Encryption))
|
||||
.or(Some((user, SignerKind::User)))
|
||||
.ok_or_else(|| anyhow!("No signer available")),
|
||||
SignerKind::Encryption => device
|
||||
.map(|d| (d, SignerKind::Encryption))
|
||||
.ok_or_else(|| anyhow!("No encryption key found")),
|
||||
SignerKind::User => Ok((user, SignerKind::User)),
|
||||
}
|
||||
}
|
||||
|
||||
fn select_receiver(
|
||||
kind: &SignerKind,
|
||||
user: PublicKey,
|
||||
device: Option<PublicKey>,
|
||||
) -> Result<PublicKey, Error> {
|
||||
match kind {
|
||||
SignerKind::Encryption => {
|
||||
Ok(device.ok_or_else(|| anyhow!("No encryption keys found"))?)
|
||||
Ok(device.ok_or_else(|| anyhow!("Receiver's encryption key not found"))?)
|
||||
}
|
||||
SignerKind::User => Ok(user),
|
||||
SignerKind::Auto => Ok(device.unwrap_or(user)),
|
||||
}
|
||||
}
|
||||
|
||||
fn select_receiver(kind: &SignerKind, user: PublicKey, device: Option<PublicKey>) -> PublicKey {
|
||||
match kind {
|
||||
SignerKind::Encryption => device.unwrap(),
|
||||
SignerKind::User => user,
|
||||
SignerKind::Auto => device.unwrap_or(user),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,20 +6,34 @@ use nostr_sdk::prelude::*;
|
||||
pub struct Device {
|
||||
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||
///
|
||||
/// The client keys that used for communication between devices
|
||||
pub client_keys: Option<Arc<dyn NostrSigner>>,
|
||||
/// Client Key that used for communication between devices
|
||||
pub client: Option<Arc<dyn NostrSigner>>,
|
||||
|
||||
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||
///
|
||||
/// The encryption keys that used for encryption and decryption
|
||||
pub encryption_keys: Option<Arc<dyn NostrSigner>>,
|
||||
/// Encryption key used for encryption and decryption instead of the user's identity
|
||||
pub encryption: Option<Arc<dyn NostrSigner>>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
client_keys: None,
|
||||
encryption_keys: None,
|
||||
client: None,
|
||||
encryption: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_client<T>(&mut self, keys: T)
|
||||
where
|
||||
T: NostrSigner,
|
||||
{
|
||||
self.client = Some(Arc::new(keys));
|
||||
}
|
||||
|
||||
pub fn set_encryption<T>(&mut self, keys: T)
|
||||
where
|
||||
T: NostrSigner,
|
||||
{
|
||||
self.encryption = Some(Arc::new(keys));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use std::borrow::Cow;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{anyhow, Context, Error};
|
||||
@@ -32,15 +31,18 @@ pub struct AppState {
|
||||
/// A client to interact with Nostr
|
||||
client: Client,
|
||||
|
||||
/// Tracks activity related to Nostr events
|
||||
event_tracker: RwLock<EventTracker>,
|
||||
|
||||
/// Signal channel for communication between Nostr and GPUI
|
||||
signal: Signal,
|
||||
|
||||
/// Ingester channel for processing public keys
|
||||
ingester: Ingester,
|
||||
|
||||
/// Tracks activity related to Nostr events
|
||||
event_tracker: RwLock<EventTracker>,
|
||||
|
||||
/// Cache of messaging relays for each public key
|
||||
pub relay_cache: RwLock<HashMap<PublicKey, HashSet<RelayUrl>>>,
|
||||
|
||||
/// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||
pub device: RwLock<Device>,
|
||||
|
||||
@@ -80,6 +82,7 @@ impl AppState {
|
||||
let client = ClientBuilder::default().database(lmdb).opts(opts).build();
|
||||
let device = RwLock::new(Device::default());
|
||||
let event_tracker = RwLock::new(EventTracker::default());
|
||||
let relay_cache = RwLock::new(HashMap::default());
|
||||
|
||||
let signal = Signal::default();
|
||||
let ingester = Ingester::default();
|
||||
@@ -88,6 +91,7 @@ impl AppState {
|
||||
client,
|
||||
device,
|
||||
event_tracker,
|
||||
relay_cache,
|
||||
signal,
|
||||
ingester,
|
||||
initialized_at: Timestamp::now(),
|
||||
@@ -287,15 +291,22 @@ impl AppState {
|
||||
}
|
||||
}
|
||||
Kind::InboxRelays => {
|
||||
// Only get up to 3 relays
|
||||
let urls: Vec<RelayUrl> = nip17::extract_relay_list(event.as_ref())
|
||||
.take(3)
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Subscribe to gift wrap events if messaging relays belong to the current user
|
||||
if let Ok(true) = self.is_self_authored(&event).await {
|
||||
let urls: Vec<RelayUrl> =
|
||||
nip17::extract_relay_list(event.as_ref()).cloned().collect();
|
||||
|
||||
if let Err(e) = self.get_messages(event.pubkey, &urls).await {
|
||||
log::error!("Failed to fetch messages: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the relay list for further queries
|
||||
let mut relay_cache = self.relay_cache.write().await;
|
||||
relay_cache.entry(event.pubkey).or_default().extend(urls);
|
||||
}
|
||||
Kind::ContactList => {
|
||||
if let Ok(true) = self.is_self_authored(&event).await {
|
||||
@@ -573,7 +584,7 @@ impl AppState {
|
||||
|
||||
// Initialize the client keys
|
||||
let mut device = self.device.write().await;
|
||||
device.client_keys = Some(Arc::new(keys));
|
||||
device.set_client(keys);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -620,7 +631,7 @@ impl AppState {
|
||||
|
||||
// Initialize the encryption keys
|
||||
let mut device = self.device.write().await;
|
||||
device.encryption_keys = Some(Arc::new(keys));
|
||||
device.set_encryption(keys);
|
||||
|
||||
// Store the encryption keys for future use
|
||||
self.set_keys("encryption", secret).await?;
|
||||
@@ -637,6 +648,9 @@ impl AppState {
|
||||
// Send the announcement event to the relays
|
||||
self.client.send_event(&event).await?;
|
||||
|
||||
// Re-subscribes to gift wrap events
|
||||
self.resubscribe_messages().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -649,7 +663,10 @@ impl AppState {
|
||||
// Check if the encryption keys match the announcement
|
||||
if announcement.public_key() == keys.public_key() {
|
||||
let mut device = self.device.write().await;
|
||||
device.encryption_keys = Some(Arc::new(keys));
|
||||
device.set_encryption(keys);
|
||||
|
||||
// Re-subscribes to gift wrap events
|
||||
self.resubscribe_messages().await?;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
@@ -665,13 +682,13 @@ impl AppState {
|
||||
let device = self.device.read().await;
|
||||
|
||||
// Client Keys are always known at this point
|
||||
let Some(client_keys) = device.client_keys.as_ref() else {
|
||||
let Some(client_key) = device.client.as_ref() else {
|
||||
return Err(anyhow!("Client Keys is required"));
|
||||
};
|
||||
|
||||
let signer = self.client.signer().await?;
|
||||
let public_key = signer.get_public_key().await?;
|
||||
let client_pubkey = client_keys.get_public_key().await?;
|
||||
let client_pubkey = client_key.get_public_key().await?;
|
||||
|
||||
// Get the encryption keys response from the database first
|
||||
let filter = Filter::new()
|
||||
@@ -691,7 +708,7 @@ impl AppState {
|
||||
.context("Invalid event's tags")?;
|
||||
|
||||
let payload = event.content.as_str();
|
||||
let decrypted = client_keys.nip44_decrypt(&root_device, payload).await?;
|
||||
let decrypted = client_key.nip44_decrypt(&root_device, payload).await?;
|
||||
|
||||
let secret = SecretKey::from_hex(&decrypted)?;
|
||||
let keys = Keys::new(secret);
|
||||
@@ -700,7 +717,10 @@ impl AppState {
|
||||
drop(device);
|
||||
|
||||
let mut device = self.device.write().await;
|
||||
device.encryption_keys = Some(Arc::new(keys));
|
||||
device.set_encryption(keys);
|
||||
|
||||
// Re-subscribes to gift wrap events
|
||||
self.resubscribe_messages().await?;
|
||||
}
|
||||
None => {
|
||||
// Construct encryption keys request event
|
||||
@@ -743,7 +763,7 @@ impl AppState {
|
||||
let device = self.device.read().await;
|
||||
|
||||
// Client Keys are always known at this point
|
||||
let Some(client_keys) = device.client_keys.as_ref() else {
|
||||
let Some(client_key) = device.client.as_ref() else {
|
||||
return Err(anyhow!("Client Keys is required"));
|
||||
};
|
||||
|
||||
@@ -751,7 +771,7 @@ impl AppState {
|
||||
let payload = res.payload();
|
||||
|
||||
// Decrypt the payload using the client keys
|
||||
let decrypted = client_keys.nip44_decrypt(&public_key, payload).await?;
|
||||
let decrypted = client_key.nip44_decrypt(&public_key, payload).await?;
|
||||
let secret = SecretKey::parse(&decrypted)?;
|
||||
let keys = Keys::new(secret);
|
||||
|
||||
@@ -759,7 +779,10 @@ impl AppState {
|
||||
drop(device);
|
||||
|
||||
let mut device = self.device.write().await;
|
||||
device.encryption_keys = Some(Arc::new(keys));
|
||||
device.set_encryption(keys);
|
||||
|
||||
// Re-subscribes to gift wrap events
|
||||
self.resubscribe_messages().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -771,15 +794,15 @@ impl AppState {
|
||||
let device = self.device.read().await;
|
||||
|
||||
// Client Keys are always known at this point
|
||||
let Some(client_keys) = device.client_keys.as_ref() else {
|
||||
let Some(client_key) = device.client.as_ref() else {
|
||||
return Err(anyhow!("Client Keys is required"));
|
||||
};
|
||||
|
||||
let encryption = self.get_keys("encryption").await?;
|
||||
let client_pubkey = client_keys.get_public_key().await?;
|
||||
let client_pubkey = client_key.get_public_key().await?;
|
||||
|
||||
// Encrypt the encryption keys with the client's signer
|
||||
let payload = client_keys
|
||||
let payload = client_key
|
||||
.nip44_encrypt(&target, &encryption.secret_key().to_secret_hex())
|
||||
.await?;
|
||||
|
||||
@@ -792,7 +815,7 @@ impl AppState {
|
||||
Tag::custom(TagKind::custom("P"), vec![client_pubkey]),
|
||||
Tag::public_key(target),
|
||||
])
|
||||
.sign(client_keys)
|
||||
.sign(client_key)
|
||||
.await?;
|
||||
|
||||
// Get the current user's signer and public key
|
||||
@@ -1014,11 +1037,11 @@ impl AppState {
|
||||
}
|
||||
|
||||
// Try to unwrap with the available signer
|
||||
if let Ok(unwrapped) = self.try_unwrap_gift(gift_wrap).await {
|
||||
if let Ok(unwrapped) = self.try_unwrap_gift_wrap(gift_wrap).await {
|
||||
let sender = unwrapped.sender;
|
||||
let mut rumor_unsigned = unwrapped.rumor;
|
||||
|
||||
if !self.verify_rumor_sender(sender, &rumor_unsigned) {
|
||||
if !self.verify_sender(sender, &rumor_unsigned).await {
|
||||
return Err(anyhow!("Invalid rumor"));
|
||||
};
|
||||
|
||||
@@ -1035,17 +1058,19 @@ impl AppState {
|
||||
}
|
||||
|
||||
// Helper method to try unwrapping with different signers
|
||||
async fn try_unwrap_gift(&self, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
|
||||
async fn try_unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result<UnwrappedGift, Error> {
|
||||
// Try to unwrap with the device's encryption keys first
|
||||
// NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md
|
||||
if let Some(signer) = self.device.read().await.encryption_keys.as_ref() {
|
||||
if let Some(signer) = self.device.read().await.encryption.as_ref() {
|
||||
if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await {
|
||||
return Ok(unwrapped);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to unwrap with the user's signer
|
||||
// Get user's signer
|
||||
let signer = self.client.signer().await?;
|
||||
|
||||
// Try to unwrap with the user's signer
|
||||
if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await {
|
||||
return Ok(unwrapped);
|
||||
}
|
||||
@@ -1087,7 +1112,22 @@ impl AppState {
|
||||
}
|
||||
|
||||
/// Verify that the sender of a rumor is the same as the sender of the event.
|
||||
fn verify_rumor_sender(&self, sender: PublicKey, rumor: &UnsignedEvent) -> bool {
|
||||
async fn verify_sender(&self, sender: PublicKey, rumor: &UnsignedEvent) -> bool {
|
||||
// If we have encryption keys, verify the sender matches the device's public key
|
||||
if let Some(keys) = self.device.read().await.encryption.as_ref() {
|
||||
if let Ok(public_key) = keys.get_public_key().await {
|
||||
let status = public_key == sender;
|
||||
// Only return if the status is true
|
||||
// else fallback to basic sender verification
|
||||
if status {
|
||||
return status;
|
||||
} else {
|
||||
return rumor.pubkey == sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to basic sender verification
|
||||
rumor.pubkey == sender
|
||||
}
|
||||
|
||||
@@ -1124,31 +1164,3 @@ impl AppState {
|
||||
Ok(Response::new(payload, root_device))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::app_state;
|
||||
|
||||
#[test]
|
||||
fn verify_rumor_sender_accepts_matching_sender() {
|
||||
let state = app_state();
|
||||
|
||||
let keys = Keys::generate();
|
||||
let public_key = keys.public_key();
|
||||
let rumor = EventBuilder::text_note("hello").build(public_key);
|
||||
|
||||
assert!(state.verify_rumor_sender(public_key, &rumor));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verify_rumor_sender_rejects_mismatched_sender() {
|
||||
let state = app_state();
|
||||
|
||||
let sender_keys = Keys::generate();
|
||||
let rumor_keys = Keys::generate();
|
||||
let rumor = EventBuilder::text_note("spoof").build(rumor_keys.public_key());
|
||||
|
||||
assert!(!state.verify_rumor_sender(sender_keys.public_key(), &rumor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pub enum IconName {
|
||||
Copy,
|
||||
Edit,
|
||||
Ellipsis,
|
||||
Encryption,
|
||||
Eye,
|
||||
EyeOff,
|
||||
EmojiFill,
|
||||
@@ -90,6 +91,7 @@ impl IconName {
|
||||
Self::Edit => "icons/edit.svg",
|
||||
Self::Ellipsis => "icons/ellipsis.svg",
|
||||
Self::Eye => "icons/eye.svg",
|
||||
Self::Encryption => "icons/encryption.svg",
|
||||
Self::EmojiFill => "icons/emoji-fill.svg",
|
||||
Self::EyeOff => "icons/eye-off.svg",
|
||||
Self::Info => "icons/info.svg",
|
||||
|
||||
@@ -909,11 +909,12 @@ impl PopupMenu {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let has_icon = self.has_icon;
|
||||
let selected = self.selected_index == Some(ix);
|
||||
const EDGE_PADDING: Pixels = px(4.);
|
||||
const INNER_PADDING: Pixels = px(8.);
|
||||
|
||||
let has_icon = self.has_icon;
|
||||
let selected = self.selected_index == Some(ix);
|
||||
|
||||
let is_submenu = matches!(item, PopupMenuItem::Submenu { .. });
|
||||
let group_name = format!("popup-menu-item-{ix}");
|
||||
|
||||
@@ -954,9 +955,8 @@ impl PopupMenu {
|
||||
h_flex()
|
||||
.cursor_default()
|
||||
.items_center()
|
||||
.gap_x_1()
|
||||
.font_semibold()
|
||||
.children(Self::render_icon(has_icon, None, window, cx))
|
||||
.text_xs()
|
||||
.child(label.clone()),
|
||||
),
|
||||
PopupMenuItem::ElementItem {
|
||||
|
||||
@@ -82,25 +82,25 @@ pending_encryption:
|
||||
label:
|
||||
en: "Wait for Approval"
|
||||
body_1:
|
||||
en: "Please open %{c} and approve the request for sharing encryption keys. Without access to them, Coop cannot decrypt your messages that are encrypted with encryption keys."
|
||||
en: "Please open %{c} and approve the request for sharing Encryption Key. Without access to them, Coop cannot decrypt your messages that are encrypted with Encryption Key."
|
||||
body_2:
|
||||
en: "Or you can click the 'Reset' button to reset the encryption keys."
|
||||
en: "Or you can click the 'Reset' button to reset the Encryption Key."
|
||||
body_3:
|
||||
en: "By resetting the encryption keys, you will not be able to view your messages that were encrypted with the old encryption keys."
|
||||
en: "By resetting the Encryption Key, you will not be able to view your messages that were encrypted with the old Encryption Key."
|
||||
|
||||
request_encryption:
|
||||
label:
|
||||
en: "Encryption Keys Request"
|
||||
en: "Encryption Key Request"
|
||||
body:
|
||||
en: "You've requested for the encryption keys from:"
|
||||
en: "You've requested for the encryption Key from:"
|
||||
|
||||
encryption:
|
||||
notice:
|
||||
en: "Encryption keys are being generated"
|
||||
en: "Encryption Key are being generated"
|
||||
success:
|
||||
en: "Encryption keys have been successfully set up"
|
||||
en: "Encryption Key have been successfully set up"
|
||||
reinit:
|
||||
en: "Encryption keys are being reinitialized"
|
||||
en: "Encryption Key are being reinitialized"
|
||||
|
||||
auto_update:
|
||||
updating:
|
||||
|
||||
Reference in New Issue
Block a user