Skip to main content

example_emoji_verification/
main.rs

1#![recursion_limit = "256"]
2
3use std::io::Write;
4
5use anyhow::Result;
6use clap::Parser;
7use futures_util::stream::StreamExt;
8use matrix_sdk::{
9    Client,
10    config::SyncSettings,
11    encryption::verification::{
12        Emoji, SasState, SasVerification, Verification, VerificationRequest,
13        VerificationRequestState, format_emojis,
14    },
15    ruma::{
16        UserId,
17        events::{
18            key::verification::request::ToDeviceKeyVerificationRequestEvent,
19            room::message::{MessageType, OriginalSyncRoomMessageEvent},
20        },
21    },
22};
23use url::Url;
24
25async fn wait_for_confirmation(sas: SasVerification, emoji: [Emoji; 7]) {
26    println!("\nDo the emojis match: \n{}", format_emojis(emoji));
27    print!("Confirm with `yes` or cancel with `no`: ");
28    std::io::stdout().flush().expect("We should be able to flush stdout");
29
30    let mut input = String::new();
31    std::io::stdin().read_line(&mut input).expect("error: unable to read user input");
32
33    match input.trim().to_lowercase().as_ref() {
34        "yes" | "true" | "ok" => sas.confirm().await.unwrap(),
35        _ => sas.cancel().await.unwrap(),
36    }
37}
38
39async fn print_devices(user_id: &UserId, client: &Client) {
40    println!("Devices of user {user_id}");
41
42    for device in client.encryption().get_user_devices(user_id).await.unwrap().devices() {
43        if device.device_id()
44            == client.device_id().expect("We should be logged in now and know our device id")
45        {
46            continue;
47        }
48
49        println!(
50            "   {:<10} {:<30} {:<}",
51            device.device_id(),
52            device.display_name().unwrap_or("-"),
53            if device.is_verified() { "✅" } else { "❌" }
54        );
55    }
56}
57
58async fn sas_verification_handler(client: Client, sas: SasVerification) {
59    println!(
60        "Starting verification with {} {}",
61        sas.other_device().user_id(),
62        sas.other_device().device_id()
63    );
64    print_devices(sas.other_device().user_id(), &client).await;
65    sas.accept().await.unwrap();
66
67    let mut stream = sas.changes();
68
69    while let Some(state) = stream.next().await {
70        match state {
71            SasState::KeysExchanged { emojis, decimals: _ } => {
72                tokio::spawn(wait_for_confirmation(
73                    sas.clone(),
74                    emojis.expect("We only support verifications using emojis").emojis,
75                ));
76            }
77            SasState::Done { .. } => {
78                let device = sas.other_device();
79
80                println!(
81                    "Successfully verified device {} {} {:?}",
82                    device.user_id(),
83                    device.device_id(),
84                    device.local_trust_state()
85                );
86
87                print_devices(sas.other_device().user_id(), &client).await;
88
89                break;
90            }
91            SasState::Cancelled(cancel_info) => {
92                println!("The verification has been cancelled, reason: {}", cancel_info.reason());
93
94                break;
95            }
96            SasState::Created { .. }
97            | SasState::Started { .. }
98            | SasState::Accepted { .. }
99            | SasState::Confirmed => (),
100        }
101    }
102}
103
104async fn request_verification_handler(client: Client, request: VerificationRequest) {
105    println!("Accepting verification request from {}", request.other_user_id());
106    request.accept().await.expect("Can't accept verification request");
107
108    let mut stream = request.changes();
109
110    while let Some(state) = stream.next().await {
111        match state {
112            VerificationRequestState::Created { .. }
113            | VerificationRequestState::Requested { .. }
114            | VerificationRequestState::Ready { .. } => (),
115            VerificationRequestState::Transitioned { verification } => {
116                // We only support SAS verification.
117                if let Verification::SasV1(s) = verification {
118                    tokio::spawn(sas_verification_handler(client, s));
119                    break;
120                }
121            }
122            VerificationRequestState::Done | VerificationRequestState::Cancelled(_) => break,
123        }
124    }
125}
126
127async fn sync(client: Client) -> matrix_sdk::Result<()> {
128    client.add_event_handler(
129        |ev: ToDeviceKeyVerificationRequestEvent, client: Client| async move {
130            let request = client
131                .encryption()
132                .get_verification_request(&ev.sender, &ev.content.transaction_id)
133                .await
134                .expect("Request object wasn't created");
135
136            tokio::spawn(request_verification_handler(client, request));
137        },
138    );
139
140    client.add_event_handler(|ev: OriginalSyncRoomMessageEvent, client: Client| async move {
141        if let MessageType::VerificationRequest(_) = &ev.content.msgtype {
142            let request = client
143                .encryption()
144                .get_verification_request(&ev.sender, &ev.event_id)
145                .await
146                .expect("Request object wasn't created");
147
148            tokio::spawn(request_verification_handler(client, request));
149        }
150    });
151
152    client.sync(SyncSettings::new()).await?;
153
154    Ok(())
155}
156
157#[derive(Parser, Debug)]
158struct Cli {
159    /// The homeserver to connect to.
160    #[clap(value_parser)]
161    homeserver: Url,
162
163    /// The user name that should be used for the login.
164    #[clap(value_parser)]
165    user_name: String,
166
167    /// The password that should be used for the login.
168    #[clap(value_parser)]
169    password: String,
170
171    /// Set the proxy that should be used for the connection.
172    #[clap(short, long)]
173    proxy: Option<Url>,
174
175    /// Enable verbose logging output.
176    #[clap(short, long, action)]
177    verbose: bool,
178}
179
180async fn login(cli: Cli) -> Result<Client> {
181    let builder = Client::builder().homeserver_url(cli.homeserver);
182
183    let builder = if let Some(proxy) = cli.proxy { builder.proxy(proxy) } else { builder };
184
185    let client = builder.build().await?;
186
187    client
188        .matrix_auth()
189        .login_username(&cli.user_name, &cli.password)
190        .initial_device_display_name("rust-sdk")
191        .await?;
192
193    Ok(client)
194}
195
196#[tokio::main]
197async fn main() -> Result<()> {
198    let cli = Cli::parse();
199
200    if cli.verbose {
201        tracing_subscriber::fmt::init();
202    }
203
204    let client = login(cli).await?;
205
206    sync(client).await?;
207
208    Ok(())
209}