shift-bot/src/serve/verification.rs
Pierre de Lacroix f83fded289
initial commit
2026-06-27 17:33:03 +02:00

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(())
}