example_emoji_verification/
main.rs

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