mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-14 11:05:32 -04:00
feat(crypto-js): Implement key verification
feat(crypto-js): Implement key verification
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2303,6 +2303,7 @@ dependencies = [
|
||||
"matrix-sdk-common",
|
||||
"matrix-sdk-crypto",
|
||||
"matrix-sdk-indexeddb",
|
||||
"matrix-sdk-qrcode",
|
||||
"ruma",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
|
||||
@@ -21,15 +21,17 @@ wasm-opt = ['-Oz']
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[features]
|
||||
default = ["tracing"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode"]
|
||||
default = ["tracing", "qrcode"]
|
||||
qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
|
||||
tracing = []
|
||||
|
||||
[dependencies]
|
||||
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
|
||||
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
|
||||
matrix-sdk-indexeddb = { version = "0.1.0", path = "../../crates/matrix-sdk-indexeddb" }
|
||||
matrix-sdk-qrcode = { version = "0.3.0", path = "../../crates/matrix-sdk-qrcode", optional = true }
|
||||
ruma = { version = "0.7.0", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
|
||||
vodozemac = { version = "0.3.0", features = ["js"] }
|
||||
wasm-bindgen = "0.2.80"
|
||||
wasm-bindgen-futures = "0.4.30"
|
||||
js-sys = "0.3.49"
|
||||
@@ -39,8 +41,4 @@ http = "0.2.6"
|
||||
anyhow = "1.0.58"
|
||||
tracing = { version = "0.1.35", default-features = false, features = ["attributes"] }
|
||||
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
|
||||
zeroize = "1.3.0"
|
||||
|
||||
[dependencies.vodozemac]
|
||||
version = "0.3.0"
|
||||
features = ["js"]
|
||||
zeroize = "1.3.0"
|
||||
@@ -26,12 +26,12 @@
|
||||
"pkg/matrix_sdk_crypto.d.ts"
|
||||
],
|
||||
"devDependencies": {
|
||||
"wasm-pack": "^0.10.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"fake-indexeddb": "^4.0",
|
||||
"jest": "^28.1.0",
|
||||
"typedoc": "^0.22.17",
|
||||
"cross-env": "^7.0.3",
|
||||
"yargs-parser": "~21.0.1",
|
||||
"fake-indexeddb": "^4.0"
|
||||
"wasm-pack": "^0.10.2",
|
||||
"yargs-parser": "~21.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
|
||||
263
bindings/matrix-sdk-crypto-js/src/device.rs
Normal file
263
bindings/matrix-sdk-crypto-js/src/device.rs
Normal file
@@ -0,0 +1,263 @@
|
||||
//! Types for a `Device`.
|
||||
|
||||
use js_sys::{Array, Map, Promise};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
future::future_to_promise,
|
||||
identifiers::{self, DeviceId, UserId},
|
||||
types, verification, vodozemac,
|
||||
};
|
||||
|
||||
/// A device represents a E2EE capable client of an user.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct Device {
|
||||
pub(crate) inner: matrix_sdk_crypto::Device,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::Device> for Device {
|
||||
fn from(inner: matrix_sdk_crypto::Device) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Device {
|
||||
/// Request an interactive verification with this device.
|
||||
#[wasm_bindgen(js_name = "requestVerification")]
|
||||
pub fn request_verification(&self, methods: Option<Array>) -> Result<Promise, JsError> {
|
||||
let methods = methods
|
||||
.map(|array| {
|
||||
array
|
||||
.iter()
|
||||
.map(|method| {
|
||||
verification::VerificationMethod::try_from(method).map(Into::into)
|
||||
})
|
||||
.collect::<Result<_, _>>()
|
||||
})
|
||||
.transpose()?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
let tuple = Array::new();
|
||||
let (verification_request, outgoing_verification_request) = match methods {
|
||||
Some(methods) => me.request_verification_with_methods(methods).await,
|
||||
None => me.request_verification().await,
|
||||
};
|
||||
|
||||
tuple.set(0, verification::VerificationRequest::from(verification_request).into());
|
||||
tuple.set(
|
||||
1,
|
||||
verification::OutgoingVerificationRequest::from(outgoing_verification_request)
|
||||
.try_into()?,
|
||||
);
|
||||
|
||||
Ok(tuple)
|
||||
}))
|
||||
}
|
||||
|
||||
/// Is this device considered to be verified.
|
||||
///
|
||||
/// This method returns true if either the `is_locally_trusted`
|
||||
/// method returns `true` or if the `is_cross_signing_trusted`
|
||||
/// method returns `true`.
|
||||
#[wasm_bindgen(js_name = "isVerified")]
|
||||
pub fn is_verified(&self) -> bool {
|
||||
self.inner.is_verified()
|
||||
}
|
||||
|
||||
/// Is this device considered to be verified using cross signing.
|
||||
#[wasm_bindgen(js_name = "isCrossSigningTrusted")]
|
||||
pub fn is_cross_signing_trusted(&self) -> bool {
|
||||
self.inner.is_cross_signing_trusted()
|
||||
}
|
||||
|
||||
/// Set the local trust state of the device to the given state.
|
||||
///
|
||||
/// This won’t affect any cross signing trust state, this only
|
||||
/// sets a flag marking to have the given trust state.
|
||||
///
|
||||
/// `trust_state` represents the new trust state that should be
|
||||
/// set for the device.
|
||||
#[wasm_bindgen(js_name = "setLocalTrust")]
|
||||
pub fn set_local_trust(&self, local_state: LocalTrust) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
me.set_local_trust(local_state.into()).await?;
|
||||
|
||||
Ok(JsValue::NULL)
|
||||
})
|
||||
}
|
||||
|
||||
/// The user ID of the device owner.
|
||||
#[wasm_bindgen(getter, js_name = "userId")]
|
||||
pub fn user_id(&self) -> UserId {
|
||||
self.inner.user_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// The unique ID of the device.
|
||||
#[wasm_bindgen(getter, js_name = "deviceId")]
|
||||
pub fn device_id(&self) -> DeviceId {
|
||||
self.inner.device_id().to_owned().into()
|
||||
}
|
||||
|
||||
/// Get the human readable name of the device.
|
||||
#[wasm_bindgen(getter, js_name = "displayName")]
|
||||
pub fn display_name(&self) -> Option<String> {
|
||||
self.inner.display_name().map(ToOwned::to_owned)
|
||||
}
|
||||
|
||||
/// Get the key of the given key algorithm belonging to this device.
|
||||
#[wasm_bindgen(js_name = "getKey")]
|
||||
pub fn get_key(
|
||||
&self,
|
||||
algorithm: identifiers::DeviceKeyAlgorithmName,
|
||||
) -> Result<Option<vodozemac::DeviceKey>, JsError> {
|
||||
Ok(self.inner.get_key(algorithm.try_into()?).cloned().map(Into::into))
|
||||
}
|
||||
|
||||
/// Get the Curve25519 key of the given device.
|
||||
#[wasm_bindgen(getter, js_name = "curve25519Key")]
|
||||
pub fn curve25519_key(&self) -> Option<vodozemac::Curve25519PublicKey> {
|
||||
self.inner.curve25519_key().map(Into::into)
|
||||
}
|
||||
|
||||
/// Get the Ed25519 key of the given device.
|
||||
#[wasm_bindgen(getter, js_name = "ed25519Key")]
|
||||
pub fn ed25519_key(&self) -> Option<vodozemac::Ed25519PublicKey> {
|
||||
self.inner.ed25519_key().map(Into::into)
|
||||
}
|
||||
|
||||
/// Get a map containing all the device keys.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn keys(&self) -> Map {
|
||||
let map = Map::new();
|
||||
|
||||
for (device_key_id, device_key) in self.inner.keys() {
|
||||
map.set(
|
||||
&identifiers::DeviceKeyId::from(device_key_id.clone()).into(),
|
||||
&vodozemac::DeviceKey::from(device_key.clone()).into(),
|
||||
);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// Get a map containing all the device signatures.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn signatures(&self) -> types::Signatures {
|
||||
self.inner.signatures().clone().into()
|
||||
}
|
||||
|
||||
/// Get the trust state of the device.
|
||||
#[wasm_bindgen(getter, js_name = "localTrustState")]
|
||||
pub fn local_trust_state(&self) -> LocalTrust {
|
||||
self.inner.local_trust_state().into()
|
||||
}
|
||||
|
||||
/// Is the device locally marked as trusted?
|
||||
#[wasm_bindgen(js_name = "isLocallyTrusted")]
|
||||
pub fn is_locally_trusted(&self) -> bool {
|
||||
self.inner.is_locally_trusted()
|
||||
}
|
||||
|
||||
/// Is the device locally marked as blacklisted?
|
||||
///
|
||||
/// Blacklisted devices won’t receive any group sessions.
|
||||
#[wasm_bindgen(js_name = "isBlacklisted")]
|
||||
pub fn is_blacklisted(&self) -> bool {
|
||||
self.inner.is_blacklisted()
|
||||
}
|
||||
|
||||
/// Is the device deleted?
|
||||
#[wasm_bindgen(js_name = "isDeleted")]
|
||||
pub fn is_deleted(&self) -> bool {
|
||||
self.inner.is_deleted()
|
||||
}
|
||||
}
|
||||
|
||||
/// The local trust state of a device.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum LocalTrust {
|
||||
/// The device has been verified and is trusted.
|
||||
Verified,
|
||||
|
||||
/// The device been blacklisted from communicating.
|
||||
BlackListed,
|
||||
|
||||
/// The trust state of the device is being ignored.
|
||||
Ignored,
|
||||
|
||||
/// The trust state is unset.
|
||||
Unset,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::LocalTrust> for LocalTrust {
|
||||
fn from(value: matrix_sdk_crypto::LocalTrust) -> Self {
|
||||
use matrix_sdk_crypto::LocalTrust::*;
|
||||
|
||||
match value {
|
||||
Verified => Self::Verified,
|
||||
BlackListed => Self::BlackListed,
|
||||
Ignored => Self::Ignored,
|
||||
Unset => Self::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalTrust> for matrix_sdk_crypto::LocalTrust {
|
||||
fn from(value: LocalTrust) -> Self {
|
||||
use LocalTrust::*;
|
||||
|
||||
match value {
|
||||
Verified => Self::Verified,
|
||||
BlackListed => Self::BlackListed,
|
||||
Ignored => Self::Ignored,
|
||||
Unset => Self::Unset,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A read only view over all devices belonging to a user.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct UserDevices {
|
||||
pub(crate) inner: matrix_sdk_crypto::UserDevices,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::UserDevices> for UserDevices {
|
||||
fn from(inner: matrix_sdk_crypto::UserDevices) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl UserDevices {
|
||||
/// Get the specific device with the given device ID.
|
||||
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
|
||||
self.inner.get(&device_id.inner).map(Into::into)
|
||||
}
|
||||
|
||||
/// Returns true if there is at least one devices of this user
|
||||
/// that is considered to be verified, false otherwise.
|
||||
///
|
||||
/// This won't consider your own device as verified, as your own
|
||||
/// device is always implicitly verified.
|
||||
#[wasm_bindgen(js_name = "isAnyVerified")]
|
||||
pub fn is_any_verified(&self) -> bool {
|
||||
self.inner.is_any_verified()
|
||||
}
|
||||
|
||||
/// Array over all the device IDs of the user devices.
|
||||
pub fn keys(&self) -> Array {
|
||||
self.inner.keys().map(ToOwned::to_owned).map(DeviceId::from).map(JsValue::from).collect()
|
||||
}
|
||||
|
||||
/// Iterator over all the devices of the user devices.
|
||||
pub fn devices(&self) -> Array {
|
||||
self.inner.devices().map(Device::from).map(JsValue::from).collect()
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ impl DeviceKeyId {
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceKeyAlgorithm {
|
||||
inner: ruma::DeviceKeyAlgorithm,
|
||||
pub(crate) inner: ruma::DeviceKeyAlgorithm,
|
||||
}
|
||||
|
||||
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithm {
|
||||
@@ -180,6 +180,25 @@ pub enum DeviceKeyAlgorithmName {
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl TryFrom<DeviceKeyAlgorithmName> for ruma::DeviceKeyAlgorithm {
|
||||
type Error = JsError;
|
||||
|
||||
fn try_from(value: DeviceKeyAlgorithmName) -> Result<Self, Self::Error> {
|
||||
use DeviceKeyAlgorithmName::*;
|
||||
|
||||
Ok(match value {
|
||||
Ed25519 => Self::Ed25519,
|
||||
Curve25519 => Self::Curve25519,
|
||||
SignedCurve25519 => Self::SignedCurve25519,
|
||||
Unknown => {
|
||||
return Err(JsError::new(
|
||||
"The `DeviceKeyAlgorithmName.Unknown` variant cannot be converted",
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithmName {
|
||||
fn from(value: ruma::DeviceKeyAlgorithm) -> Self {
|
||||
use ruma::DeviceKeyAlgorithm::*;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
|
||||
|
||||
pub mod attachment;
|
||||
pub mod device;
|
||||
pub mod encryption;
|
||||
pub mod events;
|
||||
mod future;
|
||||
@@ -26,9 +27,11 @@ pub mod machine;
|
||||
pub mod olm;
|
||||
pub mod requests;
|
||||
pub mod responses;
|
||||
pub mod store;
|
||||
pub mod sync_events;
|
||||
mod tracing;
|
||||
pub mod types;
|
||||
pub mod verification;
|
||||
pub mod vodozemac;
|
||||
|
||||
use js_sys::{Object, Reflect};
|
||||
|
||||
@@ -8,12 +8,12 @@ use serde_json::Value as JsonValue;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
downcast, encryption,
|
||||
device, downcast, encryption,
|
||||
future::future_to_promise,
|
||||
identifiers, olm, requests,
|
||||
requests::OutgoingRequest,
|
||||
responses::{self, response_from_string},
|
||||
sync_events, types, vodozemac,
|
||||
store, sync_events, types, verification, vodozemac,
|
||||
};
|
||||
|
||||
/// State machine implementation of the Olm/Megolm encryption protocol
|
||||
@@ -353,6 +353,66 @@ impl OlmMachine {
|
||||
})
|
||||
}
|
||||
|
||||
/// Export all the private cross signing keys we have.
|
||||
///
|
||||
/// The export will contain the seed for the ed25519 keys as a
|
||||
/// unpadded base64 encoded string.
|
||||
///
|
||||
/// This method returns None if we don’t have any private cross
|
||||
/// signing keys.
|
||||
#[wasm_bindgen(js_name = "exportCrossSigningKeys")]
|
||||
pub fn export_cross_signing_keys(&self) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me.export_cross_signing_keys().await.map(store::CrossSigningKeyExport::from))
|
||||
})
|
||||
}
|
||||
|
||||
/// Import our private cross signing keys.
|
||||
///
|
||||
/// The export needs to contain the seed for the ed25519 keys as
|
||||
/// an unpadded base64 encoded string.
|
||||
#[wasm_bindgen(js_name = "importCrossSigningKeys")]
|
||||
pub fn import_cross_signing_keys(&self, export: store::CrossSigningKeyExport) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
let export = export.inner;
|
||||
|
||||
future_to_promise(async move {
|
||||
Ok(me.import_cross_signing_keys(export).await.map(olm::CrossSigningStatus::from)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new cross signing identity and get the upload request
|
||||
/// to push the new public keys to the server.
|
||||
///
|
||||
/// Warning: This will delete any existing cross signing keys that
|
||||
/// might exist on the server and thus will reset the trust
|
||||
/// between all the devices.
|
||||
///
|
||||
/// Uploading these keys will require user interactive auth.
|
||||
#[wasm_bindgen(js_name = "bootstrapCrossSigning")]
|
||||
pub fn bootstrap_cross_signing(&self, reset: bool) -> Promise {
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise(async move {
|
||||
let (upload_signing_keys_request, upload_signatures_request) =
|
||||
me.bootstrap_cross_signing(reset).await?;
|
||||
|
||||
let tuple = Array::new();
|
||||
tuple.set(
|
||||
0,
|
||||
requests::SigningKeysUploadRequest::try_from(&upload_signing_keys_request)?.into(),
|
||||
);
|
||||
tuple.set(
|
||||
1,
|
||||
requests::SignatureUploadRequest::try_from(&upload_signatures_request)?.into(),
|
||||
);
|
||||
|
||||
Ok(tuple)
|
||||
})
|
||||
}
|
||||
|
||||
/// Sign the given message using our device key and if available
|
||||
/// cross-signing master key.
|
||||
pub fn sign(&self, message: String) -> Promise {
|
||||
@@ -453,4 +513,104 @@ impl OlmMachine {
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Get a map holding all the devices of a user.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that the
|
||||
/// devices belong to.
|
||||
#[wasm_bindgen(js_name = "getUserDevices")]
|
||||
pub fn get_user_devices(&self, user_id: &identifiers::UserId) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, device::UserDevices>(async move {
|
||||
Ok(me.get_user_devices(&user_id, None).await.map(Into::into)?)
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a specific device of a user if one is found and the crypto store
|
||||
/// didn't throw an error.
|
||||
///
|
||||
/// `user_id` represents the unique ID of the user that the
|
||||
/// identity belongs to. `device_id` represents the unique ID of
|
||||
/// the device.
|
||||
#[wasm_bindgen(js_name = "getDevice")]
|
||||
pub fn get_device(
|
||||
&self,
|
||||
user_id: &identifiers::UserId,
|
||||
device_id: &identifiers::DeviceId,
|
||||
) -> Promise {
|
||||
let user_id = user_id.inner.clone();
|
||||
let device_id = device_id.inner.clone();
|
||||
|
||||
let me = self.inner.clone();
|
||||
|
||||
future_to_promise::<_, Option<device::Device>>(async move {
|
||||
Ok(me.get_device(&user_id, &device_id, None).await?.map(Into::into))
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a verification object for the given user ID with the given
|
||||
/// flow ID (a to-device request ID if the verification has been
|
||||
/// requested by a to-device request, or a room event ID if the
|
||||
/// verification has been requested by a room event).
|
||||
///
|
||||
/// It returns a “`Verification` object”, which is either a `Sas`
|
||||
/// or `Qr` object.
|
||||
#[wasm_bindgen(js_name = "getVerification")]
|
||||
pub fn get_verification(
|
||||
&self,
|
||||
user_id: &identifiers::UserId,
|
||||
flow_id: &str,
|
||||
) -> Result<JsValue, JsError> {
|
||||
self.inner
|
||||
.get_verification(&user_id.inner, flow_id)
|
||||
.map(verification::Verification)
|
||||
.map(JsValue::try_from)
|
||||
.transpose()
|
||||
.map(JsValue::from)
|
||||
}
|
||||
|
||||
/// Get a verification request object with the given flow ID.
|
||||
#[wasm_bindgen(js_name = "getVerificationRequest")]
|
||||
pub fn get_verification_request(
|
||||
&self,
|
||||
user_id: &identifiers::UserId,
|
||||
flow_id: &str,
|
||||
) -> Option<verification::VerificationRequest> {
|
||||
self.inner.get_verification_request(&user_id.inner, flow_id).map(Into::into)
|
||||
}
|
||||
|
||||
/// Get all the verification requests of a given user.
|
||||
#[wasm_bindgen(js_name = "getVerificationRequests")]
|
||||
pub fn get_verification_requests(&self, user_id: &identifiers::UserId) -> Array {
|
||||
self.inner
|
||||
.get_verification_requests(&user_id.inner)
|
||||
.into_iter()
|
||||
.map(verification::VerificationRequest::from)
|
||||
.map(JsValue::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Receive an unencrypted verification event.
|
||||
///
|
||||
/// This method can be used to pass verification events that are
|
||||
/// happening in unencrypted rooms to the `OlmMachine`.
|
||||
///
|
||||
/// Note: This does not need to be called for encrypted events
|
||||
/// since those will get passed to the `OlmMachine` during
|
||||
/// decryption.
|
||||
#[wasm_bindgen(js_name = "receiveUnencryptedVerificationEvent")]
|
||||
pub fn receive_unencrypted_verification_event(&self, event: &str) -> Result<Promise, JsError> {
|
||||
let event: ruma::events::AnyMessageLikeEvent = serde_json::from_str(event)?;
|
||||
let me = self.inner.clone();
|
||||
|
||||
Ok(future_to_promise(async move {
|
||||
Ok(me
|
||||
.receive_unencrypted_verification_event(&event)
|
||||
.await
|
||||
.map(|_| JsValue::UNDEFINED)?)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
use js_sys::JsString;
|
||||
use matrix_sdk_crypto::{
|
||||
requests::{
|
||||
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
|
||||
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
|
||||
KeysBackupRequest as OriginalKeysBackupRequest,
|
||||
KeysQueryRequest as OriginalKeysQueryRequest,
|
||||
RoomMessageRequest as OriginalRoomMessageRequest,
|
||||
ToDeviceRequest as OriginalToDeviceRequest,
|
||||
UploadSigningKeysRequest as OriginalUploadSigningKeysRequest,
|
||||
},
|
||||
OutgoingRequests,
|
||||
};
|
||||
use ruma::api::client::keys::{
|
||||
claim_keys::v3::Request as RumaKeysClaimRequest,
|
||||
upload_keys::v3::Request as RumaKeysUploadRequest,
|
||||
upload_signatures::v3::Request as RumaSignatureUploadRequest,
|
||||
claim_keys::v3::Request as OriginalKeysClaimRequest,
|
||||
upload_keys::v3::Request as OriginalKeysUploadRequest,
|
||||
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
|
||||
};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/** Outgoing Requests * */
|
||||
|
||||
/// Data for a request to the `/keys/upload` API endpoint
|
||||
/// ([specification]).
|
||||
///
|
||||
@@ -26,7 +31,7 @@ use wasm_bindgen::prelude::*;
|
||||
pub struct KeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -42,7 +47,7 @@ impl KeysUploadRequest {
|
||||
/// Create a new `KeysUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -63,7 +68,7 @@ impl KeysUploadRequest {
|
||||
pub struct KeysQueryRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -79,7 +84,7 @@ impl KeysQueryRequest {
|
||||
/// Create a new `KeysQueryRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -101,7 +106,7 @@ impl KeysQueryRequest {
|
||||
pub struct KeysClaimRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -117,7 +122,7 @@ impl KeysClaimRequest {
|
||||
/// Create a new `KeysClaimRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -138,7 +143,7 @@ impl KeysClaimRequest {
|
||||
pub struct ToDeviceRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -154,7 +159,7 @@ impl ToDeviceRequest {
|
||||
/// Create a new `ToDeviceRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -175,7 +180,7 @@ impl ToDeviceRequest {
|
||||
pub struct SignatureUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -191,7 +196,7 @@ impl SignatureUploadRequest {
|
||||
/// Create a new `SignatureUploadRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -210,7 +215,7 @@ impl SignatureUploadRequest {
|
||||
pub struct RoomMessageRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -226,7 +231,7 @@ impl RoomMessageRequest {
|
||||
/// Create a new `RoomMessageRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -245,7 +250,7 @@ impl RoomMessageRequest {
|
||||
pub struct KeysBackupRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: JsString,
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
@@ -256,12 +261,33 @@ pub struct KeysBackupRequest {
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
/** Other Requests * */
|
||||
|
||||
/// Request that will publish a cross signing identity.
|
||||
///
|
||||
/// This uploads the public cross signing key triplet.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct SigningKeysUploadRequest {
|
||||
/// The request ID.
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub id: Option<JsString>,
|
||||
|
||||
/// A JSON-encoded object of form:
|
||||
///
|
||||
/// ```json
|
||||
/// {"master_key": …, "self_signing_key": …, "user_signing_key": …}
|
||||
/// ```
|
||||
#[wasm_bindgen(readonly)]
|
||||
pub body: JsString,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl KeysBackupRequest {
|
||||
/// Create a new `KeysBackupRequest`.
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(id: JsString, body: JsString) -> KeysBackupRequest {
|
||||
Self { id, body }
|
||||
Self { id: Some(id), body }
|
||||
}
|
||||
|
||||
/// Get its request type.
|
||||
@@ -272,35 +298,56 @@ impl KeysBackupRequest {
|
||||
}
|
||||
|
||||
macro_rules! request {
|
||||
($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl TryFrom<(String, &$ruma_request)> for $request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$ruma_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
($destination_request:ident from $source_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
|
||||
impl $destination_request {
|
||||
pub(crate) fn to_json(request: &$source_request) -> Result<String, serde_json::Error> {
|
||||
let mut map = serde_json::Map::new();
|
||||
$(
|
||||
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
|
||||
)+
|
||||
let value = serde_json::Value::Object(map);
|
||||
let object = serde_json::Value::Object(map);
|
||||
|
||||
Ok($request {
|
||||
id: request_id.into(),
|
||||
body: serde_json::to_string(&value)?.into(),
|
||||
serde_json::to_string(&object)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&$source_request> for $destination_request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(request: &$source_request) -> Result<Self, Self::Error> {
|
||||
Ok($destination_request {
|
||||
id: None,
|
||||
body: Self::to_json(request)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<(String, &$source_request)> for $destination_request {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(
|
||||
(request_id, request): (String, &$source_request),
|
||||
) -> Result<Self, Self::Error> {
|
||||
Ok($destination_request {
|
||||
id: Some(request_id.into()),
|
||||
body: Self::to_json(request)?.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
|
||||
// Outgoing Requests
|
||||
request!(KeysUploadRequest from OriginalKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
|
||||
request!(KeysQueryRequest from OriginalKeysQueryRequest maps fields timeout, device_keys, token);
|
||||
request!(KeysClaimRequest from OriginalKeysClaimRequest maps fields timeout, one_time_keys);
|
||||
request!(ToDeviceRequest from OriginalToDeviceRequest maps fields event_type, txn_id, messages);
|
||||
request!(SignatureUploadRequest from OriginalSignatureUploadRequest maps fields signed_keys);
|
||||
request!(RoomMessageRequest from OriginalRoomMessageRequest maps fields room_id, txn_id, content);
|
||||
request!(KeysBackupRequest from OriginalKeysBackupRequest maps fields rooms);
|
||||
|
||||
// Other Requests
|
||||
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest maps fields master_key, self_signing_key, user_signing_key);
|
||||
|
||||
// JavaScript has no complex enums like Rust. To return structs of
|
||||
// different types, we have no choice that hiding everything behind a
|
||||
|
||||
38
bindings/matrix-sdk-crypto-js/src/store.rs
Normal file
38
bindings/matrix-sdk-crypto-js/src/store.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
//! Store types.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A struct containing private cross signing keys that can be backed
|
||||
/// up or uploaded to the secret store.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct CrossSigningKeyExport {
|
||||
pub(crate) inner: matrix_sdk_crypto::store::CrossSigningKeyExport,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::store::CrossSigningKeyExport> for CrossSigningKeyExport {
|
||||
fn from(inner: matrix_sdk_crypto::store::CrossSigningKeyExport) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl CrossSigningKeyExport {
|
||||
/// The seed of the master key encoded as unpadded base64.
|
||||
#[wasm_bindgen(getter, js_name = "masterKey")]
|
||||
pub fn master_key(&self) -> Option<String> {
|
||||
self.inner.master_key.clone()
|
||||
}
|
||||
|
||||
/// The seed of the self signing key encoded as unpadded base64.
|
||||
#[wasm_bindgen(getter, js_name = "self_signing_key")]
|
||||
pub fn self_signing_key(&self) -> Option<String> {
|
||||
self.inner.self_signing_key.clone()
|
||||
}
|
||||
|
||||
/// The seed of the user signing key encoded as unpadded base64.
|
||||
#[wasm_bindgen(getter, js_name = "userSigningKey")]
|
||||
pub fn user_signing_key(&self) -> Option<String> {
|
||||
self.inner.user_signing_key.clone()
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl Signatures {
|
||||
|
||||
/// Do we hold any signatures or is our collection completely
|
||||
/// empty.
|
||||
#[wasm_bindgen(getter, js_name = "isEmpty")]
|
||||
#[wasm_bindgen(js_name = "isEmpty")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.is_empty()
|
||||
}
|
||||
@@ -138,13 +138,13 @@ impl From<MaybeSignatureInner> for MaybeSignature {
|
||||
#[wasm_bindgen]
|
||||
impl MaybeSignature {
|
||||
/// Check whether the signature has been successfully decoded.
|
||||
#[wasm_bindgen(getter, js_name = "isValid")]
|
||||
#[wasm_bindgen(js_name = "isValid")]
|
||||
pub fn is_valid(&self) -> bool {
|
||||
self.inner.is_ok()
|
||||
}
|
||||
|
||||
/// Check whether the signature could not be successfully decoded.
|
||||
#[wasm_bindgen(getter, js_name = "isInvalid")]
|
||||
#[wasm_bindgen(js_name = "isInvalid")]
|
||||
pub fn is_invalid(&self) -> bool {
|
||||
self.inner.is_err()
|
||||
}
|
||||
|
||||
1109
bindings/matrix-sdk-crypto-js/src/verification.rs
Normal file
1109
bindings/matrix-sdk-crypto-js/src/verification.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,12 @@ impl Ed25519PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<vodozemac::Ed25519PublicKey> for Ed25519PublicKey {
|
||||
fn from(inner: vodozemac::Ed25519PublicKey) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// An Ed25519 digital signature, can be used to verify the
|
||||
/// authenticity of a message.
|
||||
#[wasm_bindgen]
|
||||
@@ -79,6 +85,12 @@ impl Curve25519PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<vodozemac::Curve25519PublicKey> for Curve25519PublicKey {
|
||||
fn from(inner: vodozemac::Curve25519PublicKey) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// Struct holding the two public identity keys of an account.
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
#[derive(Debug)]
|
||||
@@ -98,3 +110,98 @@ impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum over the different key types a device can have.
|
||||
///
|
||||
/// Currently devices have a curve25519 and ed25519 keypair. The keys
|
||||
/// transport format is a base64 encoded string, any unknown key type
|
||||
/// will be left as such a string.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct DeviceKey {
|
||||
inner: matrix_sdk_crypto::types::DeviceKey,
|
||||
}
|
||||
|
||||
impl From<matrix_sdk_crypto::types::DeviceKey> for DeviceKey {
|
||||
fn from(inner: matrix_sdk_crypto::types::DeviceKey) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl DeviceKey {
|
||||
/// Get the name of the device key.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn name(&self) -> DeviceKeyName {
|
||||
(&self.inner).into()
|
||||
}
|
||||
|
||||
/// Get the value associated to the `Curve25519` device key name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn curve25519(&self) -> Option<Curve25519PublicKey> {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match &self.inner {
|
||||
Curve25519(key) => Some((*key).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value associated to the `Ed25519` device key name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn ed25519(&self) -> Option<Ed25519PublicKey> {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match &self.inner {
|
||||
Ed25519(key) => Some((*key).into()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value associated to the `Unknown` device key name.
|
||||
#[wasm_bindgen(getter)]
|
||||
pub fn unknown(&self) -> Option<String> {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match &self.inner {
|
||||
Unknown(key) => Some(key.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the `DeviceKey` into a base64 encoded string.
|
||||
#[wasm_bindgen(js_name = "toBase64")]
|
||||
pub fn to_base64(&self) -> String {
|
||||
self.inner.to_base64()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&matrix_sdk_crypto::types::DeviceKey> for DeviceKeyName {
|
||||
fn from(device_key: &matrix_sdk_crypto::types::DeviceKey) -> Self {
|
||||
use matrix_sdk_crypto::types::DeviceKey::*;
|
||||
|
||||
match device_key {
|
||||
Curve25519(_) => Self::Curve25519,
|
||||
Ed25519(_) => Self::Ed25519,
|
||||
Unknown(_) => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum over the different key types a device can have.
|
||||
///
|
||||
/// Currently devices have a curve25519 and ed25519 keypair. The keys
|
||||
/// transport format is a base64 encoded string, any unknown key type
|
||||
/// will be left as such a string.
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub enum DeviceKeyName {
|
||||
/// The curve25519 device key.
|
||||
Curve25519,
|
||||
|
||||
/// The ed25519 device key.
|
||||
Ed25519,
|
||||
|
||||
/// An unknown device key.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
901
bindings/matrix-sdk-crypto-js/tests/device.test.js
Normal file
901
bindings/matrix-sdk-crypto-js/tests/device.test.js
Normal file
@@ -0,0 +1,901 @@
|
||||
const {
|
||||
OlmMachine,
|
||||
UserId,
|
||||
DeviceId,
|
||||
DeviceKeyId,
|
||||
RoomId,
|
||||
Device,
|
||||
LocalTrust,
|
||||
UserDevices,
|
||||
DeviceKey,
|
||||
DeviceKeyName,
|
||||
DeviceKeyAlgorithmName,
|
||||
Ed25519PublicKey,
|
||||
Curve25519PublicKey,
|
||||
Signatures,
|
||||
VerificationMethod,
|
||||
VerificationRequest,
|
||||
ToDeviceRequest,
|
||||
DeviceLists,
|
||||
KeysUploadRequest,
|
||||
RequestType,
|
||||
KeysQueryRequest,
|
||||
Sas,
|
||||
Emoji,
|
||||
SigningKeysUploadRequest,
|
||||
SignatureUploadRequest,
|
||||
Qr,
|
||||
QrCode,
|
||||
QrCodeScan,
|
||||
} = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { LoggerLevel, Tracing } = require('../pkg/matrix_sdk_crypto_js');
|
||||
const { zip, addMachineToMachine } = require('./helper');
|
||||
|
||||
describe('LocalTrust', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(LocalTrust.Verified).toStrictEqual(0);
|
||||
expect(LocalTrust.BlackListed).toStrictEqual(1);
|
||||
expect(LocalTrust.Ignored).toStrictEqual(2);
|
||||
expect(LocalTrust.Unset).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('DeviceKeyName', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(DeviceKeyName.Curve25519).toStrictEqual(0);
|
||||
expect(DeviceKeyName.Ed25519).toStrictEqual(1);
|
||||
expect(DeviceKeyName.Unknown).toStrictEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe(OlmMachine.name, () => {
|
||||
const user = new UserId('@alice:example.org');
|
||||
const device = new DeviceId('foobar');
|
||||
const room = new RoomId('!baz:matrix.org');
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || user, new_device || device);
|
||||
}
|
||||
|
||||
test('can read user devices', async () => {
|
||||
const m = await machine();
|
||||
const userDevices = await m.getUserDevices(user);
|
||||
|
||||
expect(userDevices).toBeInstanceOf(UserDevices);
|
||||
expect(userDevices.get(device)).toBeInstanceOf(Device);
|
||||
expect(userDevices.isAnyVerified()).toStrictEqual(false);
|
||||
expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]);
|
||||
expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]);
|
||||
});
|
||||
|
||||
test('can read a user device', async () => {
|
||||
const m = await machine();
|
||||
const dev = await m.getDevice(user, device);
|
||||
|
||||
expect(dev).toBeInstanceOf(Device);
|
||||
expect(dev.isVerified()).toStrictEqual(false);
|
||||
expect(dev.isCrossSigningTrusted()).toStrictEqual(false);
|
||||
|
||||
expect(dev.localTrustState).toStrictEqual(LocalTrust.Unset);
|
||||
expect(dev.isLocallyTrusted()).toStrictEqual(false);
|
||||
expect(await dev.setLocalTrust(LocalTrust.Verified)).toBeNull();
|
||||
expect(dev.localTrustState).toStrictEqual(LocalTrust.Verified);
|
||||
expect(dev.isLocallyTrusted()).toStrictEqual(true);
|
||||
|
||||
expect(dev.userId.toString()).toStrictEqual(user.toString());
|
||||
expect(dev.deviceId.toString()).toStrictEqual(device.toString());
|
||||
expect(dev.deviceName).toBeUndefined();
|
||||
|
||||
const deviceKey = dev.getKey(DeviceKeyAlgorithmName.Ed25519);
|
||||
|
||||
expect(deviceKey).toBeInstanceOf(DeviceKey);
|
||||
expect(deviceKey.name).toStrictEqual(DeviceKeyName.Ed25519);
|
||||
expect(deviceKey.curve25519).toBeUndefined();
|
||||
expect(deviceKey.ed25519).toBeInstanceOf(Ed25519PublicKey);
|
||||
expect(deviceKey.unknown).toBeUndefined();
|
||||
expect(deviceKey.toBase64()).toMatch(/^[A-Za-z0-9\+/]+$/);
|
||||
|
||||
expect(dev.curve25519Key).toBeInstanceOf(Curve25519PublicKey);
|
||||
expect(dev.ed25519Key).toBeInstanceOf(Ed25519PublicKey);
|
||||
|
||||
for (const [deviceKeyId, deviceKey] of dev.keys) {
|
||||
expect(deviceKeyId).toBeInstanceOf(DeviceKeyId);
|
||||
expect(deviceKey).toBeInstanceOf(DeviceKey);
|
||||
}
|
||||
|
||||
expect(dev.signatures).toBeInstanceOf(Signatures);
|
||||
expect(dev.isBlacklisted()).toStrictEqual(false);
|
||||
expect(dev.isDeleted()).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Key Verification', () => {
|
||||
const userId1 = new UserId('@alice:example.org');
|
||||
const deviceId1 = new DeviceId('alice_device');
|
||||
|
||||
const userId2 = new UserId('@bob:example.org');
|
||||
const deviceId2 = new DeviceId('bob_device');
|
||||
|
||||
function machine(new_user, new_device) {
|
||||
return new OlmMachine(new_user || userId1, new_device || deviceId1);
|
||||
}
|
||||
|
||||
describe('SAS', () => {
|
||||
// First Olm machine.
|
||||
let m1;
|
||||
|
||||
// Second Olm machine.
|
||||
let m2;
|
||||
|
||||
beforeAll(async () => {
|
||||
m1 = await machine(userId1, deviceId1);
|
||||
m2 = await machine(userId2, deviceId2);
|
||||
});
|
||||
|
||||
// Verification request for `m1`.
|
||||
let verificationRequest1;
|
||||
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
await addMachineToMachine(m1, m2);
|
||||
}
|
||||
|
||||
// Pick the device we want to start the verification with.
|
||||
const device2 = await m1.getDevice(userId2, deviceId2);
|
||||
|
||||
expect(device2).toBeInstanceOf(Device);
|
||||
|
||||
let outgoingVerificationRequest;
|
||||
// Request a verification from `m1` to `device2`.
|
||||
[verificationRequest1, outgoingVerificationRequest] = await device2.requestVerification();
|
||||
|
||||
expect(verificationRequest1).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest1.ownUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest1.otherDeviceId).toBeUndefined();
|
||||
expect(verificationRequest1.roomId).toBeUndefined();
|
||||
expect(verificationRequest1.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest1.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
flowId = verificationRequest1.flowId;
|
||||
});
|
||||
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
expect(verificationRequest2).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest2.ownUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(verificationRequest2.roomId).toBeUndefined();
|
||||
expect(verificationRequest2.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest2.weStarted()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isCancelled()).toStrictEqual(false);
|
||||
|
||||
const verificationRequests = m2.getVerificationRequests(userId1);
|
||||
expect(verificationRequests).toHaveLength(1);
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request (`m.key.verification.ready`)', async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.accept();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
|
||||
});
|
||||
|
||||
// SAS verification for the second machine.
|
||||
let sas2;
|
||||
|
||||
test('can start a SAS verification (`m.key.verification.start`)', async () => {
|
||||
// Let's start a SAS verification, from `m2` for example.
|
||||
[sas2, outgoingVerificationRequest] = await verificationRequest2.startSas();
|
||||
expect(sas2).toBeInstanceOf(Sas);
|
||||
|
||||
expect(sas2.userId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(sas2.deviceId.toString()).toStrictEqual(deviceId2.toString());
|
||||
expect(sas2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(sas2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(sas2.flowId).toStrictEqual(flowId);
|
||||
expect(sas2.roomId).toBeUndefined();
|
||||
expect(sas2.supportsEmoji()).toStrictEqual(false);
|
||||
expect(sas2.startedFromRequest()).toStrictEqual(true);
|
||||
expect(sas2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(sas2.haveWeConfirmed()).toStrictEqual(false);
|
||||
expect(sas2.hasBeenAccepted()).toStrictEqual(false);
|
||||
expect(sas2.cancelInfo()).toBeUndefined();
|
||||
expect(sas2.weStarted()).toStrictEqual(false);
|
||||
expect(sas2.timedOut()).toStrictEqual(false);
|
||||
expect(sas2.canBePresented()).toStrictEqual(false);
|
||||
expect(sas2.isDone()).toStrictEqual(false);
|
||||
expect(sas2.isCancelled()).toStrictEqual(false);
|
||||
expect(sas2.emoji()).toBeUndefined();
|
||||
expect(sas2.emojiIndex()).toBeUndefined();
|
||||
expect(sas2.decimals()).toBeUndefined();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the SAS start to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
// SAS verification for the second machine.
|
||||
let sas1;
|
||||
|
||||
test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => {
|
||||
// Let's fetch the ongoing SAS verification.
|
||||
sas1 = await m1.getVerification(userId2, flowId);
|
||||
|
||||
expect(sas1).toBeInstanceOf(Sas);
|
||||
|
||||
expect(sas1.userId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(sas1.deviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(sas1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(sas1.otherDeviceId.toString()).toStrictEqual(deviceId2.toString());
|
||||
expect(sas1.flowId).toStrictEqual(flowId);
|
||||
expect(sas1.roomId).toBeUndefined();
|
||||
expect(sas1.startedFromRequest()).toStrictEqual(true);
|
||||
expect(sas1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(sas1.haveWeConfirmed()).toStrictEqual(false);
|
||||
expect(sas1.hasBeenAccepted()).toStrictEqual(false);
|
||||
expect(sas1.cancelInfo()).toBeUndefined();
|
||||
expect(sas1.weStarted()).toStrictEqual(true);
|
||||
expect(sas1.timedOut()).toStrictEqual(false);
|
||||
expect(sas1.canBePresented()).toStrictEqual(false);
|
||||
expect(sas1.isDone()).toStrictEqual(false);
|
||||
expect(sas1.isCancelled()).toStrictEqual(false);
|
||||
expect(sas1.emoji()).toBeUndefined();
|
||||
expect(sas1.emojiIndex()).toBeUndefined();
|
||||
expect(sas1.decimals()).toBeUndefined();
|
||||
|
||||
// Let's accept thet SAS start request.
|
||||
let outgoingVerificationRequest = sas1.accept();
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the SAS accept to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('emojis are supported by both sides', () => {
|
||||
expect(sas1.supportsEmoji()).toStrictEqual(true);
|
||||
expect(sas2.supportsEmoji()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('one side sends verification key (`m.key.verification.key`)', async () => {
|
||||
// Let's send the verification keys from `m2` to `m1`.
|
||||
const outgoingRequests = await m2.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS key to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m2.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
});
|
||||
|
||||
test('other side sends back verification key (`m.key.verification.key`)', async () => {
|
||||
// Let's send the verification keys from `m1` to `m2`.
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
});
|
||||
|
||||
test('emojis match from both sides', () => {
|
||||
const emojis1 = sas1.emoji();
|
||||
const emojiIndexes1 = sas1.emojiIndex();
|
||||
const emojis2 = sas2.emoji();
|
||||
const emojiIndexes2 = sas2.emojiIndex();
|
||||
|
||||
expect(emojis1).toHaveLength(7);
|
||||
expect(emojiIndexes1).toHaveLength(emojis1.length);
|
||||
expect(emojis2).toHaveLength(emojis1.length);
|
||||
expect(emojiIndexes2).toHaveLength(emojis1.length);
|
||||
|
||||
const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
|
||||
|
||||
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) {
|
||||
expect(emoji1).toBeInstanceOf(Emoji);
|
||||
expect(emoji1.symbol).toMatch(isEmoji);
|
||||
expect(emoji1.description).toBeTruthy();
|
||||
|
||||
expect(emojiIndex1).toBeGreaterThanOrEqual(0);
|
||||
expect(emojiIndex1).toBeLessThanOrEqual(63);
|
||||
|
||||
expect(emoji2).toBeInstanceOf(Emoji);
|
||||
expect(emoji2.symbol).toStrictEqual(emoji1.symbol);
|
||||
expect(emoji2.description).toStrictEqual(emoji1.description);
|
||||
|
||||
expect(emojiIndex2).toStrictEqual(emojiIndex1);
|
||||
}
|
||||
});
|
||||
|
||||
test('decimals match from both sides', () => {
|
||||
const decimals1 = sas1.decimals();
|
||||
const decimals2 = sas2.decimals();
|
||||
|
||||
expect(decimals1).toHaveLength(3);
|
||||
expect(decimals2).toHaveLength(decimals1.length);
|
||||
|
||||
const isDecimal = /^[0-9]{4}$/;
|
||||
|
||||
for (const [decimal1, decimal2] of zip(decimals1, decimals2)) {
|
||||
expect(decimal1.toString()).toMatch(isDecimal);
|
||||
|
||||
expect(decimal2).toStrictEqual(decimal1);
|
||||
}
|
||||
});
|
||||
|
||||
test('can confirm keys match (`m.key.verification.mac`)', async () => {
|
||||
// `m1` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm();
|
||||
|
||||
expect(signatureUploadRequest).toBeUndefined();
|
||||
expect(outgoingVerificationRequests).toHaveLength(1);
|
||||
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS confirmation to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm back keys match (`m.key.verification.done`)', async () => {
|
||||
// `m2` confirms.
|
||||
const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm();
|
||||
|
||||
expect(signatureUploadRequest).toBeUndefined();
|
||||
expect(outgoingVerificationRequests).toHaveLength(2);
|
||||
|
||||
// `.mac`
|
||||
{
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[0];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS confirmation to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
}
|
||||
|
||||
// `.done`
|
||||
{
|
||||
let outgoingVerificationRequest = outgoingVerificationRequests[1];
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS done to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
}
|
||||
});
|
||||
|
||||
test('can send final done (`m.key.verification.done`)', async () => {
|
||||
const outgoingRequests = await m1.outgoingRequests();
|
||||
expect(outgoingRequests).toHaveLength(4);
|
||||
|
||||
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
|
||||
|
||||
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
const toDeviceRequestId = toDeviceRequest.id;
|
||||
const toDeviceRequestType = toDeviceRequest.type;
|
||||
|
||||
toDeviceRequest = JSON.parse(toDeviceRequest.body);
|
||||
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: toDeviceRequest.event_type,
|
||||
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send te SAS key to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
|
||||
});
|
||||
|
||||
test('can see if verification is done', () => {
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(true);
|
||||
|
||||
expect(sas1.isDone()).toStrictEqual(true);
|
||||
expect(sas2.isDone()).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QR Code', () => {
|
||||
if (undefined === Qr) {
|
||||
// qrcode supports is not enabled
|
||||
console.info('qrcode support is disabled, skip the associated test suite');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// First Olm machine.
|
||||
let m1;
|
||||
|
||||
// Second Olm machine.
|
||||
let m2;
|
||||
|
||||
beforeAll(async () => {
|
||||
m1 = await machine(userId1, deviceId1);
|
||||
m2 = await machine(userId2, deviceId2);
|
||||
});
|
||||
|
||||
// Verification request for `m1`.
|
||||
let verificationRequest1;
|
||||
|
||||
// The flow ID.
|
||||
let flowId;
|
||||
|
||||
test('can request verification (`m.key.verification.request`)', async () => {
|
||||
// Make `m1` and `m2` be aware of each other.
|
||||
{
|
||||
await addMachineToMachine(m2, m1);
|
||||
await addMachineToMachine(m1, m2);
|
||||
}
|
||||
|
||||
// Pick the device we want to start the verification with.
|
||||
const device2 = await m1.getDevice(userId2, deviceId2);
|
||||
|
||||
expect(device2).toBeInstanceOf(Device);
|
||||
|
||||
let outgoingVerificationRequest;
|
||||
// Request a verification from `m1` to `device2`.
|
||||
[verificationRequest1, outgoingVerificationRequest] = await device2.requestVerification([
|
||||
VerificationMethod.QrCodeScanV1, // by default
|
||||
VerificationMethod.QrCodeShowV1, // the one we add
|
||||
]);
|
||||
|
||||
expect(verificationRequest1).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest1.ownUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest1.otherDeviceId).toBeUndefined();
|
||||
expect(verificationRequest1.roomId).toBeUndefined();
|
||||
expect(verificationRequest1.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest1.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest1.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest1.weStarted()).toStrictEqual(true);
|
||||
expect(verificationRequest1.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
|
||||
flowId = verificationRequest1.flowId;
|
||||
});
|
||||
|
||||
// Verification request for `m2`.
|
||||
let verificationRequest2;
|
||||
|
||||
test('can fetch received request verification', async () => {
|
||||
// Oh, a new verification request.
|
||||
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
|
||||
|
||||
expect(verificationRequest2).toBeInstanceOf(VerificationRequest);
|
||||
|
||||
expect(verificationRequest2.ownUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(verificationRequest2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(verificationRequest2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(verificationRequest2.roomId).toBeUndefined();
|
||||
expect(verificationRequest2.cancelInfo).toBeUndefined();
|
||||
expect(verificationRequest2.isPassive()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(false);
|
||||
expect(verificationRequest2.timedOut()).toStrictEqual(false);
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
|
||||
expect(verificationRequest2.flowId).toStrictEqual(flowId);
|
||||
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(verificationRequest2.weStarted()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isDone()).toStrictEqual(false);
|
||||
expect(verificationRequest2.isCancelled()).toStrictEqual(false);
|
||||
|
||||
const verificationRequests = m2.getVerificationRequests(userId1);
|
||||
expect(verificationRequests).toHaveLength(1);
|
||||
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
|
||||
});
|
||||
|
||||
test('can accept a verification request with methods (`m.key.verification.ready`)', async () => {
|
||||
// Accept the verification request.
|
||||
let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([
|
||||
VerificationMethod.QrCodeScanV1, // by default
|
||||
VerificationMethod.QrCodeShowV1, // the one we add
|
||||
]);
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
// The request verification is ready.
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}],
|
||||
};
|
||||
|
||||
// Let's send the verification ready to `m1`.
|
||||
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('verification requests are synchronized and automatically updated', () => {
|
||||
expect(verificationRequest1.isReady()).toStrictEqual(true);
|
||||
expect(verificationRequest2.isReady()).toStrictEqual(true);
|
||||
|
||||
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
|
||||
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
|
||||
});
|
||||
|
||||
// QR verification for the second machine.
|
||||
let qr2;
|
||||
|
||||
test('can generate a QR code', async () => {
|
||||
qr2 = await verificationRequest2.generateQrCode();
|
||||
|
||||
expect(qr2).toBeInstanceOf(Qr);
|
||||
|
||||
expect(qr2.hasBeenScanned()).toStrictEqual(false);
|
||||
expect(qr2.hasBeenConfirmed()).toStrictEqual(false);
|
||||
expect(qr2.userId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(qr2.otherUserId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(qr2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
|
||||
expect(qr2.weStarted()).toStrictEqual(false);
|
||||
expect(qr2.cancelInfo()).toBeUndefined();
|
||||
expect(qr2.isDone()).toStrictEqual(false);
|
||||
expect(qr2.isCancelled()).toStrictEqual(false);
|
||||
expect(qr2.isSelfVerification()).toStrictEqual(false);
|
||||
expect(qr2.reciprocated()).toStrictEqual(false);
|
||||
expect(qr2.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(qr2.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
let qrCodeBytes;
|
||||
|
||||
test('can read QR code\'s bytes', async () => {
|
||||
const qrCodeHeader = 'MATRIX';
|
||||
const qrCodeVersion = '\x02';
|
||||
|
||||
qrCodeBytes = qr2.toBytes();
|
||||
|
||||
expect(qrCodeBytes).toHaveLength(122);
|
||||
expect(qrCodeBytes.slice(0, 7)).toStrictEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0)));
|
||||
});
|
||||
|
||||
test('can render QR code', async () => {
|
||||
const qrCode = qr2.toQrCode();
|
||||
|
||||
expect(qrCode).toBeInstanceOf(QrCode);
|
||||
|
||||
// Want to get `canvasBuffer` to render the QR code? Install `npm install canvas` and uncomment the following blocks.
|
||||
|
||||
//let canvasBuffer;
|
||||
|
||||
{
|
||||
const buffer = qrCode.renderIntoBuffer();
|
||||
|
||||
expect(buffer).toBeInstanceOf(Uint8ClampedArray);
|
||||
// 45px ⨉ 45px
|
||||
expect(buffer).toHaveLength(45 * 45);
|
||||
// 0 for a white pixel, 1 for a black pixel.
|
||||
expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true);
|
||||
|
||||
/*
|
||||
const { Canvas } = require('canvas');
|
||||
const canvas = new Canvas(55, 55);
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// New image data, filled with black, transparent pixels.
|
||||
const imageData = context.createImageData(45, 45);
|
||||
const data = imageData.data;
|
||||
|
||||
const [r, g, b, a] = [0, 1, 2, 3];
|
||||
|
||||
for (
|
||||
let dataNth = 0,
|
||||
bufferNth = 0;
|
||||
dataNth < data.length && bufferNth < buffer.length;
|
||||
dataNth += 4,
|
||||
bufferNth += 1
|
||||
) {
|
||||
data[dataNth + a] = 255;
|
||||
|
||||
// White pixel
|
||||
if (buffer[bufferNth] == 0) {
|
||||
data[dataNth + r] = 255;
|
||||
data[dataNth + g] = 255;
|
||||
data[dataNth + b] = 255;
|
||||
}
|
||||
}
|
||||
|
||||
context.putImageData(imageData, 5, 5);
|
||||
canvasBuffer = canvas.toBuffer('image/png');
|
||||
*/
|
||||
}
|
||||
|
||||
// Want to see the QR code? Uncomment the following block.
|
||||
/*
|
||||
{
|
||||
const fs = require('fs/promises');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
|
||||
const qrCodeFile = path.join(tempDirectory, 'qrcode.png');
|
||||
|
||||
console.log(`View the QR code at \`${qrCodeFile}\`.`);
|
||||
|
||||
expect(await fs.writeFile(qrCodeFile, canvasBuffer)).toBeUndefined();
|
||||
}
|
||||
*/
|
||||
});
|
||||
|
||||
let qr1;
|
||||
|
||||
test('can scan a QR code from bytes', async () => {
|
||||
const scan = QrCodeScan.fromBytes(qrCodeBytes);
|
||||
|
||||
expect(scan).toBeInstanceOf(QrCodeScan);
|
||||
|
||||
qr1 = await verificationRequest1.scanQrCode(scan);
|
||||
|
||||
expect(qr1).toBeInstanceOf(Qr);
|
||||
|
||||
expect(qr1.hasBeenScanned()).toStrictEqual(false);
|
||||
expect(qr1.hasBeenConfirmed()).toStrictEqual(false);
|
||||
expect(qr1.userId.toString()).toStrictEqual(userId1.toString());
|
||||
expect(qr1.otherUserId.toString()).toStrictEqual(userId2.toString());
|
||||
expect(qr1.otherDeviceId.toString()).toStrictEqual(deviceId2.toString());
|
||||
expect(qr1.weStarted()).toStrictEqual(true);
|
||||
expect(qr1.cancelInfo()).toBeUndefined();
|
||||
expect(qr1.isDone()).toStrictEqual(false);
|
||||
expect(qr1.isCancelled()).toStrictEqual(false);
|
||||
expect(qr1.isSelfVerification()).toStrictEqual(false);
|
||||
expect(qr1.reciprocated()).toStrictEqual(true);
|
||||
expect(qr1.flowId).toMatch(/^[a-f0-9]+$/);
|
||||
expect(qr1.roomId).toBeUndefined();
|
||||
});
|
||||
|
||||
test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => {
|
||||
let outgoingVerificationRequest = qr1.reciprocate();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId1.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been scanned', () => {
|
||||
expect(qr2.hasBeenScanned()).toStrictEqual(true);
|
||||
});
|
||||
|
||||
test('can confirm scanning (`m.key.verification.done`)', async () => {
|
||||
let outgoingVerificationRequest = qr2.confirmScanning();
|
||||
|
||||
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
|
||||
|
||||
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
|
||||
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
|
||||
|
||||
const toDeviceEvents = {
|
||||
events: [{
|
||||
sender: userId2.toString(),
|
||||
type: outgoingVerificationRequest.event_type,
|
||||
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
|
||||
}]
|
||||
};
|
||||
|
||||
// Let's send the verification request to `m2`.
|
||||
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
|
||||
});
|
||||
|
||||
test('can confirm QR code has been confirmed', () => {
|
||||
expect(qr2.hasBeenConfirmed()).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('VerificationMethod', () => {
|
||||
test('has the correct variant values', () => {
|
||||
expect(VerificationMethod.SasV1).toStrictEqual(0);
|
||||
expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1);
|
||||
expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2);
|
||||
expect(VerificationMethod.ReciprocateV1).toStrictEqual(3);
|
||||
});
|
||||
});
|
||||
80
bindings/matrix-sdk-crypto-js/tests/helper.js
Normal file
80
bindings/matrix-sdk-crypto-js/tests/helper.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require('../pkg/matrix_sdk_crypto_js');
|
||||
|
||||
function* zip(...arrays) {
|
||||
const len = Math.min(...arrays.map((array) => array.length));
|
||||
|
||||
for (let nth = 0; nth < len; ++nth) {
|
||||
yield [...arrays.map((array) => array.at(nth))]
|
||||
}
|
||||
}
|
||||
|
||||
// Add a machine to another machine, i.e. be sure a machine knows
|
||||
// another exists.
|
||||
async function addMachineToMachine(machineToAdd, machine) {
|
||||
const toDeviceEvents = JSON.stringify({});
|
||||
const changedDevices = new DeviceLists();
|
||||
const oneTimeKeyCounts = new Map();
|
||||
const unusedFallbackKeys = new Set();
|
||||
|
||||
const receiveSyncChanges = JSON.parse(await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
||||
|
||||
expect(receiveSyncChanges).toEqual({});
|
||||
|
||||
const outgoingRequests = await machineToAdd.outgoingRequests();
|
||||
|
||||
expect(outgoingRequests).toHaveLength(2);
|
||||
|
||||
let keysUploadRequest;
|
||||
// Read the `KeysUploadRequest`.
|
||||
{
|
||||
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
|
||||
expect(outgoingRequests[0].id).toBeDefined();
|
||||
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
|
||||
|
||||
const body = JSON.parse(outgoingRequests[0].body);
|
||||
expect(body.device_keys).toBeDefined();
|
||||
expect(body.one_time_keys).toBeDefined();
|
||||
|
||||
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
||||
const hypothetical_response = JSON.stringify({
|
||||
"one_time_key_counts": {
|
||||
"curve25519": 10,
|
||||
"signed_curve25519": 20
|
||||
}
|
||||
});
|
||||
const marked = await machineToAdd.markRequestAsSent(outgoingRequests[0].id, outgoingRequests[0].type, hypothetical_response);
|
||||
expect(marked).toStrictEqual(true);
|
||||
|
||||
keysUploadRequest = body;
|
||||
}
|
||||
|
||||
{
|
||||
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
||||
|
||||
let [signingKeysUploadRequest, _] = await machineToAdd.bootstrapCrossSigning(true);
|
||||
signingKeysUploadRequest = JSON.parse(signingKeysUploadRequest.body);
|
||||
|
||||
// Let's forge a `KeysQuery`'s response.
|
||||
let keyQueryResponse = {
|
||||
device_keys: {},
|
||||
master_keys: {},
|
||||
self_signing_keys: {},
|
||||
user_signing_keys: {},
|
||||
};
|
||||
const userId = machineToAdd.userId.toString();
|
||||
const deviceId = machineToAdd.deviceId.toString();
|
||||
keyQueryResponse.device_keys[userId] = {};
|
||||
keyQueryResponse.device_keys[userId][deviceId] = keysUploadRequest.device_keys;
|
||||
keyQueryResponse.master_keys[userId] = signingKeysUploadRequest.master_key;
|
||||
keyQueryResponse.self_signing_keys[userId] = signingKeysUploadRequest.self_signing_key;
|
||||
keyQueryResponse.user_signing_keys[userId] = signingKeysUploadRequest.user_signing_key;
|
||||
|
||||
const marked = await machine.markRequestAsSent(outgoingRequests[1].id, outgoingRequests[1].type, JSON.stringify(keyQueryResponse));
|
||||
expect(marked).toStrictEqual(true);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
zip,
|
||||
addMachineToMachine,
|
||||
};
|
||||
@@ -414,7 +414,7 @@ describe(OlmMachine.name, () => {
|
||||
const m = await machine();
|
||||
const signatures = await m.sign('foo');
|
||||
|
||||
expect(signatures.isEmpty).toStrictEqual(false);
|
||||
expect(signatures.isEmpty()).toStrictEqual(false);
|
||||
expect(signatures.count).toStrictEqual(1);
|
||||
|
||||
let base64;
|
||||
@@ -429,8 +429,8 @@ describe(OlmMachine.name, () => {
|
||||
|
||||
expect(s).toBeInstanceOf(MaybeSignature);
|
||||
|
||||
expect(s.isValid).toStrictEqual(true);
|
||||
expect(s.isInvalid).toStrictEqual(false);
|
||||
expect(s.isValid()).toStrictEqual(true);
|
||||
expect(s.isInvalid()).toStrictEqual(false);
|
||||
expect(s.invalidSignatureSource).toBeUndefined();
|
||||
|
||||
base64 = s.signature.toBase64();
|
||||
|
||||
@@ -417,44 +417,52 @@ impl Cancelled {
|
||||
}
|
||||
}
|
||||
|
||||
/// A key verification can be requested and started by a to-device
|
||||
/// request or a room event. `FlowId` helps to represent both
|
||||
/// usecases.
|
||||
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd)]
|
||||
pub enum FlowId {
|
||||
/// The flow ID comes from a to-device request.
|
||||
ToDevice(OwnedTransactionId),
|
||||
|
||||
/// The flow ID comes from a room event.
|
||||
InRoom(OwnedRoomId, OwnedEventId),
|
||||
}
|
||||
|
||||
impl FlowId {
|
||||
/// Get the room ID if the flow ID comes from a room event.
|
||||
pub fn room_id(&self) -> Option<&RoomId> {
|
||||
if let FlowId::InRoom(room_id, _) = &self {
|
||||
if let Self::InRoom(room_id, _) = &self {
|
||||
Some(room_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the ID a string.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
FlowId::InRoom(_, event_id) => event_id.as_str(),
|
||||
FlowId::ToDevice(transaction_id) => transaction_id.as_str(),
|
||||
Self::InRoom(_, event_id) => event_id.as_str(),
|
||||
Self::ToDevice(transaction_id) => transaction_id.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OwnedTransactionId> for FlowId {
|
||||
fn from(transaction_id: OwnedTransactionId) -> Self {
|
||||
FlowId::ToDevice(transaction_id)
|
||||
Self::ToDevice(transaction_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(OwnedRoomId, OwnedEventId)> for FlowId {
|
||||
fn from(ids: (OwnedRoomId, OwnedEventId)) -> Self {
|
||||
FlowId::InRoom(ids.0, ids.1)
|
||||
Self::InRoom(ids.0, ids.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(&RoomId, &EventId)> for FlowId {
|
||||
fn from(ids: (&RoomId, &EventId)) -> Self {
|
||||
FlowId::InRoom(ids.0.to_owned(), ids.1.to_owned())
|
||||
Self::InRoom(ids.0.to_owned(), ids.1.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user