Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic callback on secret stream #34

Merged
merged 22 commits into from
Oct 11, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 111 additions & 3 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

use std::{collections::BTreeMap, ops::Deref, time::Duration};

use futures_util::StreamExt;
use futures_util::{pin_mut, StreamExt};
use js_sys::{Array, Function, Map, Promise, Set};
use matrix_sdk_common::ruma::{self, serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use matrix_sdk_common::ruma::{
self, events::secret::request::SecretName, serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId,
UInt,
};
use matrix_sdk_crypto::{
backups::MegolmV1BackupKey,
store::{DeviceChanges, IdentityChanges},
types::RoomKeyBackupInfo,
EncryptionSyncChanges,
EncryptionSyncChanges, GossippedSecret,
};
use serde_json::{json, Value as JsonValue};
use serde_wasm_bindgen;
Expand Down Expand Up @@ -1094,6 +1097,94 @@ impl OlmMachine {
});
}

/// Register a callback which will be called whenever a secret
/// (`m.secret.send`) is received.
Comment on lines +1100 to +1101
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it true that it is called for all m.secret.send events? I got the impression that some secrets (the cross-signing keys?) were handled internally? I might be wrong though

Copy link
Member Author

@BillCarsonFr BillCarsonFr Oct 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No you are right for now it will only return is the backup decryption key. The cross signing keys are handled internally and custom secret are not yet supported.
Added a comment here and in get_secrets

///
/// The only secret this will currently broadcast is the
/// `m.megolm_backup.v1` (the cross signing secrets are handled internaly).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// `m.megolm_backup.v1` (the cross signing secrets are handled internaly).
/// `m.megolm_backup.v1` (the cross signing secrets are handled internally).

///
/// To request a secret from other devices, a client sends an
/// `m.secret.request` device event with `action` set to `request` and
/// `name` set to the identifier of the secret. A device that wishes to
/// share the secret will reply with an `m.secret.send` event, encrypted
/// using olm.
///
/// The secrets are guaranteed to have been received over a 1-to-1 encrypted
/// to_device message from a one of the user's own verified devices.
///
/// See https://matrix-org.github.io/matrix-rust-sdk/matrix_sdk_crypto/store/struct.Store.html#method.secrets_stream for more information.
///
/// `callback` should be a function that takes 2 arguments: the secret name
/// (string) and value (string).
Comment on lines +1117 to +1118
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does it not matter what it returns?

///
/// **Note**: if the secret is valid and handled on the javascript side, the
/// secret inbox should be cleared by calling
/// `delete_secrets_from_inbox`.
#[wasm_bindgen(js_name = "registerReceiveSecretCallback")]
pub async fn register_receive_secret_callback(&self, callback: Function) {
let stream = self.inner.store().secrets_stream();
// fire up a promise chain which will call `callback` on each result from the
// stream
spawn_local(async move {
// Pin the stream to ensure it can be safely moved across threads
pin_mut!(stream);
while let Some(secret) = stream.next().await {
send_secret_gossip_to_callback(&callback, &secret).await;
}
});
}

/// Get all the secrets with the given secret_name we have currently
/// stored.
/// The only secret this will currently be returned is the
/// `m.megolm_backup.v1` secret.
Comment on lines +1139 to +1140
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The only secret this will currently be returned is the
/// `m.megolm_backup.v1` secret.
/// The only secret this will currently return is the
/// `m.megolm_backup.v1` secret.

///
/// Usually you would just register a callback with
/// [`register_receive_secret_callback`], but if the client is shut down
/// before handling them, this method can be used to retrieve them.
BillCarsonFr marked this conversation as resolved.
Show resolved Hide resolved
/// This method should therefore be called at client startup to retrieve any
/// secrets received during the previous session.
///
/// The secrets are guaranteed to have been received over a 1-to-1 encrypted
/// to_device message from one of the user's own verified devices.
///
/// Returns a `Promise` for a `Set` of `String` corresponding to the secret
/// values.
///
/// If the secret is valid and handled, the secret inbox should be cleared
/// by calling `delete_secrets_from_inbox`.
#[wasm_bindgen(js_name = "getSecretsFromInbox")]
pub async fn get_secrets_from_inbox(&self, secret_name: String) -> Promise {
let set = Set::new(&JsValue::UNDEFINED);
let me = self.inner.clone();

future_to_promise(async move {
let name = SecretName::from(secret_name);
for gossip in me.store().get_secrets_from_inbox(&name).await? {
set.add(&JsValue::from_str(&gossip.event.content.secret));
}
Ok(set)
})
}

/// Delete all secrets with the given secret name from the inbox.
///
/// Should be called after handling the secrets with
/// `get_secrets_from_inbox`.
///
/// # Arguments
///
/// * `secret_name` - The name of the secret to delete.
#[wasm_bindgen(js_name = "deleteSecretsFromInbox")]
pub async fn delete_secrets_from_inbox(&self, secret_name: String) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
let name = SecretName::from(secret_name);
me.store().delete_secrets_from_inbox(&name).await?;
Ok(JsValue::UNDEFINED)
})
}

/// Shut down the `OlmMachine`.
///
/// The `OlmMachine` cannot be used after this method has been called.
Expand Down Expand Up @@ -1137,6 +1228,23 @@ async fn send_user_identities_to_callback(
}
}

// helper for register_secret_receive_callback: passes the secret name and value
// into the javascript function
async fn send_secret_gossip_to_callback(callback: &Function, secret: &GossippedSecret) {
richvdh marked this conversation as resolved.
Show resolved Hide resolved
match promise_result_to_future(callback.call2(
&JsValue::NULL,
&(secret.secret_name.as_str().into()),
&(&secret.event.content.secret.to_owned().into()),
))
.await
{
Ok(_) => (),
Err(e) => {
warn!("Error calling receive secret callback: {:?}", e);
}
}
}

/// Given a result from a javascript function which returns a Promise (or throws
/// an exception before returning one), convert the result to a rust Future
/// which completes with the result of the promise
Expand Down