use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{Err, debug, err}; use futures::StreamExt; use ruma::{ MilliSecondsSinceUnixEpoch, OwnedDeviceId, api::client::{ device::{self, delete_device, delete_devices, get_device, get_devices, update_device}, error::ErrorKind, uiaa::{AuthFlow, AuthType, UiaaInfo}, }, }; use super::SESSION_ID_LENGTH; use crate::{Error, Result, Ruma, client::DEVICE_ID_LENGTH, utils}; /// # `GET /_matrix/client/r0/devices` /// /// Get metadata on all devices of the sender user. pub(crate) async fn get_devices_route( State(services): State, body: Ruma, ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let devices: Vec = services .users .all_devices_metadata(sender_user) .collect() .await; Ok(get_devices::v3::Response { devices }) } /// # `GET /_matrix/client/r0/devices/{deviceId}` /// /// Get metadata on a single device of the sender user. pub(crate) async fn get_device_route( State(services): State, body: Ruma, ) -> Result { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let device = services .users .get_device_metadata(sender_user, &body.body.device_id) .await .map_err(|_| err!(Request(NotFound("Device not found."))))?; Ok(get_device::v3::Response { device }) } /// # `PUT /_matrix/client/r0/devices/{deviceId}` /// /// Updates the metadata on a given device of the sender user. #[tracing::instrument(skip_all, fields(%client), name = "update_device")] pub(crate) async fn update_device_route( State(services): State, InsecureClientIp(client): InsecureClientIp, body: Ruma, ) -> Result { let sender_user = body.sender_user(); let appservice = body.appservice_info.as_ref(); match services .users .get_device_metadata(sender_user, &body.device_id) .await { | Ok(mut device) => { device.display_name.clone_from(&body.display_name); device.last_seen_ip.clone_from(&Some(client.to_string())); device .last_seen_ts .clone_from(&Some(MilliSecondsSinceUnixEpoch::now())); services .users .update_device_metadata(sender_user, &body.device_id, &device) .await?; Ok(update_device::v3::Response {}) }, | Err(_) => { let Some(appservice) = appservice else { return Err!(Request(NotFound("Device not found."))); }; if !appservice.registration.device_management { return Err!(Request(NotFound("Device not found."))); } debug!( "Creating new device for {sender_user} from appservice {} as MSC4190 is enabled \ and device ID does not exist", appservice.registration.id ); let device_id = OwnedDeviceId::from(utils::random_string(DEVICE_ID_LENGTH)); services .users .create_device( sender_user, &device_id, &appservice.registration.as_token, None, Some(client.to_string()), ) .await?; return Ok(update_device::v3::Response {}); }, } } /// # `DELETE /_matrix/client/r0/devices/{deviceId}` /// /// Deletes the given device. /// /// - Requires UIAA to verify user password /// - Invalidates access token /// - Deletes device metadata (device id, device display name, last seen ip, /// last seen ts) /// - Forgets to-device events /// - Triggers device list updates pub(crate) async fn delete_device_route( State(services): State, body: Ruma, ) -> Result { let (sender_user, sender_device) = body.sender(); let appservice = body.appservice_info.as_ref(); if appservice.is_some_and(|appservice| appservice.registration.device_management) { debug!( "Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \ enabled" ); services .users .remove_device(sender_user, &body.device_id) .await; return Ok(delete_device::v3::Response {}); } // UIAA let mut uiaainfo = UiaaInfo { flows: vec![AuthFlow { stages: vec![AuthType::Password] }], completed: Vec::new(), params: Box::default(), session: None, auth_error: None, }; match &body.auth { | Some(auth) => { let (worked, uiaainfo) = services .uiaa .try_auth(sender_user, sender_device, auth, &uiaainfo) .await?; if !worked { return Err!(Uiaa(uiaainfo)); } // Success! }, | _ => match body.json_body { | Some(ref json) => { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); services .uiaa .create(sender_user, sender_device, &uiaainfo, json); return Err!(Uiaa(uiaainfo)); }, | _ => { return Err!(Request(NotJson("Not json."))); }, }, } services .users .remove_device(sender_user, &body.device_id) .await; Ok(delete_device::v3::Response {}) } /// # `POST /_matrix/client/v3/delete_devices` /// /// Deletes the given list of devices. /// /// - Requires UIAA to verify user password unless from an appservice with /// MSC4190 enabled. /// /// For each device: /// - Invalidates access token /// - Deletes device metadata (device id, device display name, last seen ip, /// last seen ts) /// - Forgets to-device events /// - Triggers device list updates pub(crate) async fn delete_devices_route( State(services): State, body: Ruma, ) -> Result { let (sender_user, sender_device) = body.sender(); let appservice = body.appservice_info.as_ref(); if appservice.is_some_and(|appservice| appservice.registration.device_management) { debug!( "Skipping UIAA for {sender_user} as this is from an appservice and MSC4190 is \ enabled" ); for device_id in &body.devices { services.users.remove_device(sender_user, device_id).await; } return Ok(delete_devices::v3::Response {}); } // UIAA let mut uiaainfo = UiaaInfo { flows: vec![AuthFlow { stages: vec![AuthType::Password] }], completed: Vec::new(), params: Box::default(), session: None, auth_error: None, }; match &body.auth { | Some(auth) => { let (worked, uiaainfo) = services .uiaa .try_auth(sender_user, sender_device, auth, &uiaainfo) .await?; if !worked { return Err(Error::Uiaa(uiaainfo)); } // Success! }, | _ => match body.json_body { | Some(ref json) => { uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH)); services .uiaa .create(sender_user, sender_device, &uiaainfo, json); return Err(Error::Uiaa(uiaainfo)); }, | _ => { return Err(Error::BadRequest(ErrorKind::NotJson, "Not json.")); }, }, } for device_id in &body.devices { services.users.remove_device(sender_user, device_id).await; } Ok(delete_devices::v3::Response {}) }