From b9eb39a9c69ef3be69d517553adb8646cd4ea4a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timo=20K=C3=B6sters?= Date: Tue, 31 Aug 2021 19:14:37 +0200 Subject: [PATCH] docs: documentation for every endpoint --- src/appservice_server.rs | 2 +- src/client_server/account.rs | 59 ++++--- src/client_server/alias.rs | 32 +++- src/client_server/backup.rs | 89 ++++++++++ src/client_server/capabilities.rs | 2 +- src/client_server/config.rs | 14 +- src/client_server/context.rs | 6 + src/client_server/device.rs | 29 ++++ src/client_server/directory.rs | 20 ++- src/client_server/filter.rs | 6 + src/client_server/keys.rs | 34 +++- src/client_server/media.rs | 19 +++ src/client_server/membership.rs | 60 ++++++- src/client_server/message.rs | 13 ++ src/client_server/mod.rs | 3 + src/client_server/presence.rs | 9 ++ src/client_server/profile.rs | 25 +++ src/client_server/push.rs | 32 ++++ src/client_server/read_marker.rs | 9 ++ src/client_server/redact.rs | 5 + src/client_server/room.rs | 36 +++++ src/client_server/search.rs | 5 + src/client_server/session.rs | 19 ++- src/client_server/state.rs | 33 +++- src/client_server/sync.rs | 35 +++- src/client_server/tag.rs | 15 ++ src/client_server/thirdparty.rs | 3 + src/client_server/to_device.rs | 3 + src/client_server/typing.rs | 3 + src/client_server/unversioned.rs | 2 +- src/client_server/user_directory.rs | 5 + src/client_server/voip.rs | 3 + src/database/key_backups.rs | 21 +++ src/database/rooms.rs | 47 ++++-- src/server_server.rs | 242 ++++++++++++++++++++++++---- 35 files changed, 847 insertions(+), 93 deletions(-) diff --git a/src/appservice_server.rs b/src/appservice_server.rs index 9fc7dce..8be524c 100644 --- a/src/appservice_server.rs +++ b/src/appservice_server.rs @@ -9,7 +9,7 @@ use std::{ }; use tracing::warn; -pub async fn send_request( +pub(crate) async fn send_request( globals: &crate::database::globals::Globals, registration: serde_yaml::Value, request: T, diff --git a/src/client_server/account.rs b/src/client_server/account.rs index e68c957..4b610a3 100644 --- a/src/client_server/account.rs +++ b/src/client_server/account.rs @@ -40,8 +40,12 @@ const GUEST_NAME_LENGTH: usize = 10; /// /// Checks if a username is valid and available on this server. /// -/// - Returns true if no user or appservice on this server claimed this username -/// - This will not reserve the username, so the username might become invalid when trying to register +/// Conditions for returning true: +/// - The user id is not historical +/// - The server name of the user id matches this server +/// - No user or appservice on this server already claimed this username +/// +/// Note: This will not reserve the username, so the username might become invalid when trying to register #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/register/available", data = "") @@ -80,11 +84,15 @@ pub async fn get_register_available_route( /// /// Register an account on this homeserver. /// -/// - Returns the device id and access_token unless `inhibit_login` is true -/// - When registering a guest account, all parameters except initial_device_display_name will be -/// ignored -/// - Creates a new account and a device for it -/// - The account will be populated with default account data +/// You can use [`GET /_matrix/client/r0/register/available`](fn.get_register_available_route.html) +/// to check if the user id is valid and available. +/// +/// - Only works if registration is enabled +/// - If type is guest: ignores all parameters except initial_device_display_name +/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage) +/// - If type is not guest and no username is given: Always fails after UIAA check +/// - Creates a new account and populates it with default account data +/// - If `inhibit_login` is false: Creates a device and returns device id and access_token #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/register", data = "") @@ -129,7 +137,7 @@ pub async fn register_route( ))?; // Check if username is creative enough - if !missing_username && db.users.exists(&user_id)? { + if db.users.exists(&user_id)? { return Err(Error::BadRequest( ErrorKind::UserInUse, "Desired user ID is already taken.", @@ -193,12 +201,12 @@ pub async fn register_route( // Create user db.users.create(&user_id, password)?; + // Default to pretty displayname let displayname = format!("{} ⚡️", user_id.localpart()); - db.users .set_displayname(&user_id, Some(displayname.clone()))?; - // Initial data + // Initial account data db.account_data.update( None, &user_id, @@ -211,6 +219,7 @@ pub async fn register_route( &db.globals, )?; + // Inhibit login does not work for guests if !is_guest && body.inhibit_login { return Ok(register::Response { access_token: None, @@ -231,7 +240,7 @@ pub async fn register_route( // Generate new token for the device let token = utils::random_string(TOKEN_LENGTH); - // Add device + // Create device for this account db.users.create_device( &user_id, &device_id, @@ -239,7 +248,7 @@ pub async fn register_route( body.initial_device_display_name.clone(), )?; - // If this is the first user on this server, create the admins room + // If this is the first user on this server, create the admin room if db.users.count()? == 1 { // Create a user for the server let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name()) @@ -529,9 +538,16 @@ pub async fn register_route( /// /// Changes the password of this account. /// -/// - Invalidates all other access tokens if logout_devices is true -/// - Deletes all other devices and most of their data (to-device events, last seen, etc.) if -/// logout_devices is true +/// - Requires UIAA to verify user password +/// - Changes the password of the sender user +/// - The password hash is calculated using argon2 with 32 character salt, the plain password is +/// not saved +/// +/// If logout_devices is true it does the following for each device except the sender 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 #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/account/password", data = "") @@ -598,9 +614,9 @@ pub async fn change_password_route( /// # `GET _matrix/client/r0/account/whoami` /// -/// Get user_id of this account. +/// Get user_id of the sender user. /// -/// - Also works for Application Services +/// Note: Also works for Application Services #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/account/whoami", data = "") @@ -616,11 +632,13 @@ pub async fn whoami_route(body: Ruma) -> ConduitResult", data = "") @@ -24,6 +27,13 @@ pub async fn create_alias_route( db: DatabaseGuard, body: Ruma>, ) -> ConduitResult { + if body.room_alias.server_name() != db.globals.server_name() { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Alias is from another server.", + )); + } + if db.rooms.id_from_alias(&body.room_alias)?.is_some() { return Err(Error::Conflict("Alias already exists.")); } @@ -36,6 +46,12 @@ pub async fn create_alias_route( Ok(create_alias::Response::new().into()) } +/// # `DELETE /_matrix/client/r0/directory/room/{roomAlias}` +/// +/// Deletes a room alias from this server. +/// +/// - TODO: additional access control checks +/// - TODO: Update canonical alias event #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/r0/directory/room/<_>", data = "") @@ -45,13 +61,27 @@ pub async fn delete_alias_route( db: DatabaseGuard, body: Ruma>, ) -> ConduitResult { + if body.room_alias.server_name() != db.globals.server_name() { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Alias is from another server.", + )); + } + db.rooms.set_alias(&body.room_alias, None, &db.globals)?; + // TODO: update alt_aliases? + db.flush()?; Ok(delete_alias::Response::new().into()) } +/// # `GET /_matrix/client/r0/directory/room/{roomAlias}` +/// +/// Resolve an alias locally or over federation. +/// +/// - TODO: Suggest more servers to join via #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/directory/room/<_>", data = "") @@ -64,7 +94,7 @@ pub async fn get_alias_route( get_alias_helper(&db, &body.room_alias).await } -pub async fn get_alias_helper( +pub(crate) async fn get_alias_helper( db: &Database, room_alias: &RoomAliasId, ) -> ConduitResult { diff --git a/src/client_server/backup.rs b/src/client_server/backup.rs index 06f9818..259f1a9 100644 --- a/src/client_server/backup.rs +++ b/src/client_server/backup.rs @@ -12,6 +12,9 @@ use ruma::api::client::{ #[cfg(feature = "conduit_bin")] use rocket::{delete, get, post, put}; +/// # `POST /_matrix/client/r0/room_keys/version` +/// +/// Creates a new backup. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/unstable/room_keys/version", data = "") @@ -31,6 +34,9 @@ pub async fn create_backup_route( Ok(create_backup::Response { version }.into()) } +/// # `PUT /_matrix/client/r0/room_keys/version/{version}` +/// +/// Update information about an existing backup. Only `auth_data` can be modified. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/unstable/room_keys/version/<_>", data = "") @@ -49,6 +55,9 @@ pub async fn update_backup_route( Ok(update_backup::Response {}.into()) } +/// # `GET /_matrix/client/r0/room_keys/version` +/// +/// Get information about the latest backup version. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/unstable/room_keys/version", data = "") @@ -77,6 +86,9 @@ pub async fn get_latest_backup_route( .into()) } +/// # `GET /_matrix/client/r0/room_keys/version` +/// +/// Get information about an existing backup. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/unstable/room_keys/version/<_>", data = "") @@ -104,6 +116,11 @@ pub async fn get_backup_route( .into()) } +/// # `DELETE /_matrix/client/r0/room_keys/version/{version}` +/// +/// Delete an existing key backup. +/// +/// - Deletes both information about the backup, as well as all key data related to the backup #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/unstable/room_keys/version/<_>", data = "") @@ -122,7 +139,13 @@ pub async fn delete_backup_route( Ok(delete_backup::Response {}.into()) } +/// # `PUT /_matrix/client/r0/room_keys/keys` +/// /// Add the received backup keys to the database. +/// +/// - Only manipulating the most recently created version of the backup is allowed +/// - Adds the keys to the backup +/// - Returns the new number of keys in this backup and the etag #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/unstable/room_keys/keys", data = "") @@ -134,6 +157,18 @@ pub async fn add_backup_keys_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); + if Some(&body.version) + != db + .key_backups + .get_latest_backup_version(sender_user)? + .as_ref() + { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "You may only manipulate the most recently created version of the backup.", + )); + } + for (room_id, room) in &body.rooms { for (session_id, key_data) in &room.sessions { db.key_backups.add_key( @@ -156,7 +191,13 @@ pub async fn add_backup_keys_route( .into()) } +/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}` +/// /// Add the received backup keys to the database. +/// +/// - Only manipulating the most recently created version of the backup is allowed +/// - Adds the keys to the backup +/// - Returns the new number of keys in this backup and the etag #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/unstable/room_keys/keys/<_>", data = "") @@ -168,6 +209,18 @@ pub async fn add_backup_key_sessions_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); + if Some(&body.version) + != db + .key_backups + .get_latest_backup_version(sender_user)? + .as_ref() + { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "You may only manipulate the most recently created version of the backup.", + )); + } + for (session_id, key_data) in &body.sessions { db.key_backups.add_key( &sender_user, @@ -188,7 +241,13 @@ pub async fn add_backup_key_sessions_route( .into()) } +/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` +/// /// Add the received backup key to the database. +/// +/// - Only manipulating the most recently created version of the backup is allowed +/// - Adds the keys to the backup +/// - Returns the new number of keys in this backup and the etag #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "") @@ -200,6 +259,18 @@ pub async fn add_backup_key_session_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); + if Some(&body.version) + != db + .key_backups + .get_latest_backup_version(sender_user)? + .as_ref() + { + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "You may only manipulate the most recently created version of the backup.", + )); + } + db.key_backups.add_key( &sender_user, &body.version, @@ -218,6 +289,9 @@ pub async fn add_backup_key_session_route( .into()) } +/// # `GET /_matrix/client/r0/room_keys/keys` +/// +/// Retrieves all keys from the backup. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/unstable/room_keys/keys", data = "") @@ -234,6 +308,9 @@ pub async fn get_backup_keys_route( Ok(get_backup_keys::Response { rooms }.into()) } +/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}` +/// +/// Retrieves all keys from the backup for a given room. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/unstable/room_keys/keys/<_>", data = "") @@ -252,6 +329,9 @@ pub async fn get_backup_key_sessions_route( Ok(get_backup_key_sessions::Response { sessions }.into()) } +/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` +/// +/// Retrieves a key from the backup. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "") @@ -274,6 +354,9 @@ pub async fn get_backup_key_session_route( Ok(get_backup_key_session::Response { key_data }.into()) } +/// # `DELETE /_matrix/client/r0/room_keys/keys` +/// +/// Delete the keys from the backup. #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/unstable/room_keys/keys", data = "") @@ -297,6 +380,9 @@ pub async fn delete_backup_keys_route( .into()) } +/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}` +/// +/// Delete the keys from the backup for a given room. #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/unstable/room_keys/keys/<_>", data = "") @@ -320,6 +406,9 @@ pub async fn delete_backup_key_sessions_route( .into()) } +/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}` +/// +/// Delete a key from the backup. #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "") diff --git a/src/client_server/capabilities.rs b/src/client_server/capabilities.rs index 65c8879..2eacd8f 100644 --- a/src/client_server/capabilities.rs +++ b/src/client_server/capabilities.rs @@ -13,7 +13,7 @@ use rocket::get; /// # `GET /_matrix/client/r0/capabilities` /// -/// Get information on this server's supported feature set and other relevent capabilities. +/// Get information on the supported feature set and other relevent capabilities of this server. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/capabilities", data = "<_body>") diff --git a/src/client_server/config.rs b/src/client_server/config.rs index b692749..bd897ba 100644 --- a/src/client_server/config.rs +++ b/src/client_server/config.rs @@ -16,6 +16,9 @@ use serde_json::{json, value::RawValue as RawJsonValue}; #[cfg(feature = "conduit_bin")] use rocket::{get, put}; +/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}` +/// +/// Sets some account data for the sender user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/user/<_>/account_data/<_>", data = "") @@ -48,6 +51,9 @@ pub async fn set_global_account_data_route( Ok(set_global_account_data::Response {}.into()) } +/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}` +/// +/// Sets some room account data for the sender user. #[cfg_attr( feature = "conduit_bin", put( @@ -83,6 +89,9 @@ pub async fn set_room_account_data_route( Ok(set_room_account_data::Response {}.into()) } +/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}` +/// +/// Gets some account data for the sender user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "") @@ -98,7 +107,6 @@ pub async fn get_global_account_data_route( .account_data .get::>(None, sender_user, body.event_type.clone().into())? .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; - db.flush()?; let account_data = serde_json::from_str::(event.get()) .map_err(|_| Error::bad_database("Invalid account data event in db."))? @@ -107,6 +115,9 @@ pub async fn get_global_account_data_route( Ok(get_global_account_data::Response { account_data }.into()) } +/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}` +/// +/// Gets some room account data for the sender user. #[cfg_attr( feature = "conduit_bin", get( @@ -129,7 +140,6 @@ pub async fn get_room_account_data_route( body.event_type.clone().into(), )? .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; - db.flush()?; let account_data = serde_json::from_str::(event.get()) .map_err(|_| Error::bad_database("Invalid account data event in db."))? diff --git a/src/client_server/context.rs b/src/client_server/context.rs index 701e584..aaae8d6 100644 --- a/src/client_server/context.rs +++ b/src/client_server/context.rs @@ -5,6 +5,12 @@ use std::convert::TryFrom; #[cfg(feature = "conduit_bin")] use rocket::get; +/// # `GET /_matrix/client/r0/rooms/{roomId}/context` +/// +/// Allows loading room history around an event. +/// +/// - Only works if the user is joined (TODO: always allow, but only show events if the user was +/// joined, depending on history_visibility) #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/context/<_>", data = "") diff --git a/src/client_server/device.rs b/src/client_server/device.rs index 5210467..4aa3047 100644 --- a/src/client_server/device.rs +++ b/src/client_server/device.rs @@ -11,6 +11,9 @@ use super::SESSION_ID_LENGTH; #[cfg(feature = "conduit_bin")] use rocket::{delete, get, post, put}; +/// # `GET /_matrix/client/r0/devices` +/// +/// Get metadata on all devices of the sender user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/devices", data = "") @@ -31,6 +34,9 @@ pub async fn get_devices_route( Ok(get_devices::Response { devices }.into()) } +/// # `GET /_matrix/client/r0/devices/{deviceId}` +/// +/// Get metadata on a single device of the sender user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/devices/<_>", data = "") @@ -50,6 +56,9 @@ pub async fn get_device_route( Ok(get_device::Response { device }.into()) } +/// # `PUT /_matrix/client/r0/devices/{deviceId}` +/// +/// Updates the metadata on a given device of the sender user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/devices/<_>", data = "") @@ -76,6 +85,15 @@ pub async fn update_device_route( Ok(update_device::Response {}.into()) } +/// # `PUT /_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 #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/r0/devices/<_>", data = "") @@ -128,6 +146,17 @@ pub async fn delete_device_route( Ok(delete_device::Response {}.into()) } +/// # `PUT /_matrix/client/r0/devices/{deviceId}` +/// +/// Deletes the given device. +/// +/// - Requires UIAA to verify user password +/// +/// 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 #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/delete_devices", data = "") diff --git a/src/client_server/directory.rs b/src/client_server/directory.rs index 589aacd..5c93e22 100644 --- a/src/client_server/directory.rs +++ b/src/client_server/directory.rs @@ -28,6 +28,11 @@ use tracing::{info, warn}; #[cfg(feature = "conduit_bin")] use rocket::{get, post, put}; +/// # `POST /_matrix/client/r0/publicRooms` +/// +/// Lists the public rooms on this server. +/// +/// - Rooms are ordered by the number of joined members #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/publicRooms", data = "") @@ -48,6 +53,11 @@ pub async fn get_public_rooms_filtered_route( .await } +/// # `GET /_matrix/client/r0/publicRooms` +/// +/// Lists the public rooms on this server. +/// +/// - Rooms are ordered by the number of joined members #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/publicRooms", data = "") @@ -77,6 +87,11 @@ pub async fn get_public_rooms_route( .into()) } +/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}` +/// +/// Sets the visibility of a given room in the room directory. +/// +/// - TODO: Access control checks #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/directory/list/room/<_>", data = "") @@ -107,6 +122,9 @@ pub async fn set_room_visibility_route( Ok(set_room_visibility::Response {}.into()) } +/// # `GET /_matrix/client/r0/directory/list/room/{roomId}` +/// +/// Gets the visibility of a given room in the room directory. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/directory/list/room/<_>", data = "") @@ -126,7 +144,7 @@ pub async fn get_room_visibility_route( .into()) } -pub async fn get_public_rooms_filtered_helper( +pub(crate) async fn get_public_rooms_filtered_helper( db: &Database, server: Option<&ServerName>, limit: Option, diff --git a/src/client_server/filter.rs b/src/client_server/filter.rs index a08eb34..dfb5377 100644 --- a/src/client_server/filter.rs +++ b/src/client_server/filter.rs @@ -4,6 +4,9 @@ use ruma::api::client::r0::filter::{self, create_filter, get_filter}; #[cfg(feature = "conduit_bin")] use rocket::{get, post}; +/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}` +/// +/// TODO: Loads a filter that was previously created. #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/filter/<_>"))] #[tracing::instrument] pub async fn get_filter_route() -> ConduitResult { @@ -18,6 +21,9 @@ pub async fn get_filter_route() -> ConduitResult { .into()) } +/// # `PUT /_matrix/client/r0/user/{userId}/filter` +/// +/// TODO: Creates a new filter to be used by other endpoints. #[cfg_attr(feature = "conduit_bin", post("/_matrix/client/r0/user/<_>/filter"))] #[tracing::instrument] pub async fn create_filter_route() -> ConduitResult { diff --git a/src/client_server/keys.rs b/src/client_server/keys.rs index 0815737..3295e16 100644 --- a/src/client_server/keys.rs +++ b/src/client_server/keys.rs @@ -24,6 +24,12 @@ use std::collections::{BTreeMap, HashMap, HashSet}; #[cfg(feature = "conduit_bin")] use rocket::{get, post}; +/// # `POST /_matrix/client/r0/keys/upload` +/// +/// Publish end-to-end encryption keys for the sender device. +/// +/// - Adds one time keys +/// - If there are no device keys yet: Adds device keys (TODO: merge with existing keys?) #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/keys/upload", data = "") @@ -49,6 +55,7 @@ pub async fn upload_keys_route( } if let Some(device_keys) = &body.device_keys { + // TODO: merge this and the existing event? // This check is needed to assure that signatures are kept if db .users @@ -73,6 +80,13 @@ pub async fn upload_keys_route( .into()) } +/// # `POST /_matrix/client/r0/keys/query` +/// +/// Get end-to-end encryption keys for the given users. +/// +/// - Always fetches users from other servers over federation +/// - Gets master keys, self-signing keys, user signing keys and device keys. +/// - The master and self-signing keys contain signatures that the user is allowed to see #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/keys/query", data = "") @@ -95,6 +109,9 @@ pub async fn get_keys_route( Ok(response.into()) } +/// # `POST /_matrix/client/r0/keys/claim` +/// +/// Claims one-time keys #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/keys/claim", data = "") @@ -111,6 +128,11 @@ pub async fn claim_keys_route( Ok(response.into()) } +/// # `POST /_matrix/client/r0/keys/device_signing/upload` +/// +/// Uploads end-to-end key information for the sender user. +/// +/// - Requires UIAA to verify password #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/unstable/keys/device_signing/upload", data = "") @@ -172,6 +194,9 @@ pub async fn upload_signing_keys_route( Ok(upload_signing_keys::Response {}.into()) } +/// # `POST /_matrix/client/r0/keys/signatures/upload` +/// +/// Uploads end-to-end key signatures from the sender user. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/unstable/keys/signatures/upload", data = "") @@ -233,6 +258,11 @@ pub async fn upload_signatures_route( Ok(upload_signatures::Response {}.into()) } +/// # `POST /_matrix/client/r0/keys/changes` +/// +/// Gets a list of users who have updated their device identity keys since the previous sync token. +/// +/// - TODO: left users #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/keys/changes", data = "") @@ -284,7 +314,7 @@ pub async fn get_key_changes_route( .into()) } -pub async fn get_keys_helper bool>( +pub(crate) async fn get_keys_helper bool>( sender_user: Option<&UserId>, device_keys_input: &BTreeMap>>, allowed_signatures: F, @@ -409,7 +439,7 @@ pub async fn get_keys_helper bool>( }) } -pub async fn claim_keys_helper( +pub(crate) async fn claim_keys_helper( one_time_keys_input: &BTreeMap, DeviceKeyAlgorithm>>, db: &Database, ) -> Result { diff --git a/src/client_server/media.rs b/src/client_server/media.rs index 2bd189a..4cec0af 100644 --- a/src/client_server/media.rs +++ b/src/client_server/media.rs @@ -12,6 +12,9 @@ use rocket::{get, post}; const MXC_LENGTH: usize = 32; +/// # `GET /_matrix/media/r0/config` +/// +/// Returns max upload size. #[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))] #[tracing::instrument(skip(db))] pub async fn get_media_config_route( @@ -23,6 +26,12 @@ pub async fn get_media_config_route( .into()) } +/// # `POST /_matrix/media/r0/upload` +/// +/// Permanently save media in the server. +/// +/// - Some metadata will be saved in the database +/// - Media will be saved in the media/ directory #[cfg_attr( feature = "conduit_bin", post("/_matrix/media/r0/upload", data = "") @@ -61,6 +70,11 @@ pub async fn create_content_route( .into()) } +/// # `POST /_matrix/media/r0/download/{serverName}/{mediaId}` +/// +/// Load media from our server or over federation. +/// +/// - Only allows federation if `allow_remote` is true #[cfg_attr( feature = "conduit_bin", get("/_matrix/media/r0/download/<_>/<_>", data = "") @@ -114,6 +128,11 @@ pub async fn get_content_route( } } +/// # `POST /_matrix/media/r0/thumbnail/{serverName}/{mediaId}` +/// +/// Load media thumbnail from our server or over federation. +/// +/// - Only allows federation if `allow_remote` is true #[cfg_attr( feature = "conduit_bin", get("/_matrix/media/r0/thumbnail/<_>/<_>", data = "") diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs index 0a7ca81..c88e0a8 100644 --- a/src/client_server/membership.rs +++ b/src/client_server/membership.rs @@ -38,6 +38,12 @@ use tracing::{debug, error, warn}; #[cfg(feature = "conduit_bin")] use rocket::{get, post}; +/// # `POST /_matrix/client/r0/rooms/{roomId}/join` +/// +/// Tries to join the sender user into a room. +/// +/// - If the server knowns about this room: creates the join event and does auth rules locally +/// - If the server does not know about the room: asks other servers over federation #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/join", data = "") @@ -79,6 +85,12 @@ pub async fn join_room_by_id_route( ret } +/// # `POST /_matrix/client/r0/join/{roomIdOrAlias}` +/// +/// Tries to join the sender user into a room. +/// +/// - If the server knowns about this room: creates the join event and does auth rules locally +/// - If the server does not know about the room: asks other servers over federation #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/join/<_>", data = "") @@ -133,6 +145,11 @@ pub async fn join_room_by_id_or_alias_route( .into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/leave` +/// +/// Tries to leave the sender user from a room. +/// +/// - This should always work if the user is currently joined. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/leave", data = "") @@ -151,6 +168,9 @@ pub async fn leave_room_route( Ok(leave_room::Response::new().into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/invite` +/// +/// Tries to send an invite event into the room. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/invite", data = "") @@ -171,6 +191,9 @@ pub async fn invite_user_route( } } +/// # `POST /_matrix/client/r0/rooms/{roomId}/kick` +/// +/// Tries to send a kick event into the room. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/kick", data = "") @@ -234,6 +257,9 @@ pub async fn kick_user_route( Ok(kick_user::Response::new().into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/ban` +/// +/// Tries to send a ban event into the room. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/ban", data = "") @@ -307,6 +333,9 @@ pub async fn ban_user_route( Ok(ban_user::Response::new().into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/unban` +/// +/// Tries to send an unban event into the room. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/unban", data = "") @@ -369,6 +398,14 @@ pub async fn unban_user_route( Ok(unban_user::Response::new().into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/forget` +/// +/// Forgets about a room. +/// +/// - If the sender user currently left the room: Stops sender user from receiving information about the room +/// +/// Note: Other devices of the user have no way of knowing the room was forgotten, so this has to +/// be called from every device #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/forget", data = "") @@ -387,6 +424,9 @@ pub async fn forget_room_route( Ok(forget_room::Response::new().into()) } +/// # `POST /_matrix/client/r0/joined_rooms` +/// +/// Lists all rooms the user has joined. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/joined_rooms", data = "") @@ -408,6 +448,11 @@ pub async fn joined_rooms_route( .into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/members` +/// +/// Lists all joined users in a room (TODO: at a specific point in time, with a specific membership). +/// +/// - Only works if the user is currently joined #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/members", data = "") @@ -419,6 +464,7 @@ pub async fn get_member_events_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); + // TODO: check history visibility? if !db.rooms.is_joined(sender_user, &body.room_id)? { return Err(Error::BadRequest( ErrorKind::Forbidden, @@ -438,6 +484,12 @@ pub async fn get_member_events_route( .into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/joined_members` +/// +/// Lists all members of a room. +/// +/// - The sender user must be in the room +/// - TODO: An appservice just needs a puppet joined #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/joined_members", data = "") @@ -449,11 +501,7 @@ pub async fn joined_members_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - if !db - .rooms - .is_joined(&sender_user, &body.room_id) - .unwrap_or(false) - { + if !db.rooms.is_joined(&sender_user, &body.room_id)? { return Err(Error::BadRequest( ErrorKind::Forbidden, "You aren't a member of the room.", @@ -803,7 +851,7 @@ async fn validate_and_add_event_id( Ok((event_id, value)) } -pub async fn invite_helper<'a>( +pub(crate) async fn invite_helper<'a>( sender_user: &UserId, user_id: &UserId, room_id: &RoomId, diff --git a/src/client_server/message.rs b/src/client_server/message.rs index 70cc00f..78008a5 100644 --- a/src/client_server/message.rs +++ b/src/client_server/message.rs @@ -16,6 +16,13 @@ use std::{ #[cfg(feature = "conduit_bin")] use rocket::{get, put}; +/// # `PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}` +/// +/// Send a message event into the room. +/// +/// - Is a NOOP if the txn id was already used before and returns the same event id again +/// - The only requirement for the content is that it has to be valid json +/// - Tries to send the event into the room, auth rules will determine if it is allowed #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/rooms/<_>/send/<_>/<_>", data = "") @@ -92,6 +99,12 @@ pub async fn send_message_event_route( Ok(send_message_event::Response::new(event_id).into()) } +/// # `GET /_matrix/client/r0/rooms/{roomId}/messages` +/// +/// Allows paginating through room history. +/// +/// - Only works if the user is joined (TODO: always allow, but only show events where the user was +/// joined, depending on history_visibility) #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/messages", data = "") diff --git a/src/client_server/mod.rs b/src/client_server/mod.rs index 040015d..e0c340f 100644 --- a/src/client_server/mod.rs +++ b/src/client_server/mod.rs @@ -71,6 +71,9 @@ pub const DEVICE_ID_LENGTH: usize = 10; pub const TOKEN_LENGTH: usize = 256; pub const SESSION_ID_LENGTH: usize = 256; +/// # `OPTIONS` +/// +/// Web clients use this to get CORS headers. #[cfg(feature = "conduit_bin")] #[options("/<_..>")] #[tracing::instrument] diff --git a/src/client_server/presence.rs b/src/client_server/presence.rs index 7312cb3..54eb210 100644 --- a/src/client_server/presence.rs +++ b/src/client_server/presence.rs @@ -5,6 +5,9 @@ use std::{convert::TryInto, time::Duration}; #[cfg(feature = "conduit_bin")] use rocket::{get, put}; +/// # `PUT /_matrix/client/r0/presence/{userId}/status` +/// +/// Sets the presence state of the sender user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/presence/<_>/status", data = "") @@ -46,6 +49,11 @@ pub async fn set_presence_route( Ok(set_presence::Response {}.into()) } +/// # `GET /_matrix/client/r0/presence/{userId}/status` +/// +/// Gets the presence state of the given user. +/// +/// - Only works if you share a room with the user #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/presence/<_>/status", data = "") @@ -71,6 +79,7 @@ pub async fn get_presence_route( .get_last_presence_event(&sender_user, &room_id)? { presence_event = Some(presence); + break; } } diff --git a/src/client_server/profile.rs b/src/client_server/profile.rs index de1baba..e2a2d6c 100644 --- a/src/client_server/profile.rs +++ b/src/client_server/profile.rs @@ -17,6 +17,11 @@ use std::{convert::TryInto, sync::Arc}; #[cfg(feature = "conduit_bin")] use rocket::{get, put}; +/// # `PUT /_matrix/client/r0/profile/{userId}/displayname` +/// +/// Updates the displayname. +/// +/// - Also makes sure other users receive the update using presence EDUs #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/profile/<_>/displayname", data = "") @@ -115,6 +120,11 @@ pub async fn set_displayname_route( Ok(set_display_name::Response {}.into()) } +/// # `GET /_matrix/client/r0/profile/{userId}/displayname` +/// +/// Returns the displayname of the user. +/// +/// - If user is on another server: Fetches displayname over federation #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/profile/<_>/displayname", data = "") @@ -149,6 +159,11 @@ pub async fn get_displayname_route( .into()) } +/// # `PUT /_matrix/client/r0/profile/{userId}/avatar_url` +/// +/// Updates the avatar_url and blurhash. +/// +/// - Also makes sure other users receive the update using presence EDUs #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/profile/<_>/avatar_url", data = "") @@ -249,6 +264,11 @@ pub async fn set_avatar_url_route( Ok(set_avatar_url::Response {}.into()) } +/// # `GET /_matrix/client/r0/profile/{userId}/avatar_url` +/// +/// Returns the avatar_url and blurhash of the user. +/// +/// - If user is on another server: Fetches avatar_url and blurhash over federation #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/profile/<_>/avatar_url", data = "") @@ -285,6 +305,11 @@ pub async fn get_avatar_url_route( .into()) } +/// # `GET /_matrix/client/r0/profile/{userId}` +/// +/// Returns the displayname, avatar_url and blurhash of the user. +/// +/// - If user is on another server: Fetches profile over federation #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/profile/<_>", data = "") diff --git a/src/client_server/push.rs b/src/client_server/push.rs index 9489f07..4e4611b 100644 --- a/src/client_server/push.rs +++ b/src/client_server/push.rs @@ -15,6 +15,9 @@ use ruma::{ #[cfg(feature = "conduit_bin")] use rocket::{delete, get, post, put}; +/// # `GET /_matrix/client/r0/pushrules` +/// +/// Retrieves the push rules event for this user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/pushrules", data = "") @@ -40,6 +43,9 @@ pub async fn get_pushrules_all_route( .into()) } +/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}` +/// +/// Retrieves a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "") @@ -94,6 +100,9 @@ pub async fn get_pushrule_route( } } +/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}` +/// +/// Creates a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "") @@ -197,6 +206,9 @@ pub async fn set_pushrule_route( Ok(set_pushrule::Response {}.into()) } +/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions` +/// +/// Gets the actions of a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "") @@ -256,6 +268,9 @@ pub async fn get_pushrule_actions_route( .into()) } +/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions` +/// +/// Sets the actions of a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "") @@ -330,6 +345,9 @@ pub async fn set_pushrule_actions_route( Ok(set_pushrule_actions::Response {}.into()) } +/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled` +/// +/// Gets the enabled status of a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "") @@ -391,6 +409,9 @@ pub async fn get_pushrule_enabled_route( Ok(get_pushrule_enabled::Response { enabled }.into()) } +/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled` +/// +/// Sets the enabled status of a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "") @@ -470,6 +491,9 @@ pub async fn set_pushrule_enabled_route( Ok(set_pushrule_enabled::Response {}.into()) } +/// # `DELETE /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}` +/// +/// Deletes a single specified push rule for this user. #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "") @@ -539,6 +563,9 @@ pub async fn delete_pushrule_route( Ok(delete_pushrule::Response {}.into()) } +/// # `GET /_matrix/client/r0/pushers` +/// +/// Gets all currently active pushers for the sender user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/pushers", data = "") @@ -556,6 +583,11 @@ pub async fn get_pushers_route( .into()) } +/// # `POST /_matrix/client/r0/pushers/set` +/// +/// Adds a pusher for the sender user. +/// +/// - TODO: Handle `append` #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/pushers/set", data = "") diff --git a/src/client_server/read_marker.rs b/src/client_server/read_marker.rs index 85b0bf6..10298b9 100644 --- a/src/client_server/read_marker.rs +++ b/src/client_server/read_marker.rs @@ -13,6 +13,12 @@ use std::collections::BTreeMap; #[cfg(feature = "conduit_bin")] use rocket::post; +/// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers` +/// +/// Sets different types of read markers. +/// +/// - Updates fully-read account data event to `fully_read` +/// - If `read_receipt` is set: Update private marker and public read receipt EDU #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/read_markers", data = "") @@ -80,6 +86,9 @@ pub async fn set_read_marker_route( Ok(set_read_marker::Response {}.into()) } +/// # `POST /_matrix/client/r0/rooms/{roomId}/receipt/{receiptType}/{eventId}` +/// +/// Sets private read marker and public read receipt EDU. #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/receipt/<_>/<_>", data = "") diff --git a/src/client_server/redact.rs b/src/client_server/redact.rs index 63bf103..6d3e33c 100644 --- a/src/client_server/redact.rs +++ b/src/client_server/redact.rs @@ -9,6 +9,11 @@ use ruma::{ #[cfg(feature = "conduit_bin")] use rocket::put; +/// # `PUT /_matrix/client/r0/rooms/{roomId}/redact/{eventId}/{txnId}` +/// +/// Tries to send a redaction event into the room. +/// +/// - TODO: Handle txn id #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/rooms/<_>/redact/<_>/<_>", data = "") diff --git a/src/client_server/room.rs b/src/client_server/room.rs index 6981afc..4ae8a3f 100644 --- a/src/client_server/room.rs +++ b/src/client_server/room.rs @@ -20,6 +20,22 @@ use tracing::{info, warn}; #[cfg(feature = "conduit_bin")] use rocket::{get, post}; +/// # `POST /_matrix/client/r0/createRoom` +/// +/// Creates a new room. +/// +/// - Room ID is randomly generated +/// - Create alias if room_alias_name is set +/// - Send create event +/// - Join sender user +/// - Send power levels event +/// - Send canonical room alias +/// - Send join rules +/// - Send history visibility +/// - Send guest access +/// - Send events listed in initial state +/// - Send events implied by `name` and `topic` +/// - Send invite events #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/createRoom", data = "") @@ -344,6 +360,11 @@ pub async fn create_room_route( Ok(create_room::Response::new(room_id).into()) } +/// # `GET /_matrix/client/r0/rooms/{roomId}/event/{eventId}` +/// +/// Gets a single event. +/// +/// - You have to currently be joined to the room (TODO: Respect history visibility) #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/event/<_>", data = "") @@ -372,6 +393,11 @@ pub async fn get_room_event_route( .into()) } +/// # `GET /_matrix/client/r0/rooms/{roomId}/aliases` +/// +/// Lists all aliases of the room. +/// +/// - Only users joined to the room are allowed to call this TODO: Allow any user to call it if history_visibility is world readable #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/aliases", data = "") @@ -400,6 +426,16 @@ pub async fn get_room_aliases_route( .into()) } +/// # `GET /_matrix/client/r0/rooms/{roomId}/upgrade` +/// +/// Upgrades the room. +/// +/// - Creates a replacement room +/// - Sends a tombstone event into the current room +/// - Sender user joins the room +/// - Transfers some state events +/// - Moves local aliases +/// - Modifies old room power levels to prevent users from speaking #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/rooms/<_>/upgrade", data = "") diff --git a/src/client_server/search.rs b/src/client_server/search.rs index ec23dd4..cbd4ed7 100644 --- a/src/client_server/search.rs +++ b/src/client_server/search.rs @@ -6,6 +6,11 @@ use rocket::post; use search_events::{EventContextResult, ResultCategories, ResultRoomEvents, SearchResult}; use std::collections::BTreeMap; +/// # `POST /_matrix/client/r0/search` +/// +/// Searches rooms for messages. +/// +/// - Only works if the user is currently joined to the room (TODO: Respect history visibility) #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/search", data = "") diff --git a/src/client_server/session.rs b/src/client_server/session.rs index dada2d5..9472627 100644 --- a/src/client_server/session.rs +++ b/src/client_server/session.rs @@ -24,7 +24,7 @@ use rocket::{get, post}; /// # `GET /_matrix/client/r0/login` /// -/// Get the homeserver's supported login types. One of these should be used as the `type` field +/// Get the supported login types of this server. One of these should be used as the `type` field /// when logging in. #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))] #[tracing::instrument] @@ -41,9 +41,10 @@ pub async fn get_login_types_route() -> ConduitResult /// /// Authenticates the user and returns an access token it can use in subsequent requests. /// -/// - The returned access token is associated with the user and device -/// - Old access tokens of that device should be invalidated -/// - If `device_id` is unknown, a new device will be created +/// - The user needs to authenticate using their password (or if enabled using a json web token) +/// - If `device_id` is known: invalidates old access token of that device +/// - If `device_id` is unknown: creates a new device +/// - Returns access token that is associated with the user and device /// /// Note: You can use [`GET /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see /// supported login types. @@ -162,8 +163,10 @@ pub async fn login_route( /// /// Log out the current device. /// -/// - Invalidates the access token -/// - Deletes the device and most of it's data (to-device events, last seen, etc.) +/// - 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 #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/logout", data = "") @@ -188,7 +191,9 @@ pub async fn logout_route( /// Log out all devices of this user. /// /// - Invalidates all access tokens -/// - Deletes devices and most of their data (to-device events, last seen, etc.) +/// - Deletes all device metadata (device id, device display name, last seen ip, last seen ts) +/// - Forgets all to-device events +/// - Triggers device list updates /// /// Note: This is equivalent to calling [`GET /_matrix/client/r0/logout`](fn.logout_route.html) /// from each device of this user. diff --git a/src/client_server/state.rs b/src/client_server/state.rs index aa020b5..3555353 100644 --- a/src/client_server/state.rs +++ b/src/client_server/state.rs @@ -22,6 +22,13 @@ use ruma::{ #[cfg(feature = "conduit_bin")] use rocket::{get, put}; +/// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}/{stateKey}` +/// +/// Sends a state event into the room. +/// +/// - The only requirement for the content is that it has to be valid json +/// - Tries to send the event into the room, auth rules will determine if it is allowed +/// - If event is new canonical_alias: Rejects if alias is incorrect #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "") @@ -48,6 +55,13 @@ pub async fn send_state_event_for_key_route( Ok(send_state_event::Response { event_id }.into()) } +/// # `PUT /_matrix/client/r0/rooms/{roomId}/state/{eventType}` +/// +/// Sends a state event into the room. +/// +/// - The only requirement for the content is that it has to be valid json +/// - Tries to send the event into the room, auth rules will determine if it is allowed +/// - If event is new canonical_alias: Rejects if alias is incorrect #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/rooms/<_>/state/<_>", data = "") @@ -74,6 +88,11 @@ pub async fn send_state_event_for_empty_key_route( Ok(send_state_event::Response { event_id }.into()) } +/// # `GET /_matrix/client/r0/rooms/{roomid}/state` +/// +/// Get all state events for a room. +/// +/// - If not joined: Only works if current room history visibility is world readable #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/state", data = "") @@ -121,6 +140,11 @@ pub async fn get_state_events_route( .into()) } +/// # `GET /_matrix/client/r0/rooms/{roomid}/state/{eventType}/{stateKey}` +/// +/// Get single state event of a room. +/// +/// - If not joined: Only works if current room history visibility is world readable #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "") @@ -172,6 +196,11 @@ pub async fn get_state_events_for_key_route( .into()) } +/// # `GET /_matrix/client/r0/rooms/{roomid}/state/{eventType}` +/// +/// Get single state event of a room. +/// +/// - If not joined: Only works if current room history visibility is world readable #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/rooms/<_>/state/<_>", data = "") @@ -223,7 +252,7 @@ pub async fn get_state_events_for_empty_key_route( .into()) } -pub async fn send_state_event_for_key_helper( +async fn send_state_event_for_key_helper( db: &Database, sender: &UserId, room_id: &RoomId, @@ -233,6 +262,8 @@ pub async fn send_state_event_for_key_helper( ) -> Result { let sender_user = sender; + // TODO: Review this check, error if event is unparsable, use event type, allow alias if it + // previously existed if let Ok(canonical_alias) = serde_json::from_str::(json.json().get()) { diff --git a/src/client_server/sync.rs b/src/client_server/sync.rs index f7f2454..6612e2f 100644 --- a/src/client_server/sync.rs +++ b/src/client_server/sync.rs @@ -22,12 +22,33 @@ use rocket::{get, tokio}; /// Synchronize the client's state with the latest state on the server. /// /// - This endpoint takes a `since` parameter which should be the `next_batch` value from a -/// previous request. -/// - Calling this endpoint without a `since` parameter will return all recent events, the state -/// of all rooms and more data. This should only be called on the initial login of the device. -/// - To get incremental updates, you can call this endpoint with a `since` parameter. This will -/// return all recent events, state updates and more data that happened since the last /sync -/// request. +/// previous request for incremental syncs. +/// +/// Calling this endpoint without a `since` parameter returns: +/// - Some of the most recent events of each timeline +/// - Notification counts for each room +/// - Joined and invited member counts, heroes +/// - All state events +/// +/// Calling this endpoint with a `since` parameter from a previous `next_batch` returns: +/// For joined rooms: +/// - Some of the most recent events of each timeline that happened after since +/// - If user joined the room after since: All state events and device list updates in that room +/// - If the user was already in the room: A list of all events that are in the state now, but were +/// not in the state at `since` +/// - If the state we send contains a member event: Joined and invited member counts, heroes +/// - Device list updates that happened after `since` +/// - If there are events in the timeline we send or the user send updated his read mark: Notification counts +/// - EDUs that are active now (read receipts, typing updates, presence) +/// +/// For invited rooms: +/// - If the user was invited after `since`: A subset of the state of the room at the point of the invite +/// +/// For left rooms: +/// - If the user left after `since`: prev_batch token, empty state (TODO: subset of the state at the point of the leave) +/// +/// - Sync is handled in an async task, multiple requests from the same device with the same +/// `since` will be cached #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/sync", data = "") @@ -106,7 +127,7 @@ pub async fn sync_events_route( result } -pub async fn sync_helper_wrapper( +async fn sync_helper_wrapper( db: Arc, sender_user: UserId, sender_device: Box, diff --git a/src/client_server/tag.rs b/src/client_server/tag.rs index 5582bcd..1eb508c 100644 --- a/src/client_server/tag.rs +++ b/src/client_server/tag.rs @@ -8,6 +8,11 @@ use std::collections::BTreeMap; #[cfg(feature = "conduit_bin")] use rocket::{delete, get, put}; +/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}` +/// +/// Adds a tag to the room. +/// +/// - Inserts the tag into the tag event of the room account data. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "") @@ -45,6 +50,11 @@ pub async fn update_tag_route( Ok(create_tag::Response {}.into()) } +/// # `DELETE /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags/{tag}` +/// +/// Deletes a tag from the room. +/// +/// - Removes the tag from the tag event of the room account data. #[cfg_attr( feature = "conduit_bin", delete("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "") @@ -79,6 +89,11 @@ pub async fn delete_tag_route( Ok(delete_tag::Response {}.into()) } +/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/tags` +/// +/// Returns tags on the room. +/// +/// - Gets the tag event of the room account data. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/rooms/<_>/tags", data = "") diff --git a/src/client_server/thirdparty.rs b/src/client_server/thirdparty.rs index 5d3c540..4305902 100644 --- a/src/client_server/thirdparty.rs +++ b/src/client_server/thirdparty.rs @@ -5,6 +5,9 @@ use ruma::api::client::r0::thirdparty::get_protocols; use rocket::get; use std::collections::BTreeMap; +/// # `GET /_matrix/client/r0/thirdparty/protocols` +/// +/// TODO: Fetches all metadata about protocols supported by the homeserver. #[cfg_attr( feature = "conduit_bin", get("/_matrix/client/r0/thirdparty/protocols") diff --git a/src/client_server/to_device.rs b/src/client_server/to_device.rs index cd770bd..bf2caef 100644 --- a/src/client_server/to_device.rs +++ b/src/client_server/to_device.rs @@ -13,6 +13,9 @@ use ruma::{ #[cfg(feature = "conduit_bin")] use rocket::put; +/// # `PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}` +/// +/// Send a to-device event to a set of client devices. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/sendToDevice/<_>/<_>", data = "") diff --git a/src/client_server/typing.rs b/src/client_server/typing.rs index 50082ee..4cf4bb1 100644 --- a/src/client_server/typing.rs +++ b/src/client_server/typing.rs @@ -5,6 +5,9 @@ use ruma::api::client::r0::typing::create_typing_event; #[cfg(feature = "conduit_bin")] use rocket::put; +/// # `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}` +/// +/// Sets the typing state of the sender user. #[cfg_attr( feature = "conduit_bin", put("/_matrix/client/r0/rooms/<_>/typing/<_>", data = "") diff --git a/src/client_server/unversioned.rs b/src/client_server/unversioned.rs index d25dce6..f2624bb 100644 --- a/src/client_server/unversioned.rs +++ b/src/client_server/unversioned.rs @@ -10,7 +10,7 @@ use rocket::get; /// /// - Versions take the form MAJOR.MINOR.PATCH /// - Only the latest PATCH release will be reported for each MAJOR.MINOR value -/// - Unstable features should be namespaced and may include version information in their name +/// - Unstable features are namespaced and may include version information in their name /// /// Note: Unstable features are used while developing new features. Clients should avoid using /// unstable features in their stable releases diff --git a/src/client_server/user_directory.rs b/src/client_server/user_directory.rs index a09d527..cfcb9bb 100644 --- a/src/client_server/user_directory.rs +++ b/src/client_server/user_directory.rs @@ -4,6 +4,11 @@ use ruma::api::client::r0::user_directory::search_users; #[cfg(feature = "conduit_bin")] use rocket::post; +/// # `POST /_matrix/client/r0/user_directory/search` +/// +/// Searches all known users for a match. +/// +/// - TODO: Hide users that are not in any public rooms? #[cfg_attr( feature = "conduit_bin", post("/_matrix/client/r0/user_directory/search", data = "") diff --git a/src/client_server/voip.rs b/src/client_server/voip.rs index 7924a7f..2a7f28e 100644 --- a/src/client_server/voip.rs +++ b/src/client_server/voip.rs @@ -5,6 +5,9 @@ use std::time::Duration; #[cfg(feature = "conduit_bin")] use rocket::get; +/// # `GET /_matrix/client/r0/voip/turnServer` +/// +/// TODO: Returns information about the recommended turn server. #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/voip/turnServer"))] #[tracing::instrument] pub async fn turn_server_route() -> ConduitResult { diff --git a/src/database/key_backups.rs b/src/database/key_backups.rs index 2bb3b6d..3315be3 100644 --- a/src/database/key_backups.rs +++ b/src/database/key_backups.rs @@ -84,6 +84,27 @@ impl KeyBackups { Ok(version.to_string()) } + pub fn get_latest_backup_version(&self, user_id: &UserId) -> Result> { + let mut prefix = user_id.as_bytes().to_vec(); + prefix.push(0xff); + let mut last_possible_key = prefix.clone(); + last_possible_key.extend_from_slice(&u64::MAX.to_be_bytes()); + + self.backupid_algorithm + .iter_from(&last_possible_key, true) + .take_while(move |(k, _)| k.starts_with(&prefix)) + .next() + .map_or(Ok(None), |(key, _)| { + utils::string_from_bytes( + key.rsplit(|&b| b == 0xff) + .next() + .expect("rsplit always returns an element"), + ) + .map_err(|_| Error::bad_database("backupid_algorithm key is invalid.")) + .map(Some) + }) + } + pub fn get_latest_backup(&self, user_id: &UserId) -> Result> { let mut prefix = user_id.as_bytes().to_vec(); prefix.push(0xff); diff --git a/src/database/rooms.rs b/src/database/rooms.rs index 59ed950..4b47454 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -1529,19 +1529,35 @@ impl Rooms { "get_auth_chain" => { if args.len() == 1 { if let Ok(event_id) = EventId::try_from(args[0]) { - let start = Instant::now(); - let count = server_server::get_auth_chain( - vec![Arc::new(event_id)], - db, - )? - .count(); - let elapsed = start.elapsed(); - db.admin.send(AdminCommand::SendMessage( - message::MessageEventContent::text_plain(format!( + if let Some(event) = db.rooms.get_pdu_json(&event_id)? { + let room_id_str = event + .get("room_id") + .and_then(|val| val.as_str()) + .ok_or_else(|| { + Error::bad_database( + "Invalid event in database", + ) + })?; + + let room_id = RoomId::try_from(room_id_str) + .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; + let start = Instant::now(); + let count = server_server::get_auth_chain( + &room_id, + vec![Arc::new(event_id)], + db, + )? + .count(); + let elapsed = start.elapsed(); + db.admin.send(AdminCommand::SendMessage( + message::MessageEventContent::text_plain( + format!( "Loaded auth chain with length {} in {:?}", count, elapsed - )), - )); + ), + ), + )); + } } } } @@ -3083,6 +3099,15 @@ impl Rooms { }) } + #[tracing::instrument(skip(self))] + pub fn server_in_room<'a>(&'a self, server: &ServerName, room_id: &RoomId) -> Result { + let mut key = server.as_bytes().to_vec(); + key.push(0xff); + key.extend_from_slice(room_id.as_bytes()); + + self.serverroomids.get(&key).map(|o| o.is_some()) + } + /// Returns an iterator of all rooms a server participates in (as far as we know). #[tracing::instrument(skip(self))] pub fn server_rooms<'a>( diff --git a/src/server_server.rs b/src/server_server.rs index b965fcf..dee92e8 100644 --- a/src/server_server.rs +++ b/src/server_server.rs @@ -119,7 +119,7 @@ impl FedDest { } #[tracing::instrument(skip(globals, request))] -pub async fn send_request( +pub(crate) async fn send_request( globals: &crate::database::globals::Globals, destination: &ServerName, request: T, @@ -487,7 +487,7 @@ async fn query_srv_record( } #[tracing::instrument(skip(globals))] -pub async fn request_well_known( +async fn request_well_known( globals: &crate::database::globals::Globals, destination: &str, ) -> Option { @@ -512,6 +512,9 @@ pub async fn request_well_known( Some(body.get("m.server")?.as_str()?.to_owned()) } +/// # `GET /_matrix/federation/v1/version` +/// +/// Get version information on this server. #[cfg_attr(feature = "conduit_bin", get("/_matrix/federation/v1/version"))] #[tracing::instrument(skip(db))] pub fn get_server_version_route( @@ -530,6 +533,12 @@ pub fn get_server_version_route( .into()) } +/// # `GET /_matrix/key/v2/server` +/// +/// Gets the public signing keys of this server. +/// +/// - Matrix does not support invalidating public keys, so the key returned by this will be valid +/// forever. // Response type for this endpoint is Json because we need to calculate a signature for the response #[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server"))] #[tracing::instrument(skip(db))] @@ -578,12 +587,21 @@ pub fn get_server_keys_route(db: DatabaseGuard) -> Json { Json(serde_json::to_string(&response).expect("JSON is canonical")) } +/// # `GET /_matrix/key/v2/server/{keyId}` +/// +/// Gets the public signing keys of this server. +/// +/// - Matrix does not support invalidating public keys, so the key returned by this will be valid +/// forever. #[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server/<_>"))] #[tracing::instrument(skip(db))] pub fn get_server_keys_deprecated_route(db: DatabaseGuard) -> Json { get_server_keys_route(db) } +/// # `POST /_matrix/federation/v1/publicRooms` +/// +/// Lists the public rooms on this server. #[cfg_attr( feature = "conduit_bin", post("/_matrix/federation/v1/publicRooms", data = "") @@ -628,6 +646,9 @@ pub async fn get_public_rooms_filtered_route( .into()) } +/// # `GET /_matrix/federation/v1/publicRooms` +/// +/// Lists the public rooms on this server. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/publicRooms", data = "") @@ -672,6 +693,9 @@ pub async fn get_public_rooms_route( .into()) } +/// # `PUT /_matrix/federation/v1/send/{txnId}` +/// +/// Push EDUs and PDUs to this server. #[cfg_attr( feature = "conduit_bin", put("/_matrix/federation/v1/send/<_>", data = "") @@ -921,7 +945,7 @@ type AsyncRecursiveType<'a, T> = Pin + 'a + Send>>; /// 14. Use state resolution to find new room state // We use some AsyncRecursiveType hacks here so we can call this async funtion recursively #[tracing::instrument(skip(value, is_timeline_event, db, pub_key_map))] -pub async fn handle_incoming_pdu<'a>( +pub(crate) async fn handle_incoming_pdu<'a>( origin: &'a ServerName, event_id: &'a EventId, room_id: &'a RoomId, @@ -1397,9 +1421,13 @@ async fn upgrade_outlier_to_timeline_pdu( let mut auth_chain_sets = Vec::new(); for state in fork_states { auth_chain_sets.push( - get_auth_chain(state.iter().map(|(_, id)| id.clone()).collect(), db) - .map_err(|_| "Failed to load auth chain.".to_owned())? - .collect(), + get_auth_chain( + &room_id, + state.iter().map(|(_, id)| id.clone()).collect(), + db, + ) + .map_err(|_| "Failed to load auth chain.".to_owned())? + .collect(), ); } @@ -1745,9 +1773,13 @@ async fn upgrade_outlier_to_timeline_pdu( let mut auth_chain_sets = Vec::new(); for state in fork_states { auth_chain_sets.push( - get_auth_chain(state.iter().map(|(_, id)| id.clone()).collect(), db) - .map_err(|_| "Failed to load auth chain.".to_owned())? - .collect(), + get_auth_chain( + &room_id, + state.iter().map(|(_, id)| id.clone()).collect(), + db, + ) + .map_err(|_| "Failed to load auth chain.".to_owned())? + .collect(), ); } @@ -2187,10 +2219,11 @@ fn append_incoming_pdu( } #[tracing::instrument(skip(starting_events, db))] -pub fn get_auth_chain( +pub(crate) fn get_auth_chain<'a>( + room_id: &RoomId, starting_events: Vec>, - db: &Database, -) -> Result> + '_> { + db: &'a Database, +) -> Result> + 'a> { const NUM_BUCKETS: usize = 50; let mut buckets = vec![BTreeSet::new(); NUM_BUCKETS]; @@ -2231,7 +2264,7 @@ pub fn get_auth_chain( chunk_cache.extend(cached.iter().cloned()); } else { misses2 += 1; - let auth_chain = Arc::new(get_auth_chain_inner(&event_id, db)?); + let auth_chain = Arc::new(get_auth_chain_inner(&room_id, &event_id, db)?); db.rooms .cache_auth_chain(vec![sevent_id], Arc::clone(&auth_chain))?; println!( @@ -2267,13 +2300,20 @@ pub fn get_auth_chain( } #[tracing::instrument(skip(event_id, db))] -fn get_auth_chain_inner(event_id: &EventId, db: &Database) -> Result> { +fn get_auth_chain_inner( + room_id: &RoomId, + event_id: &EventId, + db: &Database, +) -> Result> { let mut todo = vec![event_id.clone()]; let mut found = HashSet::new(); while let Some(event_id) = todo.pop() { match db.rooms.get_pdu(&event_id) { Ok(Some(pdu)) => { + if &pdu.room_id != room_id { + return Err(Error::BadRequest(ErrorKind::Forbidden, "Evil event in db")); + } for auth_event in &pdu.auth_events { let sauthevent = db .rooms @@ -2297,6 +2337,11 @@ fn get_auth_chain_inner(event_id: &EventId, db: &Database) -> Result", data = "") @@ -2310,18 +2355,39 @@ pub fn get_event_route( return Err(Error::bad_config("Federation is disabled.")); } + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + let event = db + .rooms + .get_pdu_json(&body.event_id)? + .ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?; + + let room_id_str = event + .get("room_id") + .and_then(|val| val.as_str()) + .ok_or_else(|| Error::bad_database("Invalid event in database"))?; + + let room_id = RoomId::try_from(room_id_str) + .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; + + if !db.rooms.server_in_room(sender_servername, &room_id)? { + return Err(Error::BadRequest(ErrorKind::NotFound, "Event not found.")); + } + Ok(get_event::v1::Response { origin: db.globals.server_name().to_owned(), origin_server_ts: MilliSecondsSinceUnixEpoch::now(), - pdu: PduEvent::convert_to_outgoing_federation_event( - db.rooms - .get_pdu_json(&body.event_id)? - .ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?, - ), + pdu: PduEvent::convert_to_outgoing_federation_event(event), } .into()) } +/// # `POST /_matrix/federation/v1/get_missing_events/{roomId}` +/// +/// Retrieves events that the sender is missing. #[cfg_attr( feature = "conduit_bin", post("/_matrix/federation/v1/get_missing_events/<_>", data = "") @@ -2335,22 +2401,44 @@ pub fn get_missing_events_route( return Err(Error::bad_config("Federation is disabled.")); } + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + if !db.rooms.server_in_room(sender_servername, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Server is not in room", + )); + } + let mut queued_events = body.latest_events.clone(); let mut events = Vec::new(); let mut i = 0; while i < queued_events.len() && events.len() < u64::from(body.limit) as usize { if let Some(pdu) = db.rooms.get_pdu_json(&queued_events[i])? { - let event_id = - serde_json::from_value( - serde_json::to_value(pdu.get("event_id").cloned().ok_or_else(|| { - Error::bad_database("Event in db has no event_id field.") - })?) - .expect("canonical json is valid json value"), - ) - .map_err(|_| Error::bad_database("Invalid event_id field in pdu in db."))?; + let room_id_str = pdu + .get("room_id") + .and_then(|val| val.as_str()) + .ok_or_else(|| Error::bad_database("Invalid event in database"))?; - if body.earliest_events.contains(&event_id) { + let event_room_id = RoomId::try_from(room_id_str) + .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; + + if event_room_id != body.room_id { + warn!( + "Evil event detected: Event {} found while searching in room {}", + queued_events[i], body.room_id + ); + return Err(Error::BadRequest( + ErrorKind::InvalidParam, + "Evil event detected", + )); + } + + if body.earliest_events.contains(&queued_events[i]) { i += 1; continue; } @@ -2371,6 +2459,11 @@ pub fn get_missing_events_route( Ok(get_missing_events::v1::Response { events }.into()) } +/// # `GET /_matrix/federation/v1/event_auth/{roomId}/{eventId}` +/// +/// Retrieves the auth chain for a given event. +/// +/// - This does not include the event itself #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/event_auth/<_>/<_>", data = "") @@ -2384,7 +2477,29 @@ pub fn get_event_authorization_route( return Err(Error::bad_config("Federation is disabled.")); } - let auth_chain_ids = get_auth_chain(vec![Arc::new(body.event_id.clone())], &db)?; + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + let event = db + .rooms + .get_pdu_json(&body.event_id)? + .ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?; + + let room_id_str = event + .get("room_id") + .and_then(|val| val.as_str()) + .ok_or_else(|| Error::bad_database("Invalid event in database"))?; + + let room_id = RoomId::try_from(room_id_str) + .map_err(|_| Error::bad_database("Invalid room id field in event in database"))?; + + if !db.rooms.server_in_room(sender_servername, &room_id)? { + return Err(Error::BadRequest(ErrorKind::NotFound, "Event not found.")); + } + + let auth_chain_ids = get_auth_chain(&room_id, vec![Arc::new(body.event_id.clone())], &db)?; Ok(get_event_authorization::v1::Response { auth_chain: auth_chain_ids @@ -2395,6 +2510,9 @@ pub fn get_event_authorization_route( .into()) } +/// # `GET /_matrix/federation/v1/state/{roomId}` +/// +/// Retrieves the current state of the room. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/state/<_>", data = "") @@ -2408,6 +2526,18 @@ pub fn get_room_state_route( return Err(Error::bad_config("Federation is disabled.")); } + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + if !db.rooms.server_in_room(sender_servername, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Server is not in room.", + )); + } + let shortstatehash = db .rooms .pdu_shortstatehash(&body.event_id)? @@ -2427,7 +2557,7 @@ pub fn get_room_state_route( }) .collect(); - let auth_chain_ids = get_auth_chain(vec![Arc::new(body.event_id.clone())], &db)?; + let auth_chain_ids = get_auth_chain(&body.room_id, vec![Arc::new(body.event_id.clone())], &db)?; Ok(get_room_state::v1::Response { auth_chain: auth_chain_ids @@ -2443,6 +2573,9 @@ pub fn get_room_state_route( .into()) } +/// # `GET /_matrix/federation/v1/state_ids/{roomId}` +/// +/// Retrieves the current state of the room. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/state_ids/<_>", data = "") @@ -2456,6 +2589,18 @@ pub fn get_room_state_ids_route( return Err(Error::bad_config("Federation is disabled.")); } + let sender_servername = body + .sender_servername + .as_ref() + .expect("server is authenticated"); + + if !db.rooms.server_in_room(sender_servername, &body.room_id)? { + return Err(Error::BadRequest( + ErrorKind::Forbidden, + "Server is not in room.", + )); + } + let shortstatehash = db .rooms .pdu_shortstatehash(&body.event_id)? @@ -2471,7 +2616,7 @@ pub fn get_room_state_ids_route( .map(|(_, id)| (*id).clone()) .collect(); - let auth_chain_ids = get_auth_chain(vec![Arc::new(body.event_id.clone())], &db)?; + let auth_chain_ids = get_auth_chain(&body.room_id, vec![Arc::new(body.event_id.clone())], &db)?; Ok(get_room_state_ids::v1::Response { auth_chain_ids: auth_chain_ids.map(|id| (*id).clone()).collect(), @@ -2480,6 +2625,9 @@ pub fn get_room_state_ids_route( .into()) } +/// # `GET /_matrix/federation/v1/make_join/{roomId}/{userId}` +/// +/// Creates a join template. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/make_join/<_>/<_>", data = "") @@ -2719,7 +2867,11 @@ async fn create_join_event( drop(mutex_lock); let state_ids = db.rooms.state_full_ids(shortstatehash)?; - let auth_chain_ids = get_auth_chain(state_ids.iter().map(|(_, id)| id.clone()).collect(), &db)?; + let auth_chain_ids = get_auth_chain( + &room_id, + state_ids.iter().map(|(_, id)| id.clone()).collect(), + &db, + )?; for server in db .rooms @@ -2745,6 +2897,9 @@ async fn create_join_event( }) } +/// # `PUT /_matrix/federation/v1/send_join/{roomId}/{eventId}` +/// +/// Submits a signed join event. #[cfg_attr( feature = "conduit_bin", put("/_matrix/federation/v1/send_join/<_>/<_>", data = "") @@ -2759,6 +2914,9 @@ pub async fn create_join_event_v1_route( Ok(create_join_event::v1::Response { room_state }.into()) } +/// # `PUT /_matrix/federation/v2/send_join/{roomId}/{eventId}` +/// +/// Submits a signed join event. #[cfg_attr( feature = "conduit_bin", put("/_matrix/federation/v2/send_join/<_>/<_>", data = "") @@ -2773,6 +2931,9 @@ pub async fn create_join_event_v2_route( Ok(create_join_event::v2::Response { room_state }.into()) } +/// # `PUT /_matrix/federation/v2/invite/{roomId}/{eventId}` +/// +/// Invites a remote user to a room. #[cfg_attr( feature = "conduit_bin", put("/_matrix/federation/v2/invite/<_>/<_>", data = "") @@ -2882,6 +3043,9 @@ pub async fn create_invite_route( .into()) } +/// # `GET /_matrix/federation/v1/user/devices/{userId}` +/// +/// Gets information on all devices of the user. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/user/devices/<_>", data = "") @@ -2922,6 +3086,9 @@ pub fn get_devices_route( .into()) } +/// # `GET /_matrix/federation/v1/query/directory` +/// +/// Resolve a room alias to a room id. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/query/directory", data = "") @@ -2950,6 +3117,9 @@ pub fn get_room_information_route( .into()) } +/// # `GET /_matrix/federation/v1/query/profile` +/// +/// Gets information on a profile. #[cfg_attr( feature = "conduit_bin", get("/_matrix/federation/v1/query/profile", data = "") @@ -2990,6 +3160,9 @@ pub fn get_profile_information_route( .into()) } +/// # `POST /_matrix/federation/v1/user/keys/query` +/// +/// Gets devices and identity keys for the given users. #[cfg_attr( feature = "conduit_bin", post("/_matrix/federation/v1/user/keys/query", data = "") @@ -3021,6 +3194,9 @@ pub async fn get_keys_route( .into()) } +/// # `POST /_matrix/federation/v1/user/keys/claim` +/// +/// Claims one-time keys. #[cfg_attr( feature = "conduit_bin", post("/_matrix/federation/v1/user/keys/claim", data = "") @@ -3045,7 +3221,7 @@ pub async fn claim_keys_route( } #[tracing::instrument(skip(event, pub_key_map, db))] -pub async fn fetch_required_signing_keys( +pub(crate) async fn fetch_required_signing_keys( event: &BTreeMap, pub_key_map: &RwLock>>, db: &Database,