197 lines
5.8 KiB
Rust
197 lines
5.8 KiB
Rust
use std::io::Write;
|
|
|
|
use anyhow::Result;
|
|
use clap::Parser;
|
|
use futures::stream::StreamExt;
|
|
use matrix_sdk::{
|
|
config::SyncSettings,
|
|
encryption::verification::{format_emojis, Emoji, SasState, SasVerification, Verification},
|
|
ruma::{
|
|
events::{
|
|
key::verification::{
|
|
request::ToDeviceKeyVerificationRequestEvent,
|
|
start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent},
|
|
},
|
|
room::message::{MessageType, OriginalSyncRoomMessageEvent},
|
|
},
|
|
UserId,
|
|
},
|
|
Client,
|
|
};
|
|
use url::Url;
|
|
|
|
async fn wait_for_confirmation(sas: SasVerification, emoji: [Emoji; 7]) {
|
|
println!("\nDo the emojis match: \n{}", format_emojis(emoji));
|
|
print!("Confirm with `yes` or cancel with `no`: ");
|
|
std::io::stdout().flush().expect("We should be able to flush stdout");
|
|
|
|
let mut input = String::new();
|
|
std::io::stdin().read_line(&mut input).expect("error: unable to read user input");
|
|
|
|
match input.trim().to_lowercase().as_ref() {
|
|
"yes" | "true" | "ok" => sas.confirm().await.unwrap(),
|
|
_ => sas.cancel().await.unwrap(),
|
|
}
|
|
}
|
|
|
|
async fn print_devices(user_id: &UserId, client: &Client) {
|
|
println!("Devices of user {user_id}");
|
|
|
|
for device in client.encryption().get_user_devices(user_id).await.unwrap().devices() {
|
|
println!(
|
|
" {:<10} {:<30} {:<}",
|
|
device.device_id(),
|
|
device.display_name().unwrap_or("-"),
|
|
device.is_verified()
|
|
);
|
|
}
|
|
}
|
|
|
|
async fn sas_verification_handler(client: Client, sas: SasVerification) {
|
|
println!(
|
|
"Starting verification with {} {}",
|
|
&sas.other_device().user_id(),
|
|
&sas.other_device().device_id()
|
|
);
|
|
print_devices(sas.other_device().user_id(), &client).await;
|
|
sas.accept().await.unwrap();
|
|
|
|
let mut stream = sas.changes();
|
|
|
|
while let Some(state) = stream.next().await {
|
|
match state {
|
|
SasState::KeysExchanged { emojis, decimals: _ } => {
|
|
tokio::spawn(wait_for_confirmation(
|
|
sas.clone(),
|
|
emojis.expect("We only support verifications using emojis").emojis,
|
|
));
|
|
}
|
|
SasState::Done { .. } => {
|
|
let device = sas.other_device();
|
|
|
|
println!(
|
|
"Successfully verified device {} {} {:?}",
|
|
device.user_id(),
|
|
device.device_id(),
|
|
device.local_trust_state()
|
|
);
|
|
|
|
print_devices(sas.other_device().user_id(), &client).await;
|
|
|
|
break;
|
|
}
|
|
SasState::Cancelled(cancel_info) => {
|
|
println!("The verification has been cancelled, reason: {}", cancel_info.reason());
|
|
|
|
break;
|
|
}
|
|
SasState::Started { .. } | SasState::Accepted { .. } | SasState::Confirmed => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn sync(client: Client) -> matrix_sdk::Result<()> {
|
|
client.add_event_handler(
|
|
|ev: ToDeviceKeyVerificationRequestEvent, client: Client| async move {
|
|
let request = client
|
|
.encryption()
|
|
.get_verification_request(&ev.sender, &ev.content.transaction_id)
|
|
.await
|
|
.expect("Request object wasn't created");
|
|
|
|
request.accept().await.expect("Can't accept verification request");
|
|
},
|
|
);
|
|
|
|
client.add_event_handler(|ev: ToDeviceKeyVerificationStartEvent, client: Client| async move {
|
|
if let Some(Verification::SasV1(sas)) = client
|
|
.encryption()
|
|
.get_verification(&ev.sender, ev.content.transaction_id.as_str())
|
|
.await
|
|
{
|
|
tokio::spawn(sas_verification_handler(client, sas));
|
|
}
|
|
});
|
|
|
|
client.add_event_handler(|ev: OriginalSyncRoomMessageEvent, client: Client| async move {
|
|
if let MessageType::VerificationRequest(_) = &ev.content.msgtype {
|
|
let request = client
|
|
.encryption()
|
|
.get_verification_request(&ev.sender, &ev.event_id)
|
|
.await
|
|
.expect("Request object wasn't created");
|
|
|
|
request.accept().await.expect("Can't accept verification request");
|
|
}
|
|
});
|
|
|
|
client.add_event_handler(
|
|
|ev: OriginalSyncKeyVerificationStartEvent, client: Client| async move {
|
|
if let Some(Verification::SasV1(sas)) = client
|
|
.encryption()
|
|
.get_verification(&ev.sender, ev.content.relates_to.event_id.as_str())
|
|
.await
|
|
{
|
|
tokio::spawn(sas_verification_handler(client, sas));
|
|
}
|
|
},
|
|
);
|
|
|
|
client.sync(SyncSettings::new()).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Parser, Debug)]
|
|
struct Cli {
|
|
/// The homeserver to connect to.
|
|
#[clap(value_parser)]
|
|
homeserver: Url,
|
|
|
|
/// The user name that should be used for the login.
|
|
#[clap(value_parser)]
|
|
user_name: String,
|
|
|
|
/// The password that should be used for the login.
|
|
#[clap(value_parser)]
|
|
password: String,
|
|
|
|
/// Set the proxy that should be used for the connection.
|
|
#[clap(short, long)]
|
|
proxy: Option<Url>,
|
|
|
|
/// Enable verbose logging output.
|
|
#[clap(short, long, action)]
|
|
verbose: bool,
|
|
}
|
|
|
|
async fn login(cli: Cli) -> Result<Client> {
|
|
let builder = Client::builder().homeserver_url(cli.homeserver);
|
|
|
|
let builder = if let Some(proxy) = cli.proxy { builder.proxy(proxy) } else { builder };
|
|
|
|
let client = builder.build().await?;
|
|
|
|
client
|
|
.login_username(&cli.user_name, &cli.password)
|
|
.initial_device_display_name("rust-sdk")
|
|
.await?;
|
|
|
|
Ok(client)
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<()> {
|
|
let cli = Cli::parse();
|
|
|
|
if cli.verbose {
|
|
tracing_subscriber::fmt::init();
|
|
}
|
|
|
|
let client = login(cli).await?;
|
|
|
|
sync(client).await?;
|
|
|
|
Ok(())
|
|
}
|