168 lines
4.9 KiB
Rust
168 lines
4.9 KiB
Rust
/*
|
|
Copyright © 2026 Anzenlang
|
|
|
|
Licensed under the PolyForm Noncommercial License 1.0.0
|
|
https://polyformproject.org/licenses/noncommercial/
|
|
|
|
SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
|
SPDX-AI-Restriction: No training allowed. See NOTICE file.
|
|
|
|
See LICENSE file for complete terms.
|
|
|
|
WARNING: The contents of this file may NOT be used to train AI/LLM models. See NOTICE for legal
|
|
details.
|
|
*/
|
|
|
|
use std::io::Write;
|
|
|
|
use futures_util::stream::StreamExt;
|
|
use matrix_sdk::{
|
|
encryption::verification::{
|
|
Emoji, SasState, SasVerification, Verification, VerificationRequest,
|
|
VerificationRequestState, format_emojis,
|
|
},
|
|
ruma::UserId,
|
|
};
|
|
|
|
prelude! {}
|
|
|
|
async fn wait_for_confirmation(sas: SasVerification, emoji: [Emoji; 7]) -> Res<()> {
|
|
println!("\ndo the emojis match: \n{}", format_emojis(emoji));
|
|
print!("confirm with `yes` or cancel with `no`: ");
|
|
std::io::stdout()
|
|
.flush()
|
|
.context("failed to flush stdout while asking for confirmation")?;
|
|
|
|
let mut input = String::new();
|
|
std::io::stdin()
|
|
.read_line(&mut input)
|
|
.context("unable to read user input while asking for confirmation")?;
|
|
|
|
match input.trim().to_lowercase().as_ref() {
|
|
"yes" | "true" | "ok" => sas.confirm().await.context("SAS confirmation failed"),
|
|
_ => sas.cancel().await.context("SAS cancellation failed"),
|
|
}
|
|
}
|
|
|
|
async fn print_devices(user_id: &UserId, client: &Client) -> Res<()> {
|
|
let mut acc = String::with_capacity(500);
|
|
|
|
for device in client
|
|
.encryption()
|
|
.get_user_devices(user_id)
|
|
.await?
|
|
.devices()
|
|
{
|
|
if device.device_id()
|
|
== client
|
|
.device_id()
|
|
.context("we should be logged in now and know our device id")?
|
|
{
|
|
continue;
|
|
}
|
|
|
|
acc = format!(
|
|
"{}\n- {:<10} {:<30} {:<}",
|
|
acc,
|
|
device.device_id(),
|
|
device.display_name().unwrap_or("-"),
|
|
if device.is_verified() { "✅" } else { "❌" }
|
|
);
|
|
}
|
|
acc = if acc.is_empty() { " none".into() } else { acc };
|
|
|
|
info!("Devices of user {user_id}:{acc}");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn sas_verification_handler(client: Client, sas: SasVerification) -> Res<()> {
|
|
debug!(
|
|
"starting verification with {} {}",
|
|
&sas.other_device().user_id(),
|
|
&sas.other_device().device_id()
|
|
);
|
|
print_devices(sas.other_device().user_id(), &client)
|
|
.await
|
|
.context("failed to print devices")?;
|
|
sas.accept().await?;
|
|
|
|
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
|
|
.context("we only support verifications using emojis")?
|
|
.emojis,
|
|
));
|
|
}
|
|
SasState::Done { .. } => {
|
|
let device = sas.other_device();
|
|
|
|
debug!(
|
|
"successfully verified device {} {} {:?}",
|
|
device.user_id(),
|
|
device.device_id(),
|
|
device.local_trust_state()
|
|
);
|
|
|
|
print_devices(sas.other_device().user_id(), &client)
|
|
.await
|
|
.context("failed to print devices")?;
|
|
|
|
break;
|
|
}
|
|
SasState::Cancelled(cancel_info) => {
|
|
debug!(
|
|
"verification has been cancelled, reason: {}",
|
|
cancel_info.reason()
|
|
);
|
|
|
|
break;
|
|
}
|
|
SasState::Created { .. }
|
|
| SasState::Started { .. }
|
|
| SasState::Accepted { .. }
|
|
| SasState::Confirmed => (),
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn request_verification_handler(client: Client, request: VerificationRequest) -> Res<()> {
|
|
info!(
|
|
"accepting verification request from {}",
|
|
request.other_user_id()
|
|
);
|
|
request
|
|
.accept()
|
|
.await
|
|
.context("could not accept verification request")?;
|
|
|
|
let mut stream = request.changes();
|
|
|
|
while let Some(state) = stream.next().await {
|
|
match state {
|
|
VerificationRequestState::Created { .. }
|
|
| VerificationRequestState::Requested { .. }
|
|
| VerificationRequestState::Ready { .. } => (),
|
|
VerificationRequestState::Transitioned { verification } => {
|
|
// We only support SAS verification.
|
|
if let Verification::SasV1(s) = verification {
|
|
tokio::spawn(sas_verification_handler(client, s));
|
|
break;
|
|
}
|
|
}
|
|
VerificationRequestState::Done | VerificationRequestState::Cancelled(_) => break,
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|