diff --git a/crates/matrix-sdk-crypto-js/src/identifiers.rs b/crates/matrix-sdk-crypto-js/src/identifiers.rs index 417acd680..8742cbc64 100644 --- a/crates/matrix-sdk-crypto-js/src/identifiers.rs +++ b/crates/matrix-sdk-crypto-js/src/identifiers.rs @@ -11,6 +11,12 @@ pub struct UserId { pub(crate) inner: ruma::OwnedUserId, } +impl UserId { + pub(crate) fn new_with(inner: ruma::OwnedUserId) -> Self { + Self { inner } + } +} + #[wasm_bindgen] impl UserId { /// Parse/validate and create a new `UserId`. @@ -57,6 +63,12 @@ pub struct DeviceId { pub(crate) inner: ruma::OwnedDeviceId, } +impl DeviceId { + pub(crate) fn new_with(inner: ruma::OwnedDeviceId) -> Self { + Self { inner } + } +} + #[wasm_bindgen] impl DeviceId { /// Create a new `DeviceId`. @@ -81,6 +93,12 @@ pub struct RoomId { pub(crate) inner: ruma::OwnedRoomId, } +impl RoomId { + pub(crate) fn new_with(inner: ruma::OwnedRoomId) -> Self { + Self { inner } + } +} + #[wasm_bindgen] impl RoomId { /// Parse/validate and create a new `RoomId`. diff --git a/crates/matrix-sdk-crypto-js/src/lib.rs b/crates/matrix-sdk-crypto-js/src/lib.rs index 0c3bef81e..f93cde86f 100644 --- a/crates/matrix-sdk-crypto-js/src/lib.rs +++ b/crates/matrix-sdk-crypto-js/src/lib.rs @@ -23,6 +23,7 @@ pub mod events; mod future; pub mod identifiers; pub mod machine; +pub mod verifications; pub mod requests; pub mod responses; pub mod sync_events; diff --git a/crates/matrix-sdk-crypto-js/src/machine.rs b/crates/matrix-sdk-crypto-js/src/machine.rs index 18efb5a05..8ffade45c 100644 --- a/crates/matrix-sdk-crypto-js/src/machine.rs +++ b/crates/matrix-sdk-crypto-js/src/machine.rs @@ -14,7 +14,7 @@ use crate::{ identifiers, requests, requests::OutgoingRequest, responses::{self, response_from_string}, - sync_events, + sync_events, verifications, }; #[wasm_bindgen] @@ -344,6 +344,31 @@ impl OlmMachine { } })) } + + /// Get a verification object for the given user ID with the given flow ID. + /// + /// Returns a list of `JsValue` to represent either (depending on + /// how the Wasm module has been compiled): + /// * `Sas` (enabled), + /// * `Qr` + #[cfg_attr(feature = "qrcode", doc = "(enabled).")] + #[cfg_attr(not(feature = "qrcode"), doc = "(disabled).")] + /// + /// If a verification mode is missing, please try to compile the + /// Wasm module with different features. + #[wasm_bindgen(js_name = "getVerification")] + pub fn get_verification( + &self, + user_id: &identifiers::UserId, + flow_id: &str, + ) -> Result { + self.inner + .get_verification(user_id.inner.as_ref(), flow_id) + .map(verifications::Verification) + .map(JsValue::try_from) + .transpose() + .map(|r| r.unwrap_or_else(|| JsValue::UNDEFINED)) + } } #[wasm_bindgen] diff --git a/crates/matrix-sdk-crypto-js/src/sync_events.rs b/crates/matrix-sdk-crypto-js/src/sync_events.rs index 6dfcec056..a94528197 100644 --- a/crates/matrix-sdk-crypto-js/src/sync_events.rs +++ b/crates/matrix-sdk-crypto-js/src/sync_events.rs @@ -49,7 +49,7 @@ impl DeviceLists { self.inner .changed .iter() - .map(|user| identifiers::UserId { inner: user.clone() }) + .map(|user| identifiers::UserId::new_with(user.clone())) .map(JsValue::from) .collect() } @@ -60,7 +60,7 @@ impl DeviceLists { self.inner .left .iter() - .map(|user| identifiers::UserId { inner: user.clone() }) + .map(|user| identifiers::UserId::new_with(user.clone())) .map(JsValue::from) .collect() } diff --git a/crates/matrix-sdk-crypto-js/src/verifications.rs b/crates/matrix-sdk-crypto-js/src/verifications.rs new file mode 100644 index 000000000..1a22ccb40 --- /dev/null +++ b/crates/matrix-sdk-crypto-js/src/verifications.rs @@ -0,0 +1,354 @@ +use js_sys::{Array, JsString}; +use ruma::events::key::verification::cancel::CancelCode as RumaCancelCode; +use wasm_bindgen::prelude::*; + +use crate::identifiers::{DeviceId, RoomId, UserId}; + +#[wasm_bindgen] +#[derive(Debug)] +pub struct Sas { + inner: matrix_sdk_crypto::Sas, +} + +#[wasm_bindgen] +impl Sas { + /// Get our own user ID. + #[wasm_bindgen(js_name = "userId")] + pub fn user_id(&self) -> UserId { + UserId { inner: self.inner.user_id().to_owned() } + } + + /// Get our own device ID. + #[wasm_bindgen(js_name = "deviceId")] + pub fn device_id(&self) -> DeviceId { + DeviceId { inner: self.inner.device_id().to_owned() } + } + + /// Get the user id of the other side. + #[wasm_bindgen(js_name = "otherUserId")] + pub fn other_user_id(&self) -> UserId { + UserId { inner: self.inner.other_user_id().to_owned() } + } + + /// Get the device ID of the other side. + #[wasm_bindgen(js_name = "otherDeviceId")] + pub fn other_device_id(&self) -> DeviceId { + DeviceId { inner: self.inner.other_device_id().to_owned() } + } + + #[wasm_bindgen(js_name = "otherDevice")] + pub fn other_device(&self) { + todo!() + } + + #[wasm_bindgen(js_name = "flowId")] + pub fn flow_id(&self) { + todo!() + } + + /// Get the room ID if the verification is happening inside a + /// room. + #[wasm_bindgen(js_name = "roomId")] + pub fn room_id(&self) -> Option { + self.inner.room_id().map(ToOwned::to_owned).map(RoomId::new_with) + } + + /// Does this verification flow support displaying emoji for the + /// short authentication string. + #[wasm_bindgen(js_name = "supportsEmoji")] + pub fn supports_emoji(&self) -> bool { + self.inner.supports_emoji() + } + + /// Did this verification flow start from a verification request. + #[wasm_bindgen(js_name = "startedFromRequest")] + pub fn started_from_request(&self) -> bool { + self.inner.started_from_request() + } + + /// Is this a verification that is veryfying one of our own + /// devices. + #[wasm_bindgen(js_name = "isSelfVerification")] + pub fn is_self_verification(&self) -> bool { + self.inner.is_self_verification() + } + + /// Have we confirmed that the short auth string matches. + #[wasm_bindgen(js_name = "haveWeConfirmed")] + pub fn have_we_confirmed(&self) -> bool { + self.inner.have_we_confirmed() + } + + /// Has the verification been accepted by both parties. + #[wasm_bindgen(js_name = "hasBeenAccepted")] + pub fn has_been_accepted(&self) -> bool { + self.inner.has_been_accepted() + } + + /// Get info about the cancellation if the verification flow has + /// been cancelled. + #[wasm_bindgen(js_name = "cancelInfo")] + pub fn cancel_info(&self) -> Option { + self.inner.cancel_info().map(CancelInfo::new_with) + } + + /// Did we initiate the verification flow. + #[wasm_bindgen(js_name = "weStarted")] + pub fn we_started(&self) -> bool { + self.inner.we_started() + } + + pub fn accept(&self) { + todo!() + } + + #[wasm_bindgen(js_name = "acceptWithSetings")] + pub fn accept_with_settings(&self) { + todo!() + } + + pub fn confirm(&self) { + todo!() + } + + pub fn cancel(&self) { + todo!() + } + + #[wasm_bindgen(js_name = "cancelWithCode")] + pub fn cancel_with_code(&self) { + todo!() + } + + /// Has the SAS verification flow timed out. + #[wasm_bindgen(js_name = "timedOut")] + pub fn timed_out(&self) -> bool { + self.inner.timed_out() + } + + /// Are we in a state where we can show the short auth string. + #[wasm_bindgen(js_name = "canBePresented")] + pub fn can_be_presented(&self) -> bool { + self.inner.can_be_presented() + } + + /// Is the SAS flow done. + #[wasm_bindgen(js_name = "isDone")] + pub fn is_done(&self) -> bool { + self.inner.is_done() + } + + /// Is the SAS flow canceled. + #[wasm_bindgen(js_name = "isCancelled")] + pub fn is_cancelled(&self) -> bool { + self.inner.is_cancelled() + } + + /// Get the emoji version of the short auth string. + /// + /// Returns `undefined` if we can't yet present the short auth string, + /// otherwise seven tuples containing the emoji and description. + pub fn emoji(&self) -> Option { + Some( + self.inner + .emoji()? + .iter() + .map(|emoji| Emoji::new_with(emoji.clone())) + .map(JsValue::from) + .collect(), + ) + } + + /// Get the index of the emoji representing the short auth string + /// + /// Returns `undefined` if we can’t yet present the short auth + /// string, otherwise seven u8 numbers in the range from 0 to 63 + /// inclusive which can be converted to an emoji using [the + /// relevant specification + /// entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji). + #[wasm_bindgen(js_name = "emoji_index")] + pub fn emoji_index(&self) -> Option { + Some(self.inner.emoji_index()?.iter().map(|emoji| *emoji).map(JsValue::from).collect()) + } + + /// Get the decimal version of the short auth string. + /// + /// Returns None if we can’t yet present the short auth string, + /// otherwise a tuple containing three 4-digit integers that + /// represent the short auth string. + pub fn decimals(&self) -> Option { + let decimals = self.inner.decimals()?; + + let out = Array::new_with_length(3); + out.set(0, JsValue::from(decimals.0)); + out.set(1, JsValue::from(decimals.1)); + out.set(2, JsValue::from(decimals.2)); + + Some(out) + } +} + +#[cfg(feature = "qrcode")] +#[wasm_bindgen] +#[derive(Debug)] +pub struct Qr { + inner: matrix_sdk_crypto::QrVerification, +} + +#[cfg(feature = "qrcode")] +#[wasm_bindgen] +impl Qr { + #[wasm_bindgen(js_name = "hasBeenScanned")] + pub fn has_been_scanned(&self) -> bool { + self.inner.has_been_scanned() + } +} + +pub(crate) struct Verification(pub(crate) matrix_sdk_crypto::Verification); + +impl TryFrom for JsValue { + type Error = JsError; + + fn try_from(verification: Verification) -> Result { + use matrix_sdk_crypto::Verification::*; + + Ok(match verification.0 { + SasV1(sas) => JsValue::from(Sas { inner: sas }), + + #[cfg(feature = "qrcode")] + QrV1(qr) => JsValue::from(Qr { inner: qr }), + + _ => { + return Err(JsError::new( + "Unknown verification type, expect `m.sas.v1` only for now", + )) + } + }) + } +} + +#[wasm_bindgen] +pub struct CancelInfo { + inner: matrix_sdk_crypto::CancelInfo, +} + +impl CancelInfo { + pub(crate) fn new_with(inner: matrix_sdk_crypto::CancelInfo) -> Self { + Self { inner } + } +} + +#[wasm_bindgen] +impl CancelInfo { + pub fn reason(&self) -> JsString { + self.inner.reason().into() + } + + #[wasm_bindgen(js_name = "cancelCode")] + pub fn cancel_code(&self) -> CancelCode { + self.inner.cancel_code().into() + } + + #[wasm_bindgen(js_name = "cancelledbyUs")] + pub fn cancelled_by_us(&self) -> bool { + self.inner.cancelled_by_us() + } +} + +#[wasm_bindgen] +pub enum CancelCode { + /// Unknown cancel code. + Other, + + /// The user cancelled the verification. + User, + + /// The verification process timed out. + /// + /// Verification processes can define their own timeout + /// parameters. + Timeout, + + /// The device does not know about the given transaction ID. + UnknownTransaction, + + /// The device does not know how to handle the requested method. + /// + /// Should be sent for `m.key.verification.start` messages and + /// messages defined by individual verification processes. + UnknownMethod, + + /// The device received an unexpected message. + /// + /// Typically raised when one of the parties is handling the + /// verification out of order. + UnexpectedMessage, + + /// The key was not verified. + KeyMismatch, + + /// The expected user did not match the user verified. + UserMismatch, + + /// The message received was invalid. + InvalidMessage, + + /// An `m.key.verification.request` was accepted by a different + /// device. + /// + /// The device receiving this error can ignore the verification + /// request. + Accepted, + + /// The device receiving this error can ignore the verification + /// request. + MismatchedCommitment, + + /// The SAS did not match. + MismatchedSas, +} + +impl From<&RumaCancelCode> for CancelCode { + fn from(code: &RumaCancelCode) -> Self { + use RumaCancelCode::*; + + match code { + User => Self::User, + Timeout => Self::Timeout, + UnknownTransaction => Self::UnknownTransaction, + UnknownMethod => Self::UnknownMethod, + UnexpectedMessage => Self::UnexpectedMessage, + KeyMismatch => Self::KeyMismatch, + UserMismatch => Self::UserMismatch, + InvalidMessage => Self::InvalidMessage, + Accepted => Self::Accepted, + MismatchedCommitment => Self::MismatchedCommitment, + MismatchedSas => Self::MismatchedSas, + _ => Self::Other, + } + } +} + +#[wasm_bindgen] +pub struct Emoji { + inner: matrix_sdk_crypto::Emoji, +} + +impl Emoji { + pub(crate) fn new_with(inner: matrix_sdk_crypto::Emoji) -> Self { + Self { inner } + } +} + +#[wasm_bindgen] +impl Emoji { + #[wasm_bindgen(getter)] + pub fn symbol(&self) -> JsString { + self.inner.symbol.into() + } + + #[wasm_bindgen(getter)] + pub fn description(&self) -> JsString { + self.inner.description.into() + } +}