example_emoji_verification/
main.rs1#![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 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 #[clap(value_parser)]
161 homeserver: Url,
162
163 #[clap(value_parser)]
165 user_name: String,
166
167 #[clap(value_parser)]
169 password: String,
170
171 #[clap(short, long)]
173 proxy: Option<Url>,
174
175 #[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}