diff --git a/src/client_server.rs b/src/client_server.rs
index 2cb7a5fc..5f5070e9 100644
--- a/src/client_server.rs
+++ b/src/client_server.rs
@@ -13,6 +13,9 @@ use ruma_client_api::{
alias::{create_alias, delete_alias, get_alias},
capabilities::get_capabilities,
config::{get_global_account_data, set_global_account_data},
+ device::{
+ self, delete_device, delete_devices, get_device, get_devices, update_device,
+ },
directory::{
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
set_room_visibility,
@@ -52,7 +55,7 @@ use ruma_events::{
room::{canonical_alias, guest_access, history_visibility, join_rules, member, redaction},
EventJson, EventType,
};
-use ruma_identifiers::{RoomAliasId, RoomId, RoomVersionId, UserId};
+use ruma_identifiers::{DeviceId, RoomAliasId, RoomId, RoomVersionId, UserId};
use serde_json::{json, value::RawValue};
use crate::{server_server, utils, Database, MatrixResult, Ruma};
@@ -173,7 +176,7 @@ pub fn register_route(
// Generate new device id if the user didn't specify one
let device_id = body
- .device_id
+ .device_id.clone()
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
// Generate new token for the device
@@ -181,7 +184,7 @@ pub fn register_route(
// Add device
db.users
- .create_device(&user_id, &device_id, &token)
+ .create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())
.unwrap();
// Initial data
@@ -300,6 +303,7 @@ pub fn login_route(
let device_id = body
.body
.device_id
+ .clone()
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
// Generate a new token for the device
@@ -307,7 +311,7 @@ pub fn login_route(
// Add device
db.users
- .create_device(&user_id, &device_id, &token)
+ .create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())
.unwrap();
MatrixResult(Ok(login::Response {
@@ -2430,6 +2434,93 @@ pub fn get_content_thumbnail_route(
}
}
+#[get("/_matrix/client/r0/devices", data = "
")]
+pub fn get_devices_route(
+ db: State<'_, Database>,
+ body: Ruma,
+) -> MatrixResult {
+ let user_id = body.user_id.as_ref().expect("user is authenticated");
+
+ let devices = db
+ .users
+ .all_devices_metadata(user_id)
+ .map(|r| r.unwrap())
+ .collect::>();
+
+ MatrixResult(Ok(get_devices::Response { devices }))
+}
+
+#[get("/_matrix/client/r0/devices/", data = "")]
+pub fn get_device_route(
+ db: State<'_, Database>,
+ body: Ruma,
+ device_id: DeviceId,
+) -> MatrixResult {
+ let user_id = body.user_id.as_ref().expect("user is authenticated");
+ let device = db.users.get_device_metadata(&user_id, &device_id).unwrap();
+
+ match device {
+ None => MatrixResult(Err(Error {
+ kind: ErrorKind::NotFound,
+ message: "Device not found".to_string(),
+ status_code: http::StatusCode::NOT_FOUND,
+ })),
+ Some(device) => MatrixResult(Ok(get_device::Response { device })),
+ }
+}
+
+#[put("/_matrix/client/r0/devices/", data = "")]
+pub fn update_device_route(
+ db: State<'_, Database>,
+ body: Ruma,
+ device_id: DeviceId,
+) -> MatrixResult {
+ let user_id = body.user_id.as_ref().expect("user is authenticated");
+ let device = db.users.get_device_metadata(&user_id, &device_id).unwrap();
+
+ match device {
+ None => MatrixResult(Err(Error {
+ kind: ErrorKind::NotFound,
+ message: "Device not found".to_string(),
+ status_code: http::StatusCode::NOT_FOUND,
+ })),
+ Some(mut device) => {
+ device.display_name = body.display_name.clone();
+
+ db.users
+ .update_device_metadata(&user_id, &device_id, &device)
+ .unwrap();
+
+ MatrixResult(Ok(update_device::Response))
+ }
+ }
+}
+
+#[delete("/_matrix/client/r0/devices/", data = "")]
+pub fn delete_device_route(
+ db: State<'_, Database>,
+ body: Ruma,
+ device_id: DeviceId,
+) -> MatrixResult {
+ let user_id = body.user_id.as_ref().expect("user is authenticated");
+ db.users.remove_device(&user_id, &device_id).unwrap();
+
+ MatrixResult(Ok(delete_device::Response))
+}
+
+#[post("/_matrix/client/r0/delete_devices", data = "")]
+pub fn delete_devices_route(
+ db: State<'_, Database>,
+ body: Ruma,
+) -> MatrixResult {
+ let user_id = body.user_id.as_ref().expect("user is authenticated");
+ for device_id in &body.devices {
+ db.users.remove_device(&user_id, &device_id).unwrap()
+ }
+
+ MatrixResult(Ok(delete_devices::Response))
+}
+
#[options("/<_segments..>")]
pub fn options_route(
_segments: rocket::http::uri::Segments<'_>,
diff --git a/src/database.rs b/src/database.rs
index de14805b..d4927a70 100644
--- a/src/database.rs
+++ b/src/database.rs
@@ -56,10 +56,10 @@ impl Database {
),
users: users::Users {
userid_password: db.open_tree("userid_password").unwrap(),
- userdeviceids: db.open_tree("userdeviceids").unwrap(),
userid_displayname: db.open_tree("userid_displayname").unwrap(),
userid_avatarurl: db.open_tree("userid_avatarurl").unwrap(),
userdeviceid_token: db.open_tree("userdeviceid_token").unwrap(),
+ userdeviceid_metadata: db.open_tree("userdeviceid_metadata").unwrap(),
token_userdeviceid: db.open_tree("token_userdeviceid").unwrap(),
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys").unwrap(),
userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys").unwrap(),
diff --git a/src/database/users.rs b/src/database/users.rs
index 6540a70a..e216301a 100644
--- a/src/database/users.rs
+++ b/src/database/users.rs
@@ -1,16 +1,19 @@
use crate::{utils, Error, Result};
use js_int::UInt;
-use ruma_client_api::r0::keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey};
+use ruma_client_api::r0::{
+ device::Device,
+ keys::{AlgorithmAndDeviceId, DeviceKeys, KeyAlgorithm, OneTimeKey},
+};
use ruma_events::{to_device::AnyToDeviceEvent, EventJson, EventType};
use ruma_identifiers::{DeviceId, UserId};
-use std::{collections::BTreeMap, convert::TryFrom};
+use std::{collections::BTreeMap, convert::TryFrom, time::SystemTime};
pub struct Users {
pub(super) userid_password: sled::Tree,
pub(super) userid_displayname: sled::Tree,
pub(super) userid_avatarurl: sled::Tree,
- pub(super) userdeviceids: sled::Tree,
pub(super) userdeviceid_token: sled::Tree,
+ pub(super) userdeviceid_metadata: sled::Tree, // This is also used to check if a device exists
pub(super) token_userdeviceid: sled::Tree,
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId
@@ -105,25 +108,40 @@ impl Users {
}
/// Adds a new device to a user.
- pub fn create_device(&self, user_id: &UserId, device_id: &DeviceId, token: &str) -> Result<()> {
+ pub fn create_device(
+ &self,
+ user_id: &UserId,
+ device_id: &DeviceId,
+ token: &str,
+ initial_device_display_name: Option,
+ ) -> Result<()> {
if !self.exists(user_id)? {
return Err(Error::BadRequest(
"tried to create device for nonexistent user",
));
}
- let mut key = user_id.to_string().as_bytes().to_vec();
- key.push(0xff);
- key.extend_from_slice(device_id.as_bytes());
+ let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
+ userdeviceid.push(0xff);
+ userdeviceid.extend_from_slice(device_id.as_bytes());
- self.userdeviceids.insert(key, &[])?;
+ self.userdeviceid_metadata.insert(
+ userdeviceid,
+ serde_json::to_string(&Device {
+ device_id: device_id.clone(),
+ display_name: initial_device_display_name,
+ last_seen_ip: None, // TODO
+ last_seen_ts: Some(SystemTime::now()),
+ })?
+ .as_bytes(),
+ )?;
self.set_token(user_id, device_id, token)?;
Ok(())
}
- /// Removes a device from a user
+ /// Removes a device from a user.
pub fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
userdeviceid.push(0xff);
@@ -147,8 +165,7 @@ impl Users {
// TODO: Remove onetimekeys
- // Remove the device
- self.userdeviceids.remove(userdeviceid)?;
+ self.userdeviceid_metadata.remove(&userdeviceid)?;
Ok(())
}
@@ -157,14 +174,18 @@ impl Users {
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator- > {
let mut prefix = user_id.to_string().as_bytes().to_vec();
prefix.push(0xff);
- self.userdeviceids.scan_prefix(prefix).keys().map(|bytes| {
- Ok(utils::string_from_bytes(
- &*bytes?
- .rsplit(|&b| b == 0xff)
- .next()
- .ok_or(Error::BadDatabase("userdeviceid is invalid"))?,
- )?)
- })
+ // All devices have metadata
+ self.userdeviceid_metadata
+ .scan_prefix(prefix)
+ .keys()
+ .map(|bytes| {
+ Ok(utils::string_from_bytes(
+ &*bytes?
+ .rsplit(|&b| b == 0xff)
+ .next()
+ .ok_or(Error::BadDatabase("userdeviceid is invalid"))?,
+ )?)
+ })
}
/// Replaces the access token of one device.
@@ -173,7 +194,8 @@ impl Users {
userdeviceid.push(0xff);
userdeviceid.extend_from_slice(device_id.as_bytes());
- if self.userdeviceids.get(&userdeviceid)?.is_none() {
+ // All devices have metadata
+ if self.userdeviceid_metadata.get(&userdeviceid)?.is_none() {
return Err(Error::BadRequest(
"Tried to set token for nonexistent device",
));
@@ -203,7 +225,8 @@ impl Users {
key.push(0xff);
key.extend_from_slice(device_id.as_bytes());
- if self.userdeviceids.get(&key)?.is_none() {
+ // All devices have metadata
+ if self.userdeviceid_metadata.get(&key)?.is_none() {
return Err(Error::BadRequest(
"Tried to set token for nonexistent device",
));
@@ -396,4 +419,49 @@ impl Users {
Ok(events)
}
+
+ pub fn update_device_metadata(
+ &self,
+ user_id: &UserId,
+ device_id: &DeviceId,
+ device: &Device,
+ ) -> Result<()> {
+ let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
+ userdeviceid.push(0xff);
+ userdeviceid.extend_from_slice(device_id.as_bytes());
+
+ if self.userdeviceid_metadata.get(userdeviceid)?.is_none() {
+ return Err(Error::BadRequest("device does not exist"));
+ }
+
+ self.userdeviceid_metadata
+ .insert(userdeviceid, serde_json::to_string(device)?.as_bytes())?;
+
+ Ok(())
+ }
+
+ /// Get device metadata.
+ pub fn get_device_metadata(
+ &self,
+ user_id: &UserId,
+ device_id: &DeviceId,
+ ) -> Result