use std::future::Future;
use matrix_sdk::encryption::recovery::RecoveryState;
use matrix_sdk::ruma::api::client::uiaa::{
AuthData, MatrixUserIdentifier, Password, UserIdentifier,
};
use matrix_sdk::ruma::OwnedDeviceId;
use matrix_sdk::RoomMemberships;
use super::{failed, room_id, user_id, Client, Error};
use crate::api::types::{ChannelId, UserId};
use crate::config::Credentials;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Session {
pub id: String,
pub name: String,
pub ip: String,
pub seen: i64,
pub current: bool,
}
impl Client {
async fn confirmed<T, F, Fut>(&self, call: F) -> Result<T, Error>
where
F: Fn(Option<AuthData>) -> Fut,
Fut: Future<Output = Result<T, matrix_sdk::Error>>,
{
let first = match call(None).await {
Ok(done) => return Ok(done),
Err(e) => e,
};
let Some(info) = first.as_uiaa_response() else {
return Err(failed(first));
};
let Credentials::Password { login, password } = &self.credentials else {
return Err(failed(
"This needs the password login: do it in your account page",
));
};
let who = UserIdentifier::Matrix(MatrixUserIdentifier::new(login.clone()));
let mut auth = Password::new(who, password.clone());
auth.session.clone_from(&info.session);
call(Some(AuthData::Password(auth))).await.map_err(failed)
}
pub async fn sessions(&self) -> Result<Vec<Session>, Error> {
let sdk = self.sdk();
let mine = sdk.device_id().map(ToOwned::to_owned);
let mut sessions: Vec<Session> = sdk
.devices()
.await
.map_err(failed)?
.devices
.into_iter()
.map(|d| Session {
current: Some(&d.device_id) == mine.as_ref(),
id: d.device_id.to_string(),
name: d.display_name.unwrap_or_default(),
ip: d.last_seen_ip.unwrap_or_default(),
seen: d.last_seen_ts.map_or(0, |t| i64::from(t.0)),
})
.collect();
sessions.sort_by_key(|s| std::cmp::Reverse(s.seen));
Ok(sessions)
}
pub async fn end_session(&self, id: &str) -> Result<(), Error> {
let sdk = self.sdk();
if sdk.device_id().is_some_and(|d| d.as_str() == id) {
return Err(failed("This is the running session: use :quit"));
}
let ids = [OwnedDeviceId::from(id)];
self.confirmed(|auth| {
let (sdk, ids) = (&sdk, &ids);
async move { Ok(sdk.delete_devices(ids, auth).await.map(drop)?) }
})
.await
}
pub async fn change_password(&self, new: &str) -> Result<(), Error> {
let sdk = self.sdk();
self.confirmed(|auth| {
let sdk = &sdk;
async move { sdk.account().change_password(new, auth).await.map(drop) }
})
.await
}
pub async fn deactivate(&self) -> Result<(), Error> {
let sdk = self.sdk();
self.confirmed(|auth| {
let sdk = &sdk;
async move { sdk.account().deactivate(None, auth, false).await.map(drop) }
})
.await?;
let _ = std::fs::remove_dir_all(&self.dir);
Ok(())
}
pub async fn recovery_key(&self) -> Result<String, Error> {
let sdk = self.sdk();
let recovery = sdk.encryption().recovery();
let key = if recovery.state() == RecoveryState::Enabled {
recovery.reset_key().await
} else {
self.confirmed(|auth| {
let sdk = &sdk;
async move {
sdk.encryption()
.bootstrap_cross_signing_if_needed(auth)
.await
}
})
.await?;
recovery.enable().await
};
key.map_err(failed)
}
pub async fn changed_keys(&self, channel: &ChannelId) -> Vec<String> {
let (Ok(id), sdk) = (room_id(channel), self.sdk()) else {
return Vec::new();
};
let Some(room) = sdk.get_room(&id) else {
return Vec::new();
};
let mut names = Vec::new();
for member in room
.members(RoomMemberships::ACTIVE)
.await
.unwrap_or_default()
{
if member.user_id().as_str() == self.me.0 {
continue;
}
let identity = sdk.encryption().get_user_identity(member.user_id()).await;
if identity
.ok()
.flatten()
.is_some_and(|i| i.has_verification_violation())
{
names.push(member.name().to_owned());
}
}
names
}
pub async fn trust(&self, user: &UserId) -> Result<(), Error> {
let sdk = self.sdk();
let identity = sdk
.encryption()
.request_user_identity(&user_id(user)?)
.await
.map_err(failed)?
.ok_or_else(|| failed("This user has no keys yet"))?;
if identity.has_verification_violation() {
identity.withdraw_verification().await.map_err(failed)
} else {
identity.pin().await.map_err(failed)
}
}
}