1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2024-11-04 17:38:53 +01:00

Merge branch 'docs' into 'master'

docs: documentation for every endpoint

See merge request famedly/conduit!177
This commit is contained in:
Timo Kösters 2021-09-01 10:20:13 +00:00
commit 4e68c22457
36 changed files with 864 additions and 94 deletions

View file

@ -9,7 +9,7 @@ use std::{
}; };
use tracing::warn; use tracing::warn;
pub async fn send_request<T: OutgoingRequest>( pub(crate) async fn send_request<T: OutgoingRequest>(
globals: &crate::database::globals::Globals, globals: &crate::database::globals::Globals,
registration: serde_yaml::Value, registration: serde_yaml::Value,
request: T, request: T,

View file

@ -40,8 +40,12 @@ const GUEST_NAME_LENGTH: usize = 10;
/// ///
/// Checks if a username is valid and available on this server. /// Checks if a username is valid and available on this server.
/// ///
/// - Returns true if no user or appservice on this server claimed this username /// Conditions for returning true:
/// - This will not reserve the username, so the username might become invalid when trying to register /// - 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/register/available", data = "<body>") get("/_matrix/client/r0/register/available", data = "<body>")
@ -80,11 +84,15 @@ pub async fn get_register_available_route(
/// ///
/// Register an account on this homeserver. /// Register an account on this homeserver.
/// ///
/// - Returns the device id and access_token unless `inhibit_login` is true /// You can use [`GET /_matrix/client/r0/register/available`](fn.get_register_available_route.html)
/// - When registering a guest account, all parameters except initial_device_display_name will be /// to check if the user id is valid and available.
/// ignored ///
/// - Creates a new account and a device for it /// - Only works if registration is enabled
/// - The account will be populated with default account data /// - 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/register", data = "<body>") post("/_matrix/client/r0/register", data = "<body>")
@ -129,7 +137,7 @@ pub async fn register_route(
))?; ))?;
// Check if username is creative enough // Check if username is creative enough
if !missing_username && db.users.exists(&user_id)? { if db.users.exists(&user_id)? {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::UserInUse, ErrorKind::UserInUse,
"Desired user ID is already taken.", "Desired user ID is already taken.",
@ -193,12 +201,12 @@ pub async fn register_route(
// Create user // Create user
db.users.create(&user_id, password)?; db.users.create(&user_id, password)?;
// Default to pretty displayname
let displayname = format!("{} ⚡️", user_id.localpart()); let displayname = format!("{} ⚡️", user_id.localpart());
db.users db.users
.set_displayname(&user_id, Some(displayname.clone()))?; .set_displayname(&user_id, Some(displayname.clone()))?;
// Initial data // Initial account data
db.account_data.update( db.account_data.update(
None, None,
&user_id, &user_id,
@ -211,6 +219,7 @@ pub async fn register_route(
&db.globals, &db.globals,
)?; )?;
// Inhibit login does not work for guests
if !is_guest && body.inhibit_login { if !is_guest && body.inhibit_login {
return Ok(register::Response { return Ok(register::Response {
access_token: None, access_token: None,
@ -231,7 +240,7 @@ pub async fn register_route(
// Generate new token for the device // Generate new token for the device
let token = utils::random_string(TOKEN_LENGTH); let token = utils::random_string(TOKEN_LENGTH);
// Add device // Create device for this account
db.users.create_device( db.users.create_device(
&user_id, &user_id,
&device_id, &device_id,
@ -239,7 +248,7 @@ pub async fn register_route(
body.initial_device_display_name.clone(), 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 { if db.users.count()? == 1 {
// Create a user for the server // Create a user for the server
let conduit_user = UserId::parse_with_server_name("conduit", db.globals.server_name()) 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. /// Changes the password of this account.
/// ///
/// - Invalidates all other access tokens if logout_devices is true /// - Requires UIAA to verify user password
/// - Deletes all other devices and most of their data (to-device events, last seen, etc.) if /// - Changes the password of the sender user
/// logout_devices is true /// - 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/account/password", data = "<body>") post("/_matrix/client/r0/account/password", data = "<body>")
@ -598,9 +614,9 @@ pub async fn change_password_route(
/// # `GET _matrix/client/r0/account/whoami` /// # `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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/account/whoami", data = "<body>") get("/_matrix/client/r0/account/whoami", data = "<body>")
@ -616,11 +632,13 @@ pub async fn whoami_route(body: Ruma<whoami::Request>) -> ConduitResult<whoami::
/// # `POST /_matrix/client/r0/account/deactivate` /// # `POST /_matrix/client/r0/account/deactivate`
/// ///
/// Deactivate this user's account /// Deactivate sender user account.
/// ///
/// - Leaves all rooms and rejects all invitations /// - Leaves all rooms and rejects all invitations
/// - Invalidates all access tokens /// - Invalidates all access tokens
/// - Deletes all devices /// - Deletes all device metadata (device id, device display name, last seen ip, last seen ts)
/// - Forgets all to-device events
/// - Triggers device list updates
/// - Removes ability to log in again /// - Removes ability to log in again
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
@ -667,6 +685,7 @@ pub async fn deactivate_route(
} }
// Leave all joined rooms and reject all invitations // Leave all joined rooms and reject all invitations
// TODO: work over federation invites
let all_rooms = db let all_rooms = db
.rooms .rooms
.rooms_joined(&sender_user) .rooms_joined(&sender_user)
@ -730,6 +749,8 @@ pub async fn deactivate_route(
/// # `GET _matrix/client/r0/account/3pid` /// # `GET _matrix/client/r0/account/3pid`
/// ///
/// Get a list of third party identifiers associated with this account. /// Get a list of third party identifiers associated with this account.
///
/// - Currently always returns empty list
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/account/3pid", data = "<body>") get("/_matrix/client/r0/account/3pid", data = "<body>")

View file

@ -15,6 +15,9 @@ use ruma::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{delete, get, put}; use rocket::{delete, get, put};
/// # `PUT /_matrix/client/r0/directory/room/{roomAlias}`
///
/// Creates a new room alias on this server.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/directory/room/<_>", data = "<body>") put("/_matrix/client/r0/directory/room/<_>", data = "<body>")
@ -24,6 +27,13 @@ pub async fn create_alias_route(
db: DatabaseGuard, db: DatabaseGuard,
body: Ruma<create_alias::Request<'_>>, body: Ruma<create_alias::Request<'_>>,
) -> ConduitResult<create_alias::Response> { ) -> ConduitResult<create_alias::Response> {
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() { if db.rooms.id_from_alias(&body.room_alias)?.is_some() {
return Err(Error::Conflict("Alias already exists.")); return Err(Error::Conflict("Alias already exists."));
} }
@ -36,6 +46,12 @@ pub async fn create_alias_route(
Ok(create_alias::Response::new().into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/r0/directory/room/<_>", data = "<body>") delete("/_matrix/client/r0/directory/room/<_>", data = "<body>")
@ -45,13 +61,27 @@ pub async fn delete_alias_route(
db: DatabaseGuard, db: DatabaseGuard,
body: Ruma<delete_alias::Request<'_>>, body: Ruma<delete_alias::Request<'_>>,
) -> ConduitResult<delete_alias::Response> { ) -> ConduitResult<delete_alias::Response> {
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)?; db.rooms.set_alias(&body.room_alias, None, &db.globals)?;
// TODO: update alt_aliases?
db.flush()?; db.flush()?;
Ok(delete_alias::Response::new().into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/directory/room/<_>", data = "<body>") get("/_matrix/client/r0/directory/room/<_>", data = "<body>")
@ -64,7 +94,7 @@ pub async fn get_alias_route(
get_alias_helper(&db, &body.room_alias).await get_alias_helper(&db, &body.room_alias).await
} }
pub async fn get_alias_helper( pub(crate) async fn get_alias_helper(
db: &Database, db: &Database,
room_alias: &RoomAliasId, room_alias: &RoomAliasId,
) -> ConduitResult<get_alias::Response> { ) -> ConduitResult<get_alias::Response> {

View file

@ -12,6 +12,9 @@ use ruma::api::client::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{delete, get, post, put}; use rocket::{delete, get, post, put};
/// # `POST /_matrix/client/r0/room_keys/version`
///
/// Creates a new backup.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/unstable/room_keys/version", data = "<body>") post("/_matrix/client/unstable/room_keys/version", data = "<body>")
@ -31,6 +34,9 @@ pub async fn create_backup_route(
Ok(create_backup::Response { version }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>") put("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
@ -49,6 +55,9 @@ pub async fn update_backup_route(
Ok(update_backup::Response {}.into()) Ok(update_backup::Response {}.into())
} }
/// # `GET /_matrix/client/r0/room_keys/version`
///
/// Get information about the latest backup version.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/unstable/room_keys/version", data = "<body>") get("/_matrix/client/unstable/room_keys/version", data = "<body>")
@ -77,6 +86,9 @@ pub async fn get_latest_backup_route(
.into()) .into())
} }
/// # `GET /_matrix/client/r0/room_keys/version`
///
/// Get information about an existing backup.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>") get("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
@ -104,6 +116,11 @@ pub async fn get_backup_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>") delete("/_matrix/client/unstable/room_keys/version/<_>", data = "<body>")
@ -122,7 +139,13 @@ pub async fn delete_backup_route(
Ok(delete_backup::Response {}.into()) Ok(delete_backup::Response {}.into())
} }
/// # `PUT /_matrix/client/r0/room_keys/keys`
///
/// Add the received backup keys to the database. /// 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/unstable/room_keys/keys", data = "<body>") put("/_matrix/client/unstable/room_keys/keys", data = "<body>")
@ -134,6 +157,18 @@ pub async fn add_backup_keys_route(
) -> ConduitResult<add_backup_keys::Response> { ) -> ConduitResult<add_backup_keys::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); 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 (room_id, room) in &body.rooms {
for (session_id, key_data) in &room.sessions { for (session_id, key_data) in &room.sessions {
db.key_backups.add_key( db.key_backups.add_key(
@ -156,7 +191,13 @@ pub async fn add_backup_keys_route(
.into()) .into())
} }
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Add the received backup keys to the database. /// 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>") put("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>")
@ -168,6 +209,18 @@ pub async fn add_backup_key_sessions_route(
) -> ConduitResult<add_backup_key_sessions::Response> { ) -> ConduitResult<add_backup_key_sessions::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); 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 { for (session_id, key_data) in &body.sessions {
db.key_backups.add_key( db.key_backups.add_key(
&sender_user, &sender_user,
@ -188,7 +241,13 @@ pub async fn add_backup_key_sessions_route(
.into()) .into())
} }
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Add the received backup key to the database. /// 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>") put("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>")
@ -200,6 +259,18 @@ pub async fn add_backup_key_session_route(
) -> ConduitResult<add_backup_key_session::Response> { ) -> ConduitResult<add_backup_key_session::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); 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( db.key_backups.add_key(
&sender_user, &sender_user,
&body.version, &body.version,
@ -218,6 +289,9 @@ pub async fn add_backup_key_session_route(
.into()) .into())
} }
/// # `GET /_matrix/client/r0/room_keys/keys`
///
/// Retrieves all keys from the backup.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/unstable/room_keys/keys", data = "<body>") get("/_matrix/client/unstable/room_keys/keys", data = "<body>")
@ -234,6 +308,9 @@ pub async fn get_backup_keys_route(
Ok(get_backup_keys::Response { rooms }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>") get("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>")
@ -252,6 +329,9 @@ pub async fn get_backup_key_sessions_route(
Ok(get_backup_key_sessions::Response { sessions }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>") get("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>")
@ -274,6 +354,9 @@ pub async fn get_backup_key_session_route(
Ok(get_backup_key_session::Response { key_data }.into()) Ok(get_backup_key_session::Response { key_data }.into())
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys`
///
/// Delete the keys from the backup.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/unstable/room_keys/keys", data = "<body>") delete("/_matrix/client/unstable/room_keys/keys", data = "<body>")
@ -297,6 +380,9 @@ pub async fn delete_backup_keys_route(
.into()) .into())
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Delete the keys from the backup for a given room.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>") delete("/_matrix/client/unstable/room_keys/keys/<_>", data = "<body>")
@ -320,6 +406,9 @@ pub async fn delete_backup_key_sessions_route(
.into()) .into())
} }
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Delete a key from the backup.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>") delete("/_matrix/client/unstable/room_keys/keys/<_>/<_>", data = "<body>")

View file

@ -13,7 +13,7 @@ use rocket::get;
/// # `GET /_matrix/client/r0/capabilities` /// # `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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/capabilities", data = "<_body>") get("/_matrix/client/r0/capabilities", data = "<_body>")

View file

@ -16,6 +16,9 @@ use serde_json::{json, value::RawValue as RawJsonValue};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; use rocket::{get, put};
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Sets some account data for the sender user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>") put("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
@ -48,6 +51,9 @@ pub async fn set_global_account_data_route(
Ok(set_global_account_data::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put( put(
@ -83,6 +89,9 @@ pub async fn set_room_account_data_route(
Ok(set_room_account_data::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>") get("/_matrix/client/r0/user/<_>/account_data/<_>", data = "<body>")
@ -98,7 +107,6 @@ pub async fn get_global_account_data_route(
.account_data .account_data
.get::<Box<RawJsonValue>>(None, sender_user, body.event_type.clone().into())? .get::<Box<RawJsonValue>>(None, sender_user, body.event_type.clone().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush()?;
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get()) let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))? .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()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get( get(
@ -129,7 +140,6 @@ pub async fn get_room_account_data_route(
body.event_type.clone().into(), body.event_type.clone().into(),
)? )?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?; .ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
db.flush()?;
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get()) let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))? .map_err(|_| Error::bad_database("Invalid account data event in db."))?

View file

@ -5,6 +5,12 @@ use std::convert::TryFrom;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::get; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/context/<_>", data = "<body>") get("/_matrix/client/r0/rooms/<_>/context/<_>", data = "<body>")

View file

@ -11,6 +11,9 @@ use super::SESSION_ID_LENGTH;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{delete, get, post, put}; use rocket::{delete, get, post, put};
/// # `GET /_matrix/client/r0/devices`
///
/// Get metadata on all devices of the sender user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/devices", data = "<body>") get("/_matrix/client/r0/devices", data = "<body>")
@ -31,6 +34,9 @@ pub async fn get_devices_route(
Ok(get_devices::Response { devices }.into()) Ok(get_devices::Response { devices }.into())
} }
/// # `GET /_matrix/client/r0/devices/{deviceId}`
///
/// Get metadata on a single device of the sender user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/devices/<_>", data = "<body>") get("/_matrix/client/r0/devices/<_>", data = "<body>")
@ -50,6 +56,9 @@ pub async fn get_device_route(
Ok(get_device::Response { device }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/devices/<_>", data = "<body>") put("/_matrix/client/r0/devices/<_>", data = "<body>")
@ -76,6 +85,15 @@ pub async fn update_device_route(
Ok(update_device::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/r0/devices/<_>", data = "<body>") delete("/_matrix/client/r0/devices/<_>", data = "<body>")
@ -128,6 +146,17 @@ pub async fn delete_device_route(
Ok(delete_device::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/delete_devices", data = "<body>") post("/_matrix/client/r0/delete_devices", data = "<body>")

View file

@ -28,6 +28,11 @@ use tracing::{info, warn};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post, put}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/publicRooms", data = "<body>") post("/_matrix/client/r0/publicRooms", data = "<body>")
@ -48,6 +53,11 @@ pub async fn get_public_rooms_filtered_route(
.await .await
} }
/// # `GET /_matrix/client/r0/publicRooms`
///
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/publicRooms", data = "<body>") get("/_matrix/client/r0/publicRooms", data = "<body>")
@ -77,6 +87,11 @@ pub async fn get_public_rooms_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/directory/list/room/<_>", data = "<body>") put("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
@ -107,6 +122,9 @@ pub async fn set_room_visibility_route(
Ok(set_room_visibility::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/directory/list/room/<_>", data = "<body>") get("/_matrix/client/r0/directory/list/room/<_>", data = "<body>")
@ -126,7 +144,7 @@ pub async fn get_room_visibility_route(
.into()) .into())
} }
pub async fn get_public_rooms_filtered_helper( pub(crate) async fn get_public_rooms_filtered_helper(
db: &Database, db: &Database,
server: Option<&ServerName>, server: Option<&ServerName>,
limit: Option<UInt>, limit: Option<UInt>,

View file

@ -4,6 +4,9 @@ use ruma::api::client::r0::filter::{self, create_filter, get_filter};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; 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/<_>"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/user/<_>/filter/<_>"))]
#[tracing::instrument] #[tracing::instrument]
pub async fn get_filter_route() -> ConduitResult<get_filter::Response> { pub async fn get_filter_route() -> ConduitResult<get_filter::Response> {
@ -18,6 +21,9 @@ pub async fn get_filter_route() -> ConduitResult<get_filter::Response> {
.into()) .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"))] #[cfg_attr(feature = "conduit_bin", post("/_matrix/client/r0/user/<_>/filter"))]
#[tracing::instrument] #[tracing::instrument]
pub async fn create_filter_route() -> ConduitResult<create_filter::Response> { pub async fn create_filter_route() -> ConduitResult<create_filter::Response> {

View file

@ -24,6 +24,12 @@ use std::collections::{BTreeMap, HashMap, HashSet};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/keys/upload", data = "<body>") post("/_matrix/client/r0/keys/upload", data = "<body>")
@ -49,6 +55,7 @@ pub async fn upload_keys_route(
} }
if let Some(device_keys) = &body.device_keys { 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 // This check is needed to assure that signatures are kept
if db if db
.users .users
@ -73,6 +80,13 @@ pub async fn upload_keys_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/keys/query", data = "<body>") post("/_matrix/client/r0/keys/query", data = "<body>")
@ -95,6 +109,9 @@ pub async fn get_keys_route(
Ok(response.into()) Ok(response.into())
} }
/// # `POST /_matrix/client/r0/keys/claim`
///
/// Claims one-time keys
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/keys/claim", data = "<body>") post("/_matrix/client/r0/keys/claim", data = "<body>")
@ -111,6 +128,11 @@ pub async fn claim_keys_route(
Ok(response.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/unstable/keys/device_signing/upload", data = "<body>") post("/_matrix/client/unstable/keys/device_signing/upload", data = "<body>")
@ -172,6 +194,9 @@ pub async fn upload_signing_keys_route(
Ok(upload_signing_keys::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/unstable/keys/signatures/upload", data = "<body>") post("/_matrix/client/unstable/keys/signatures/upload", data = "<body>")
@ -233,6 +258,11 @@ pub async fn upload_signatures_route(
Ok(upload_signatures::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/keys/changes", data = "<body>") get("/_matrix/client/r0/keys/changes", data = "<body>")
@ -284,7 +314,7 @@ pub async fn get_key_changes_route(
.into()) .into())
} }
pub async fn get_keys_helper<F: Fn(&UserId) -> bool>( pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
sender_user: Option<&UserId>, sender_user: Option<&UserId>,
device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>, device_keys_input: &BTreeMap<UserId, Vec<Box<DeviceId>>>,
allowed_signatures: F, allowed_signatures: F,
@ -409,7 +439,7 @@ pub async fn get_keys_helper<F: Fn(&UserId) -> bool>(
}) })
} }
pub async fn claim_keys_helper( pub(crate) async fn claim_keys_helper(
one_time_keys_input: &BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>, one_time_keys_input: &BTreeMap<UserId, BTreeMap<Box<DeviceId>, DeviceKeyAlgorithm>>,
db: &Database, db: &Database,
) -> Result<claim_keys::Response> { ) -> Result<claim_keys::Response> {

View file

@ -12,6 +12,9 @@ use rocket::{get, post};
const MXC_LENGTH: usize = 32; const MXC_LENGTH: usize = 32;
/// # `GET /_matrix/media/r0/config`
///
/// Returns max upload size.
#[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/media/r0/config"))]
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
pub async fn get_media_config_route( pub async fn get_media_config_route(
@ -23,6 +26,12 @@ pub async fn get_media_config_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/media/r0/upload", data = "<body>") post("/_matrix/media/r0/upload", data = "<body>")
@ -61,6 +70,11 @@ pub async fn create_content_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/media/r0/download/<_>/<_>", data = "<body>") get("/_matrix/media/r0/download/<_>/<_>", data = "<body>")
@ -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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/media/r0/thumbnail/<_>/<_>", data = "<body>") get("/_matrix/media/r0/thumbnail/<_>/<_>", data = "<body>")

View file

@ -38,6 +38,12 @@ use tracing::{debug, error, warn};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/join", data = "<body>") post("/_matrix/client/r0/rooms/<_>/join", data = "<body>")
@ -79,6 +85,12 @@ pub async fn join_room_by_id_route(
ret 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/join/<_>", data = "<body>") post("/_matrix/client/r0/join/<_>", data = "<body>")
@ -133,6 +145,11 @@ pub async fn join_room_by_id_or_alias_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/leave", data = "<body>") post("/_matrix/client/r0/rooms/<_>/leave", data = "<body>")
@ -151,6 +168,9 @@ pub async fn leave_room_route(
Ok(leave_room::Response::new().into()) Ok(leave_room::Response::new().into())
} }
/// # `POST /_matrix/client/r0/rooms/{roomId}/invite`
///
/// Tries to send an invite event into the room.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/invite", data = "<body>") post("/_matrix/client/r0/rooms/<_>/invite", data = "<body>")
@ -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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/kick", data = "<body>") post("/_matrix/client/r0/rooms/<_>/kick", data = "<body>")
@ -234,6 +257,9 @@ pub async fn kick_user_route(
Ok(kick_user::Response::new().into()) Ok(kick_user::Response::new().into())
} }
/// # `POST /_matrix/client/r0/rooms/{roomId}/ban`
///
/// Tries to send a ban event into the room.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/ban", data = "<body>") post("/_matrix/client/r0/rooms/<_>/ban", data = "<body>")
@ -307,6 +333,9 @@ pub async fn ban_user_route(
Ok(ban_user::Response::new().into()) Ok(ban_user::Response::new().into())
} }
/// # `POST /_matrix/client/r0/rooms/{roomId}/unban`
///
/// Tries to send an unban event into the room.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/unban", data = "<body>") post("/_matrix/client/r0/rooms/<_>/unban", data = "<body>")
@ -369,6 +398,14 @@ pub async fn unban_user_route(
Ok(unban_user::Response::new().into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/forget", data = "<body>") post("/_matrix/client/r0/rooms/<_>/forget", data = "<body>")
@ -387,6 +424,9 @@ pub async fn forget_room_route(
Ok(forget_room::Response::new().into()) Ok(forget_room::Response::new().into())
} }
/// # `POST /_matrix/client/r0/joined_rooms`
///
/// Lists all rooms the user has joined.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/joined_rooms", data = "<body>") get("/_matrix/client/r0/joined_rooms", data = "<body>")
@ -408,6 +448,11 @@ pub async fn joined_rooms_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/members", data = "<body>") get("/_matrix/client/r0/rooms/<_>/members", data = "<body>")
@ -419,6 +464,7 @@ pub async fn get_member_events_route(
) -> ConduitResult<get_member_events::Response> { ) -> ConduitResult<get_member_events::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); 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)? { if !db.rooms.is_joined(sender_user, &body.room_id)? {
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::Forbidden, ErrorKind::Forbidden,
@ -438,6 +484,12 @@ pub async fn get_member_events_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/joined_members", data = "<body>") get("/_matrix/client/r0/rooms/<_>/joined_members", data = "<body>")
@ -449,11 +501,7 @@ pub async fn joined_members_route(
) -> ConduitResult<joined_members::Response> { ) -> ConduitResult<joined_members::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated"); let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !db if !db.rooms.is_joined(&sender_user, &body.room_id)? {
.rooms
.is_joined(&sender_user, &body.room_id)
.unwrap_or(false)
{
return Err(Error::BadRequest( return Err(Error::BadRequest(
ErrorKind::Forbidden, ErrorKind::Forbidden,
"You aren't a member of the room.", "You aren't a member of the room.",
@ -803,7 +851,7 @@ async fn validate_and_add_event_id(
Ok((event_id, value)) Ok((event_id, value))
} }
pub async fn invite_helper<'a>( pub(crate) async fn invite_helper<'a>(
sender_user: &UserId, sender_user: &UserId,
user_id: &UserId, user_id: &UserId,
room_id: &RoomId, room_id: &RoomId,

View file

@ -16,6 +16,13 @@ use std::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/rooms/<_>/send/<_>/<_>", data = "<body>") put("/_matrix/client/r0/rooms/<_>/send/<_>/<_>", data = "<body>")
@ -92,6 +99,12 @@ pub async fn send_message_event_route(
Ok(send_message_event::Response::new(event_id).into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/messages", data = "<body>") get("/_matrix/client/r0/rooms/<_>/messages", data = "<body>")

View file

@ -71,6 +71,9 @@ pub const DEVICE_ID_LENGTH: usize = 10;
pub const TOKEN_LENGTH: usize = 256; pub const TOKEN_LENGTH: usize = 256;
pub const SESSION_ID_LENGTH: usize = 256; pub const SESSION_ID_LENGTH: usize = 256;
/// # `OPTIONS`
///
/// Web clients use this to get CORS headers.
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
#[options("/<_..>")] #[options("/<_..>")]
#[tracing::instrument] #[tracing::instrument]

View file

@ -5,6 +5,9 @@ use std::{convert::TryInto, time::Duration};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; use rocket::{get, put};
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
///
/// Sets the presence state of the sender user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/presence/<_>/status", data = "<body>") put("/_matrix/client/r0/presence/<_>/status", data = "<body>")
@ -46,6 +49,11 @@ pub async fn set_presence_route(
Ok(set_presence::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/presence/<_>/status", data = "<body>") get("/_matrix/client/r0/presence/<_>/status", data = "<body>")
@ -71,6 +79,7 @@ pub async fn get_presence_route(
.get_last_presence_event(&sender_user, &room_id)? .get_last_presence_event(&sender_user, &room_id)?
{ {
presence_event = Some(presence); presence_event = Some(presence);
break;
} }
} }

View file

@ -17,6 +17,11 @@ use std::{convert::TryInto, sync::Arc};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/profile/<_>/displayname", data = "<body>") put("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
@ -115,6 +120,11 @@ pub async fn set_displayname_route(
Ok(set_display_name::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/profile/<_>/displayname", data = "<body>") get("/_matrix/client/r0/profile/<_>/displayname", data = "<body>")
@ -149,6 +159,11 @@ pub async fn get_displayname_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>") put("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
@ -249,6 +264,11 @@ pub async fn set_avatar_url_route(
Ok(set_avatar_url::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>") get("/_matrix/client/r0/profile/<_>/avatar_url", data = "<body>")
@ -285,6 +305,11 @@ pub async fn get_avatar_url_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/profile/<_>", data = "<body>") get("/_matrix/client/r0/profile/<_>", data = "<body>")

View file

@ -15,6 +15,9 @@ use ruma::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{delete, get, post, put}; use rocket::{delete, get, post, put};
/// # `GET /_matrix/client/r0/pushrules`
///
/// Retrieves the push rules event for this user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/pushrules", data = "<body>") get("/_matrix/client/r0/pushrules", data = "<body>")
@ -40,6 +43,9 @@ pub async fn get_pushrules_all_route(
.into()) .into())
} }
/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
///
/// Retrieves a single specified push rule for this user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>") get("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>")
@ -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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<req>") put("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<req>")
@ -197,6 +206,9 @@ pub async fn set_pushrule_route(
Ok(set_pushrule::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "<body>") get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "<body>")
@ -256,6 +268,9 @@ pub async fn get_pushrule_actions_route(
.into()) .into())
} }
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
///
/// Sets the actions of a single specified push rule for this user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "<body>") put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/actions", data = "<body>")
@ -330,6 +345,9 @@ pub async fn set_pushrule_actions_route(
Ok(set_pushrule_actions::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "<body>") get("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "<body>")
@ -391,6 +409,9 @@ pub async fn get_pushrule_enabled_route(
Ok(get_pushrule_enabled::Response { enabled }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "<body>") put("/_matrix/client/r0/pushrules/<_>/<_>/<_>/enabled", data = "<body>")
@ -470,6 +491,9 @@ pub async fn set_pushrule_enabled_route(
Ok(set_pushrule_enabled::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>") delete("/_matrix/client/r0/pushrules/<_>/<_>/<_>", data = "<body>")
@ -539,6 +563,9 @@ pub async fn delete_pushrule_route(
Ok(delete_pushrule::Response {}.into()) Ok(delete_pushrule::Response {}.into())
} }
/// # `GET /_matrix/client/r0/pushers`
///
/// Gets all currently active pushers for the sender user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/pushers", data = "<body>") get("/_matrix/client/r0/pushers", data = "<body>")
@ -556,6 +583,11 @@ pub async fn get_pushers_route(
.into()) .into())
} }
/// # `POST /_matrix/client/r0/pushers/set`
///
/// Adds a pusher for the sender user.
///
/// - TODO: Handle `append`
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/pushers/set", data = "<body>") post("/_matrix/client/r0/pushers/set", data = "<body>")

View file

@ -13,6 +13,12 @@ use std::collections::BTreeMap;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::post; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/read_markers", data = "<body>") post("/_matrix/client/r0/rooms/<_>/read_markers", data = "<body>")
@ -80,6 +86,9 @@ pub async fn set_read_marker_route(
Ok(set_read_marker::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/receipt/<_>/<_>", data = "<body>") post("/_matrix/client/r0/rooms/<_>/receipt/<_>/<_>", data = "<body>")

View file

@ -9,6 +9,11 @@ use ruma::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::put; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/rooms/<_>/redact/<_>/<_>", data = "<body>") put("/_matrix/client/r0/rooms/<_>/redact/<_>/<_>", data = "<body>")

View file

@ -20,6 +20,22 @@ use tracing::{info, warn};
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, post}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/createRoom", data = "<body>") post("/_matrix/client/r0/createRoom", data = "<body>")
@ -344,6 +360,11 @@ pub async fn create_room_route(
Ok(create_room::Response::new(room_id).into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/event/<_>", data = "<body>") get("/_matrix/client/r0/rooms/<_>/event/<_>", data = "<body>")
@ -372,6 +393,11 @@ pub async fn get_room_event_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/aliases", data = "<body>") get("/_matrix/client/r0/rooms/<_>/aliases", data = "<body>")
@ -400,6 +426,16 @@ pub async fn get_room_aliases_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/rooms/<_>/upgrade", data = "<body>") post("/_matrix/client/r0/rooms/<_>/upgrade", data = "<body>")

View file

@ -6,6 +6,11 @@ use rocket::post;
use search_events::{EventContextResult, ResultCategories, ResultRoomEvents, SearchResult}; use search_events::{EventContextResult, ResultCategories, ResultRoomEvents, SearchResult};
use std::collections::BTreeMap; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/search", data = "<body>") post("/_matrix/client/r0/search", data = "<body>")

View file

@ -24,7 +24,7 @@ use rocket::{get, post};
/// # `GET /_matrix/client/r0/login` /// # `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. /// when logging in.
#[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/login"))]
#[tracing::instrument] #[tracing::instrument]
@ -41,9 +41,10 @@ pub async fn get_login_types_route() -> ConduitResult<get_login_types::Response>
/// ///
/// Authenticates the user and returns an access token it can use in subsequent requests. /// 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 /// - The user needs to authenticate using their password (or if enabled using a json web token)
/// - Old access tokens of that device should be invalidated /// - If `device_id` is known: invalidates old access token of that device
/// - If `device_id` is unknown, a new device will be created /// - 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 /// Note: You can use [`GET /_matrix/client/r0/login`](fn.get_supported_versions_route.html) to see
/// supported login types. /// supported login types.
@ -162,8 +163,10 @@ pub async fn login_route(
/// ///
/// Log out the current device. /// Log out the current device.
/// ///
/// - Invalidates the access token /// - Invalidates access token
/// - Deletes the device and most of it's data (to-device events, last seen, etc.) /// - Deletes device metadata (device id, device display name, last seen ip, last seen ts)
/// - Forgets to-device events
/// - Triggers device list updates
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/logout", data = "<body>") post("/_matrix/client/r0/logout", data = "<body>")
@ -188,7 +191,9 @@ pub async fn logout_route(
/// Log out all devices of this user. /// Log out all devices of this user.
/// ///
/// - Invalidates all access tokens /// - 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) /// Note: This is equivalent to calling [`GET /_matrix/client/r0/logout`](fn.logout_route.html)
/// from each device of this user. /// from each device of this user.

View file

@ -22,6 +22,13 @@ use ruma::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{get, put}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>") put("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
@ -48,6 +55,13 @@ pub async fn send_state_event_for_key_route(
Ok(send_state_event::Response { event_id }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>") put("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
@ -74,6 +88,11 @@ pub async fn send_state_event_for_empty_key_route(
Ok(send_state_event::Response { event_id }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/state", data = "<body>") get("/_matrix/client/r0/rooms/<_>/state", data = "<body>")
@ -121,6 +140,11 @@ pub async fn get_state_events_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>") get("/_matrix/client/r0/rooms/<_>/state/<_>/<_>", data = "<body>")
@ -172,6 +196,11 @@ pub async fn get_state_events_for_key_route(
.into()) .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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>") get("/_matrix/client/r0/rooms/<_>/state/<_>", data = "<body>")
@ -223,7 +252,7 @@ pub async fn get_state_events_for_empty_key_route(
.into()) .into())
} }
pub async fn send_state_event_for_key_helper( async fn send_state_event_for_key_helper(
db: &Database, db: &Database,
sender: &UserId, sender: &UserId,
room_id: &RoomId, room_id: &RoomId,
@ -233,6 +262,8 @@ pub async fn send_state_event_for_key_helper(
) -> Result<EventId> { ) -> Result<EventId> {
let sender_user = sender; 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) = if let Ok(canonical_alias) =
serde_json::from_str::<CanonicalAliasEventContent>(json.json().get()) serde_json::from_str::<CanonicalAliasEventContent>(json.json().get())
{ {

View file

@ -22,12 +22,33 @@ use rocket::{get, tokio};
/// Synchronize the client's state with the latest state on the server. /// 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 /// - This endpoint takes a `since` parameter which should be the `next_batch` value from a
/// previous request. /// previous request for incremental syncs.
/// - 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. /// Calling this endpoint without a `since` parameter returns:
/// - To get incremental updates, you can call this endpoint with a `since` parameter. This will /// - Some of the most recent events of each timeline
/// return all recent events, state updates and more data that happened since the last /sync /// - Notification counts for each room
/// request. /// - 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/sync", data = "<body>") get("/_matrix/client/r0/sync", data = "<body>")
@ -106,7 +127,7 @@ pub async fn sync_events_route(
result result
} }
pub async fn sync_helper_wrapper( async fn sync_helper_wrapper(
db: Arc<DatabaseGuard>, db: Arc<DatabaseGuard>,
sender_user: UserId, sender_user: UserId,
sender_device: Box<DeviceId>, sender_device: Box<DeviceId>,

View file

@ -8,6 +8,11 @@ use std::collections::BTreeMap;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::{delete, get, put}; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>") put("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>")
@ -45,6 +50,11 @@ pub async fn update_tag_route(
Ok(create_tag::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
delete("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>") delete("/_matrix/client/r0/user/<_>/rooms/<_>/tags/<_>", data = "<body>")
@ -79,6 +89,11 @@ pub async fn delete_tag_route(
Ok(delete_tag::Response {}.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/user/<_>/rooms/<_>/tags", data = "<body>") get("/_matrix/client/r0/user/<_>/rooms/<_>/tags", data = "<body>")

View file

@ -5,6 +5,9 @@ use ruma::api::client::r0::thirdparty::get_protocols;
use rocket::get; use rocket::get;
use std::collections::BTreeMap; use std::collections::BTreeMap;
/// # `GET /_matrix/client/r0/thirdparty/protocols`
///
/// TODO: Fetches all metadata about protocols supported by the homeserver.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/client/r0/thirdparty/protocols") get("/_matrix/client/r0/thirdparty/protocols")

View file

@ -13,6 +13,9 @@ use ruma::{
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::put; use rocket::put;
/// # `PUT /_matrix/client/r0/sendToDevice/{eventType}/{txnId}`
///
/// Send a to-device event to a set of client devices.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/sendToDevice/<_>/<_>", data = "<body>") put("/_matrix/client/r0/sendToDevice/<_>/<_>", data = "<body>")

View file

@ -5,6 +5,9 @@ use ruma::api::client::r0::typing::create_typing_event;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::put; use rocket::put;
/// # `PUT /_matrix/client/r0/rooms/{roomId}/typing/{userId}`
///
/// Sets the typing state of the sender user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/client/r0/rooms/<_>/typing/<_>", data = "<body>") put("/_matrix/client/r0/rooms/<_>/typing/<_>", data = "<body>")

View file

@ -10,7 +10,7 @@ use rocket::get;
/// ///
/// - Versions take the form MAJOR.MINOR.PATCH /// - Versions take the form MAJOR.MINOR.PATCH
/// - Only the latest PATCH release will be reported for each MAJOR.MINOR value /// - 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 /// Note: Unstable features are used while developing new features. Clients should avoid using
/// unstable features in their stable releases /// unstable features in their stable releases

View file

@ -4,6 +4,11 @@ use ruma::api::client::r0::user_directory::search_users;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::post; 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/client/r0/user_directory/search", data = "<body>") post("/_matrix/client/r0/user_directory/search", data = "<body>")

View file

@ -5,6 +5,9 @@ use std::time::Duration;
#[cfg(feature = "conduit_bin")] #[cfg(feature = "conduit_bin")]
use rocket::get; 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"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/client/r0/voip/turnServer"))]
#[tracing::instrument] #[tracing::instrument]
pub async fn turn_server_route() -> ConduitResult<get_turn_server_info::Response> { pub async fn turn_server_route() -> ConduitResult<get_turn_server_info::Response> {

View file

@ -47,6 +47,8 @@ pub struct Config {
database_path: String, database_path: String,
#[serde(default = "default_db_cache_capacity_mb")] #[serde(default = "default_db_cache_capacity_mb")]
db_cache_capacity_mb: f64, db_cache_capacity_mb: f64,
#[serde(default = "default_pdu_cache_capacity")]
pdu_cache_capacity: u32,
#[serde(default = "default_sqlite_wal_clean_second_interval")] #[serde(default = "default_sqlite_wal_clean_second_interval")]
sqlite_wal_clean_second_interval: u32, sqlite_wal_clean_second_interval: u32,
#[serde(default = "default_max_request_size")] #[serde(default = "default_max_request_size")]
@ -107,6 +109,10 @@ fn default_db_cache_capacity_mb() -> f64 {
200.0 200.0
} }
fn default_pdu_cache_capacity() -> u32 {
100_000
}
fn default_sqlite_wal_clean_second_interval() -> u32 { fn default_sqlite_wal_clean_second_interval() -> u32 {
1 * 60 // every minute 1 * 60 // every minute
} }
@ -281,7 +287,12 @@ impl Database {
softfailedeventids: builder.open_tree("softfailedeventids")?, softfailedeventids: builder.open_tree("softfailedeventids")?,
referencedevents: builder.open_tree("referencedevents")?, referencedevents: builder.open_tree("referencedevents")?,
pdu_cache: Mutex::new(LruCache::new(100_000)), pdu_cache: Mutex::new(LruCache::new(
config
.pdu_cache_capacity
.try_into()
.expect("pdu cache capacity fits into usize"),
)),
auth_chain_cache: Mutex::new(LruCache::new(1_000_000)), auth_chain_cache: Mutex::new(LruCache::new(1_000_000)),
shorteventid_cache: Mutex::new(LruCache::new(1_000_000)), shorteventid_cache: Mutex::new(LruCache::new(1_000_000)),
eventidshort_cache: Mutex::new(LruCache::new(1_000_000)), eventidshort_cache: Mutex::new(LruCache::new(1_000_000)),

View file

@ -84,6 +84,27 @@ impl KeyBackups {
Ok(version.to_string()) Ok(version.to_string())
} }
pub fn get_latest_backup_version(&self, user_id: &UserId) -> Result<Option<String>> {
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<Option<(String, BackupAlgorithm)>> { pub fn get_latest_backup(&self, user_id: &UserId) -> Result<Option<(String, BackupAlgorithm)>> {
let mut prefix = user_id.as_bytes().to_vec(); let mut prefix = user_id.as_bytes().to_vec();
prefix.push(0xff); prefix.push(0xff);

View file

@ -1529,19 +1529,35 @@ impl Rooms {
"get_auth_chain" => { "get_auth_chain" => {
if args.len() == 1 { if args.len() == 1 {
if let Ok(event_id) = EventId::try_from(args[0]) { if let Ok(event_id) = EventId::try_from(args[0]) {
let start = Instant::now(); if let Some(event) = db.rooms.get_pdu_json(&event_id)? {
let count = server_server::get_auth_chain( let room_id_str = event
vec![Arc::new(event_id)], .get("room_id")
db, .and_then(|val| val.as_str())
)? .ok_or_else(|| {
.count(); Error::bad_database(
let elapsed = start.elapsed(); "Invalid event in database",
db.admin.send(AdminCommand::SendMessage( )
message::MessageEventContent::text_plain(format!( })?;
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 {:?}", "Loaded auth chain with length {} in {:?}",
count, elapsed 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<bool> {
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). /// Returns an iterator of all rooms a server participates in (as far as we know).
#[tracing::instrument(skip(self))] #[tracing::instrument(skip(self))]
pub fn server_rooms<'a>( pub fn server_rooms<'a>(

View file

@ -119,7 +119,7 @@ impl FedDest {
} }
#[tracing::instrument(skip(globals, request))] #[tracing::instrument(skip(globals, request))]
pub async fn send_request<T: OutgoingRequest>( pub(crate) async fn send_request<T: OutgoingRequest>(
globals: &crate::database::globals::Globals, globals: &crate::database::globals::Globals,
destination: &ServerName, destination: &ServerName,
request: T, request: T,
@ -487,7 +487,7 @@ async fn query_srv_record(
} }
#[tracing::instrument(skip(globals))] #[tracing::instrument(skip(globals))]
pub async fn request_well_known( async fn request_well_known(
globals: &crate::database::globals::Globals, globals: &crate::database::globals::Globals,
destination: &str, destination: &str,
) -> Option<String> { ) -> Option<String> {
@ -512,6 +512,9 @@ pub async fn request_well_known(
Some(body.get("m.server")?.as_str()?.to_owned()) 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"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/federation/v1/version"))]
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
pub fn get_server_version_route( pub fn get_server_version_route(
@ -530,6 +533,12 @@ pub fn get_server_version_route(
.into()) .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 // 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"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server"))]
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
@ -578,12 +587,21 @@ pub fn get_server_keys_route(db: DatabaseGuard) -> Json<String> {
Json(serde_json::to_string(&response).expect("JSON is canonical")) 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/<_>"))] #[cfg_attr(feature = "conduit_bin", get("/_matrix/key/v2/server/<_>"))]
#[tracing::instrument(skip(db))] #[tracing::instrument(skip(db))]
pub fn get_server_keys_deprecated_route(db: DatabaseGuard) -> Json<String> { pub fn get_server_keys_deprecated_route(db: DatabaseGuard) -> Json<String> {
get_server_keys_route(db) get_server_keys_route(db)
} }
/// # `POST /_matrix/federation/v1/publicRooms`
///
/// Lists the public rooms on this server.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/federation/v1/publicRooms", data = "<body>") post("/_matrix/federation/v1/publicRooms", data = "<body>")
@ -628,6 +646,9 @@ pub async fn get_public_rooms_filtered_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/publicRooms`
///
/// Lists the public rooms on this server.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/publicRooms", data = "<body>") get("/_matrix/federation/v1/publicRooms", data = "<body>")
@ -672,6 +693,9 @@ pub async fn get_public_rooms_route(
.into()) .into())
} }
/// # `PUT /_matrix/federation/v1/send/{txnId}`
///
/// Push EDUs and PDUs to this server.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/federation/v1/send/<_>", data = "<body>") put("/_matrix/federation/v1/send/<_>", data = "<body>")
@ -921,7 +945,7 @@ type AsyncRecursiveType<'a, T> = Pin<Box<dyn Future<Output = T> + 'a + Send>>;
/// 14. Use state resolution to find new room state /// 14. Use state resolution to find new room state
// We use some AsyncRecursiveType hacks here so we can call this async funtion recursively // 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))] #[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, origin: &'a ServerName,
event_id: &'a EventId, event_id: &'a EventId,
room_id: &'a RoomId, room_id: &'a RoomId,
@ -1060,7 +1084,11 @@ pub async fn handle_incoming_pdu<'a>(
}) })
.map_err(|_| "Error sorting prev events".to_owned())?; .map_err(|_| "Error sorting prev events".to_owned())?;
let mut errors = 0;
for prev_id in dbg!(sorted) { for prev_id in dbg!(sorted) {
if errors >= 5 {
break;
}
if let Some((pdu, json)) = eventid_info.remove(&prev_id) { if let Some((pdu, json)) = eventid_info.remove(&prev_id) {
let start_time = Instant::now(); let start_time = Instant::now();
let event_id = pdu.event_id.clone(); let event_id = pdu.event_id.clone();
@ -1075,6 +1103,7 @@ pub async fn handle_incoming_pdu<'a>(
) )
.await .await
{ {
errors += 1;
warn!("Prev event {} failed: {}", event_id, e); warn!("Prev event {} failed: {}", event_id, e);
} }
let elapsed = start_time.elapsed(); let elapsed = start_time.elapsed();
@ -1397,9 +1426,13 @@ async fn upgrade_outlier_to_timeline_pdu(
let mut auth_chain_sets = Vec::new(); let mut auth_chain_sets = Vec::new();
for state in fork_states { for state in fork_states {
auth_chain_sets.push( auth_chain_sets.push(
get_auth_chain(state.iter().map(|(_, id)| id.clone()).collect(), db) get_auth_chain(
.map_err(|_| "Failed to load auth chain.".to_owned())? &room_id,
.collect(), state.iter().map(|(_, id)| id.clone()).collect(),
db,
)
.map_err(|_| "Failed to load auth chain.".to_owned())?
.collect(),
); );
} }
@ -1745,9 +1778,13 @@ async fn upgrade_outlier_to_timeline_pdu(
let mut auth_chain_sets = Vec::new(); let mut auth_chain_sets = Vec::new();
for state in fork_states { for state in fork_states {
auth_chain_sets.push( auth_chain_sets.push(
get_auth_chain(state.iter().map(|(_, id)| id.clone()).collect(), db) get_auth_chain(
.map_err(|_| "Failed to load auth chain.".to_owned())? &room_id,
.collect(), state.iter().map(|(_, id)| id.clone()).collect(),
db,
)
.map_err(|_| "Failed to load auth chain.".to_owned())?
.collect(),
); );
} }
@ -2187,10 +2224,11 @@ fn append_incoming_pdu(
} }
#[tracing::instrument(skip(starting_events, db))] #[tracing::instrument(skip(starting_events, db))]
pub fn get_auth_chain( pub(crate) fn get_auth_chain<'a>(
room_id: &RoomId,
starting_events: Vec<Arc<EventId>>, starting_events: Vec<Arc<EventId>>,
db: &Database, db: &'a Database,
) -> Result<impl Iterator<Item = Arc<EventId>> + '_> { ) -> Result<impl Iterator<Item = Arc<EventId>> + 'a> {
const NUM_BUCKETS: usize = 50; const NUM_BUCKETS: usize = 50;
let mut buckets = vec![BTreeSet::new(); NUM_BUCKETS]; let mut buckets = vec![BTreeSet::new(); NUM_BUCKETS];
@ -2231,7 +2269,7 @@ pub fn get_auth_chain(
chunk_cache.extend(cached.iter().cloned()); chunk_cache.extend(cached.iter().cloned());
} else { } else {
misses2 += 1; 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 db.rooms
.cache_auth_chain(vec![sevent_id], Arc::clone(&auth_chain))?; .cache_auth_chain(vec![sevent_id], Arc::clone(&auth_chain))?;
println!( println!(
@ -2267,13 +2305,20 @@ pub fn get_auth_chain(
} }
#[tracing::instrument(skip(event_id, db))] #[tracing::instrument(skip(event_id, db))]
fn get_auth_chain_inner(event_id: &EventId, db: &Database) -> Result<HashSet<u64>> { fn get_auth_chain_inner(
room_id: &RoomId,
event_id: &EventId,
db: &Database,
) -> Result<HashSet<u64>> {
let mut todo = vec![event_id.clone()]; let mut todo = vec![event_id.clone()];
let mut found = HashSet::new(); let mut found = HashSet::new();
while let Some(event_id) = todo.pop() { while let Some(event_id) = todo.pop() {
match db.rooms.get_pdu(&event_id) { match db.rooms.get_pdu(&event_id) {
Ok(Some(pdu)) => { 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 { for auth_event in &pdu.auth_events {
let sauthevent = db let sauthevent = db
.rooms .rooms
@ -2297,6 +2342,11 @@ fn get_auth_chain_inner(event_id: &EventId, db: &Database) -> Result<HashSet<u64
Ok(found) Ok(found)
} }
/// # `GET /_matrix/federation/v1/event/{eventId}`
///
/// Retrieves a single event from the server.
///
/// - Only works if a user of this server is currently invited or joined the room
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/event/<_>", data = "<body>") get("/_matrix/federation/v1/event/<_>", data = "<body>")
@ -2310,18 +2360,39 @@ pub fn get_event_route(
return Err(Error::bad_config("Federation is disabled.")); 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 { Ok(get_event::v1::Response {
origin: db.globals.server_name().to_owned(), origin: db.globals.server_name().to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch::now(), origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
pdu: PduEvent::convert_to_outgoing_federation_event( pdu: PduEvent::convert_to_outgoing_federation_event(event),
db.rooms
.get_pdu_json(&body.event_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Event not found."))?,
),
} }
.into()) .into())
} }
/// # `POST /_matrix/federation/v1/get_missing_events/{roomId}`
///
/// Retrieves events that the sender is missing.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/federation/v1/get_missing_events/<_>", data = "<body>") post("/_matrix/federation/v1/get_missing_events/<_>", data = "<body>")
@ -2335,22 +2406,44 @@ pub fn get_missing_events_route(
return Err(Error::bad_config("Federation is disabled.")); 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 queued_events = body.latest_events.clone();
let mut events = Vec::new(); let mut events = Vec::new();
let mut i = 0; let mut i = 0;
while i < queued_events.len() && events.len() < u64::from(body.limit) as usize { 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])? { if let Some(pdu) = db.rooms.get_pdu_json(&queued_events[i])? {
let event_id = let room_id_str = pdu
serde_json::from_value( .get("room_id")
serde_json::to_value(pdu.get("event_id").cloned().ok_or_else(|| { .and_then(|val| val.as_str())
Error::bad_database("Event in db has no event_id field.") .ok_or_else(|| Error::bad_database("Invalid event in database"))?;
})?)
.expect("canonical json is valid json value"),
)
.map_err(|_| Error::bad_database("Invalid event_id field in pdu in db."))?;
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; i += 1;
continue; continue;
} }
@ -2371,6 +2464,11 @@ pub fn get_missing_events_route(
Ok(get_missing_events::v1::Response { events }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/event_auth/<_>/<_>", data = "<body>") get("/_matrix/federation/v1/event_auth/<_>/<_>", data = "<body>")
@ -2384,7 +2482,29 @@ pub fn get_event_authorization_route(
return Err(Error::bad_config("Federation is disabled.")); 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 { Ok(get_event_authorization::v1::Response {
auth_chain: auth_chain_ids auth_chain: auth_chain_ids
@ -2395,6 +2515,9 @@ pub fn get_event_authorization_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/state/{roomId}`
///
/// Retrieves the current state of the room.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/state/<_>", data = "<body>") get("/_matrix/federation/v1/state/<_>", data = "<body>")
@ -2408,6 +2531,18 @@ pub fn get_room_state_route(
return Err(Error::bad_config("Federation is disabled.")); 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 let shortstatehash = db
.rooms .rooms
.pdu_shortstatehash(&body.event_id)? .pdu_shortstatehash(&body.event_id)?
@ -2427,7 +2562,7 @@ pub fn get_room_state_route(
}) })
.collect(); .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 { Ok(get_room_state::v1::Response {
auth_chain: auth_chain_ids auth_chain: auth_chain_ids
@ -2443,6 +2578,9 @@ pub fn get_room_state_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/state_ids/{roomId}`
///
/// Retrieves the current state of the room.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/state_ids/<_>", data = "<body>") get("/_matrix/federation/v1/state_ids/<_>", data = "<body>")
@ -2456,6 +2594,18 @@ pub fn get_room_state_ids_route(
return Err(Error::bad_config("Federation is disabled.")); 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 let shortstatehash = db
.rooms .rooms
.pdu_shortstatehash(&body.event_id)? .pdu_shortstatehash(&body.event_id)?
@ -2471,7 +2621,7 @@ pub fn get_room_state_ids_route(
.map(|(_, id)| (*id).clone()) .map(|(_, id)| (*id).clone())
.collect(); .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 { Ok(get_room_state_ids::v1::Response {
auth_chain_ids: auth_chain_ids.map(|id| (*id).clone()).collect(), auth_chain_ids: auth_chain_ids.map(|id| (*id).clone()).collect(),
@ -2480,6 +2630,9 @@ pub fn get_room_state_ids_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/make_join/{roomId}/{userId}`
///
/// Creates a join template.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/make_join/<_>/<_>", data = "<body>") get("/_matrix/federation/v1/make_join/<_>/<_>", data = "<body>")
@ -2719,7 +2872,11 @@ async fn create_join_event(
drop(mutex_lock); drop(mutex_lock);
let state_ids = db.rooms.state_full_ids(shortstatehash)?; 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 for server in db
.rooms .rooms
@ -2745,6 +2902,9 @@ async fn create_join_event(
}) })
} }
/// # `PUT /_matrix/federation/v1/send_join/{roomId}/{eventId}`
///
/// Submits a signed join event.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/federation/v1/send_join/<_>/<_>", data = "<body>") put("/_matrix/federation/v1/send_join/<_>/<_>", data = "<body>")
@ -2759,6 +2919,9 @@ pub async fn create_join_event_v1_route(
Ok(create_join_event::v1::Response { room_state }.into()) Ok(create_join_event::v1::Response { room_state }.into())
} }
/// # `PUT /_matrix/federation/v2/send_join/{roomId}/{eventId}`
///
/// Submits a signed join event.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/federation/v2/send_join/<_>/<_>", data = "<body>") put("/_matrix/federation/v2/send_join/<_>/<_>", data = "<body>")
@ -2773,6 +2936,9 @@ pub async fn create_join_event_v2_route(
Ok(create_join_event::v2::Response { room_state }.into()) 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( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
put("/_matrix/federation/v2/invite/<_>/<_>", data = "<body>") put("/_matrix/federation/v2/invite/<_>/<_>", data = "<body>")
@ -2882,6 +3048,9 @@ pub async fn create_invite_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/user/devices/{userId}`
///
/// Gets information on all devices of the user.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/user/devices/<_>", data = "<body>") get("/_matrix/federation/v1/user/devices/<_>", data = "<body>")
@ -2922,6 +3091,9 @@ pub fn get_devices_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/query/directory`
///
/// Resolve a room alias to a room id.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/query/directory", data = "<body>") get("/_matrix/federation/v1/query/directory", data = "<body>")
@ -2950,6 +3122,9 @@ pub fn get_room_information_route(
.into()) .into())
} }
/// # `GET /_matrix/federation/v1/query/profile`
///
/// Gets information on a profile.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
get("/_matrix/federation/v1/query/profile", data = "<body>") get("/_matrix/federation/v1/query/profile", data = "<body>")
@ -2990,6 +3165,9 @@ pub fn get_profile_information_route(
.into()) .into())
} }
/// # `POST /_matrix/federation/v1/user/keys/query`
///
/// Gets devices and identity keys for the given users.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/federation/v1/user/keys/query", data = "<body>") post("/_matrix/federation/v1/user/keys/query", data = "<body>")
@ -3021,6 +3199,9 @@ pub async fn get_keys_route(
.into()) .into())
} }
/// # `POST /_matrix/federation/v1/user/keys/claim`
///
/// Claims one-time keys.
#[cfg_attr( #[cfg_attr(
feature = "conduit_bin", feature = "conduit_bin",
post("/_matrix/federation/v1/user/keys/claim", data = "<body>") post("/_matrix/federation/v1/user/keys/claim", data = "<body>")
@ -3045,7 +3226,7 @@ pub async fn claim_keys_route(
} }
#[tracing::instrument(skip(event, pub_key_map, db))] #[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<String, CanonicalJsonValue>, event: &BTreeMap<String, CanonicalJsonValue>,
pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, String>>>, pub_key_map: &RwLock<BTreeMap<String, BTreeMap<String, String>>>,
db: &Database, db: &Database,