Implement devices API (#20)
small improvements Cargo fmt Simplify insert and update methods Review feedback Remove has_device method calls Load all devices with a single db call Remove device as in logout Put all metadata on the same tree Create userdevice key fucntion Implement devices API Implement all the devices endpoints. There's a couple of pending tasks: - Integrate the "logout" logic once it lands to master (this should remove the given device from the database). - Track and store last seen timestamp and IP. Co-authored-by: timokoesters <timo@koesters.xyz> Co-authored-by: Guillem Nieto <gnieto.talo@gmail.com>next
parent
720d48bd67
commit
ed9b544ace
|
@ -13,6 +13,9 @@ use ruma_client_api::{
|
||||||
alias::{create_alias, delete_alias, get_alias},
|
alias::{create_alias, delete_alias, get_alias},
|
||||||
capabilities::get_capabilities,
|
capabilities::get_capabilities,
|
||||||
config::{get_global_account_data, set_global_account_data},
|
config::{get_global_account_data, set_global_account_data},
|
||||||
|
device::{
|
||||||
|
self, delete_device, delete_devices, get_device, get_devices, update_device,
|
||||||
|
},
|
||||||
directory::{
|
directory::{
|
||||||
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
|
self, get_public_rooms, get_public_rooms_filtered, get_room_visibility,
|
||||||
set_room_visibility,
|
set_room_visibility,
|
||||||
|
@ -52,7 +55,7 @@ use ruma_events::{
|
||||||
room::{canonical_alias, guest_access, history_visibility, join_rules, member, redaction},
|
room::{canonical_alias, guest_access, history_visibility, join_rules, member, redaction},
|
||||||
EventJson, EventType,
|
EventJson, EventType,
|
||||||
};
|
};
|
||||||
use ruma_identifiers::{RoomAliasId, RoomId, RoomVersionId, UserId};
|
use ruma_identifiers::{DeviceId, RoomAliasId, RoomId, RoomVersionId, UserId};
|
||||||
use serde_json::{json, value::RawValue};
|
use serde_json::{json, value::RawValue};
|
||||||
|
|
||||||
use crate::{server_server, utils, Database, MatrixResult, Ruma};
|
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
|
// Generate new device id if the user didn't specify one
|
||||||
let device_id = body
|
let device_id = body
|
||||||
.device_id
|
.device_id.clone()
|
||||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
|
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
|
||||||
|
|
||||||
// Generate new token for the device
|
// Generate new token for the device
|
||||||
|
@ -181,7 +184,7 @@ pub fn register_route(
|
||||||
|
|
||||||
// Add device
|
// Add device
|
||||||
db.users
|
db.users
|
||||||
.create_device(&user_id, &device_id, &token)
|
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Initial data
|
// Initial data
|
||||||
|
@ -300,6 +303,7 @@ pub fn login_route(
|
||||||
let device_id = body
|
let device_id = body
|
||||||
.body
|
.body
|
||||||
.device_id
|
.device_id
|
||||||
|
.clone()
|
||||||
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
|
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH));
|
||||||
|
|
||||||
// Generate a new token for the device
|
// Generate a new token for the device
|
||||||
|
@ -307,7 +311,7 @@ pub fn login_route(
|
||||||
|
|
||||||
// Add device
|
// Add device
|
||||||
db.users
|
db.users
|
||||||
.create_device(&user_id, &device_id, &token)
|
.create_device(&user_id, &device_id, &token, body.initial_device_display_name.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
MatrixResult(Ok(login::Response {
|
MatrixResult(Ok(login::Response {
|
||||||
|
@ -2430,6 +2434,93 @@ pub fn get_content_thumbnail_route(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/_matrix/client/r0/devices", data = "<body>")]
|
||||||
|
pub fn get_devices_route(
|
||||||
|
db: State<'_, Database>,
|
||||||
|
body: Ruma<get_devices::Request>,
|
||||||
|
) -> MatrixResult<get_devices::Response> {
|
||||||
|
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::<Vec<device::Device>>();
|
||||||
|
|
||||||
|
MatrixResult(Ok(get_devices::Response { devices }))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/_matrix/client/r0/devices/<device_id>", data = "<body>")]
|
||||||
|
pub fn get_device_route(
|
||||||
|
db: State<'_, Database>,
|
||||||
|
body: Ruma<get_device::Request>,
|
||||||
|
device_id: DeviceId,
|
||||||
|
) -> MatrixResult<get_device::Response> {
|
||||||
|
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/<device_id>", data = "<body>")]
|
||||||
|
pub fn update_device_route(
|
||||||
|
db: State<'_, Database>,
|
||||||
|
body: Ruma<update_device::Request>,
|
||||||
|
device_id: DeviceId,
|
||||||
|
) -> MatrixResult<update_device::Response> {
|
||||||
|
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/<device_id>", data = "<body>")]
|
||||||
|
pub fn delete_device_route(
|
||||||
|
db: State<'_, Database>,
|
||||||
|
body: Ruma<delete_device::Request>,
|
||||||
|
device_id: DeviceId,
|
||||||
|
) -> MatrixResult<delete_device::Response> {
|
||||||
|
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 = "<body>")]
|
||||||
|
pub fn delete_devices_route(
|
||||||
|
db: State<'_, Database>,
|
||||||
|
body: Ruma<delete_devices::Request>,
|
||||||
|
) -> MatrixResult<delete_devices::Response> {
|
||||||
|
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..>")]
|
#[options("/<_segments..>")]
|
||||||
pub fn options_route(
|
pub fn options_route(
|
||||||
_segments: rocket::http::uri::Segments<'_>,
|
_segments: rocket::http::uri::Segments<'_>,
|
||||||
|
|
|
@ -56,10 +56,10 @@ impl Database {
|
||||||
),
|
),
|
||||||
users: users::Users {
|
users: users::Users {
|
||||||
userid_password: db.open_tree("userid_password").unwrap(),
|
userid_password: db.open_tree("userid_password").unwrap(),
|
||||||
userdeviceids: db.open_tree("userdeviceids").unwrap(),
|
|
||||||
userid_displayname: db.open_tree("userid_displayname").unwrap(),
|
userid_displayname: db.open_tree("userid_displayname").unwrap(),
|
||||||
userid_avatarurl: db.open_tree("userid_avatarurl").unwrap(),
|
userid_avatarurl: db.open_tree("userid_avatarurl").unwrap(),
|
||||||
userdeviceid_token: db.open_tree("userdeviceid_token").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(),
|
token_userdeviceid: db.open_tree("token_userdeviceid").unwrap(),
|
||||||
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys").unwrap(),
|
onetimekeyid_onetimekeys: db.open_tree("onetimekeyid_onetimekeys").unwrap(),
|
||||||
userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys").unwrap(),
|
userdeviceid_devicekeys: db.open_tree("userdeviceid_devicekeys").unwrap(),
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
use crate::{utils, Error, Result};
|
use crate::{utils, Error, Result};
|
||||||
use js_int::UInt;
|
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_events::{to_device::AnyToDeviceEvent, EventJson, EventType};
|
||||||
use ruma_identifiers::{DeviceId, UserId};
|
use ruma_identifiers::{DeviceId, UserId};
|
||||||
use std::{collections::BTreeMap, convert::TryFrom};
|
use std::{collections::BTreeMap, convert::TryFrom, time::SystemTime};
|
||||||
|
|
||||||
pub struct Users {
|
pub struct Users {
|
||||||
pub(super) userid_password: sled::Tree,
|
pub(super) userid_password: sled::Tree,
|
||||||
pub(super) userid_displayname: sled::Tree,
|
pub(super) userid_displayname: sled::Tree,
|
||||||
pub(super) userid_avatarurl: sled::Tree,
|
pub(super) userid_avatarurl: sled::Tree,
|
||||||
pub(super) userdeviceids: sled::Tree,
|
|
||||||
pub(super) userdeviceid_token: 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) token_userdeviceid: sled::Tree,
|
||||||
|
|
||||||
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId
|
pub(super) onetimekeyid_onetimekeys: sled::Tree, // OneTimeKeyId = UserId + AlgorithmAndDeviceId
|
||||||
|
@ -105,25 +108,40 @@ impl Users {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new device to a user.
|
/// 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<String>,
|
||||||
|
) -> Result<()> {
|
||||||
if !self.exists(user_id)? {
|
if !self.exists(user_id)? {
|
||||||
return Err(Error::BadRequest(
|
return Err(Error::BadRequest(
|
||||||
"tried to create device for nonexistent user",
|
"tried to create device for nonexistent user",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut key = user_id.to_string().as_bytes().to_vec();
|
let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
|
||||||
key.push(0xff);
|
userdeviceid.push(0xff);
|
||||||
key.extend_from_slice(device_id.as_bytes());
|
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)?;
|
self.set_token(user_id, device_id, token)?;
|
||||||
|
|
||||||
Ok(())
|
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<()> {
|
pub fn remove_device(&self, user_id: &UserId, device_id: &DeviceId) -> Result<()> {
|
||||||
let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
|
let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
|
||||||
userdeviceid.push(0xff);
|
userdeviceid.push(0xff);
|
||||||
|
@ -147,8 +165,7 @@ impl Users {
|
||||||
|
|
||||||
// TODO: Remove onetimekeys
|
// TODO: Remove onetimekeys
|
||||||
|
|
||||||
// Remove the device
|
self.userdeviceid_metadata.remove(&userdeviceid)?;
|
||||||
self.userdeviceids.remove(userdeviceid)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -157,14 +174,18 @@ impl Users {
|
||||||
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<DeviceId>> {
|
pub fn all_device_ids(&self, user_id: &UserId) -> impl Iterator<Item = Result<DeviceId>> {
|
||||||
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
let mut prefix = user_id.to_string().as_bytes().to_vec();
|
||||||
prefix.push(0xff);
|
prefix.push(0xff);
|
||||||
self.userdeviceids.scan_prefix(prefix).keys().map(|bytes| {
|
// All devices have metadata
|
||||||
Ok(utils::string_from_bytes(
|
self.userdeviceid_metadata
|
||||||
&*bytes?
|
.scan_prefix(prefix)
|
||||||
.rsplit(|&b| b == 0xff)
|
.keys()
|
||||||
.next()
|
.map(|bytes| {
|
||||||
.ok_or(Error::BadDatabase("userdeviceid is invalid"))?,
|
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.
|
/// Replaces the access token of one device.
|
||||||
|
@ -173,7 +194,8 @@ impl Users {
|
||||||
userdeviceid.push(0xff);
|
userdeviceid.push(0xff);
|
||||||
userdeviceid.extend_from_slice(device_id.as_bytes());
|
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(
|
return Err(Error::BadRequest(
|
||||||
"Tried to set token for nonexistent device",
|
"Tried to set token for nonexistent device",
|
||||||
));
|
));
|
||||||
|
@ -203,7 +225,8 @@ impl Users {
|
||||||
key.push(0xff);
|
key.push(0xff);
|
||||||
key.extend_from_slice(device_id.as_bytes());
|
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(
|
return Err(Error::BadRequest(
|
||||||
"Tried to set token for nonexistent device",
|
"Tried to set token for nonexistent device",
|
||||||
));
|
));
|
||||||
|
@ -396,4 +419,49 @@ impl Users {
|
||||||
|
|
||||||
Ok(events)
|
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<Option<Device>> {
|
||||||
|
let mut userdeviceid = user_id.to_string().as_bytes().to_vec();
|
||||||
|
userdeviceid.push(0xff);
|
||||||
|
userdeviceid.extend_from_slice(device_id.as_bytes());
|
||||||
|
|
||||||
|
self.userdeviceid_metadata
|
||||||
|
.get(&userdeviceid)?
|
||||||
|
.map_or(Ok(None), |bytes| Ok(Some(serde_json::from_slice(&bytes)?)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all_devices_metadata(&self, user_id: &UserId) -> impl Iterator<Item = Result<Device>> {
|
||||||
|
let mut key = user_id.to_string().as_bytes().to_vec();
|
||||||
|
key.push(0xff);
|
||||||
|
|
||||||
|
self.userdeviceid_metadata
|
||||||
|
.scan_prefix(key)
|
||||||
|
.values()
|
||||||
|
.map(|bytes| Ok(serde_json::from_slice::<Device>(&bytes?)?))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,11 @@ fn setup_rocket() -> rocket::Rocket {
|
||||||
client_server::create_content_route,
|
client_server::create_content_route,
|
||||||
client_server::get_content_route,
|
client_server::get_content_route,
|
||||||
client_server::get_content_thumbnail_route,
|
client_server::get_content_thumbnail_route,
|
||||||
|
client_server::get_devices_route,
|
||||||
|
client_server::get_device_route,
|
||||||
|
client_server::update_device_route,
|
||||||
|
client_server::delete_device_route,
|
||||||
|
client_server::delete_devices_route,
|
||||||
client_server::options_route,
|
client_server::options_route,
|
||||||
server_server::well_known_server,
|
server_server::well_known_server,
|
||||||
server_server::get_server_version,
|
server_server::get_server_version,
|
||||||
|
|
Loading…
Reference in New Issue