Database Refactor

combine service/users data w/ mod unit

split sliding sync related out of service/users

instrument database entry points

remove increment crap from database interface

de-wrap all database get() calls

de-wrap all database insert() calls

de-wrap all database remove() calls

refactor database interface for async streaming

add query key serializer for database

implement Debug for result handle

add query deserializer for database

add deserialization trait for option handle

start a stream utils suite

de-wrap/asyncify/type-query count_one_time_keys()

de-wrap/asyncify users count

add admin query users command suite

de-wrap/asyncify users exists

de-wrap/partially asyncify user filter related

asyncify/de-wrap users device/keys related

asyncify/de-wrap user auth/misc related

asyncify/de-wrap users blurhash

asyncify/de-wrap account_data get; merge Data into Service

partial asyncify/de-wrap uiaa; merge Data into Service

partially asyncify/de-wrap transaction_ids get; merge Data into Service

partially asyncify/de-wrap key_backups; merge Data into Service

asyncify/de-wrap pusher service getters; merge Data into Service

asyncify/de-wrap rooms alias getters/some iterators

asyncify/de-wrap rooms directory getters/iterator

partially asyncify/de-wrap rooms lazy-loading

partially asyncify/de-wrap rooms metadata

asyncify/dewrap rooms outlier

asyncify/dewrap rooms pdu_metadata

dewrap/partially asyncify rooms read receipt

de-wrap rooms search service

de-wrap/partially asyncify rooms user service

partial de-wrap rooms state_compressor

de-wrap rooms state_cache

de-wrap room state et al

de-wrap rooms timeline service

additional users device/keys related

de-wrap/asyncify sender

asyncify services

refactor database to TryFuture/TryStream

refactor services for TryFuture/TryStream

asyncify api handlers

additional asyncification for admin module

abstract stream related; support reverse streams

additional stream conversions

asyncify state-res related

Signed-off-by: Jason Volk <jason@zemos.net>
This commit is contained in:
Jason Volk 2024-08-08 17:18:30 +00:00 committed by strawberry
parent 6001014078
commit 946ca364e0
203 changed files with 12202 additions and 10709 deletions

View file

@ -2,7 +2,8 @@ use std::fmt::Write;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{debug_info, error, info, utils, warn, Error, PduBuilder, Result};
use conduit::{debug_info, error, info, is_equal_to, utils, utils::ReadyExt, warn, Error, PduBuilder, Result};
use futures::{FutureExt, StreamExt};
use register::RegistrationKind;
use ruma::{
api::client::{
@ -55,7 +56,7 @@ pub(crate) async fn get_register_available_route(
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
// Check if username is creative enough
if services.users.exists(&user_id)? {
if services.users.exists(&user_id).await {
return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken."));
}
@ -125,7 +126,7 @@ pub(crate) async fn register_route(
// forbid guests from registering if there is not a real admin user yet. give
// generic user error.
if is_guest && services.users.count()? < 2 {
if is_guest && services.users.count().await < 2 {
warn!(
"Guest account attempted to register before a real admin user has been registered, rejecting \
registration. Guest's initial device name: {:?}",
@ -142,7 +143,7 @@ pub(crate) async fn register_route(
.filter(|user_id| !user_id.is_historical() && services.globals.user_is_local(user_id))
.ok_or(Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
if services.users.exists(&proposed_user_id)? {
if services.users.exists(&proposed_user_id).await {
return Err(Error::BadRequest(ErrorKind::UserInUse, "Desired user ID is already taken."));
}
@ -162,7 +163,7 @@ pub(crate) async fn register_route(
services.globals.server_name(),
)
.unwrap();
if !services.users.exists(&proposed_user_id)? {
if !services.users.exists(&proposed_user_id).await {
break proposed_user_id;
}
},
@ -210,12 +211,15 @@ pub(crate) async fn register_route(
if !skip_auth {
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services.uiaa.try_auth(
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
)?;
let (worked, uiaainfo) = services
.uiaa
.try_auth(
&UserId::parse_with_server_name("", services.globals.server_name()).expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
@ -227,7 +231,7 @@ pub(crate) async fn register_route(
"".into(),
&uiaainfo,
&json,
)?;
);
return Err(Error::Uiaa(uiaainfo));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
@ -255,21 +259,23 @@ pub(crate) async fn register_route(
services
.users
.set_displayname(&user_id, Some(displayname.clone()))
.await?;
.set_displayname(&user_id, Some(displayname.clone()));
// Initial account data
services.account_data.update(
None,
&user_id,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id),
},
})
.expect("to json always works"),
)?;
services
.account_data
.update(
None,
&user_id,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id),
},
})
.expect("to json always works"),
)
.await?;
// Inhibit login does not work for guests
if !is_guest && body.inhibit_login {
@ -294,13 +300,16 @@ pub(crate) async fn register_route(
let token = utils::random_string(TOKEN_LENGTH);
// Create device for this account
services.users.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)?;
services
.users
.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
debug_info!(%user_id, %device_id, "User account was created");
@ -318,7 +327,8 @@ pub(crate) async fn register_route(
"New user \"{user_id}\" registered on this server from IP {client} and device display name \
\"{device_display_name}\""
)))
.await;
.await
.ok();
}
} else {
info!("New user \"{user_id}\" registered on this server.");
@ -329,7 +339,8 @@ pub(crate) async fn register_route(
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user \"{user_id}\" registered on this server from IP {client}"
)))
.await;
.await
.ok();
}
}
}
@ -346,7 +357,8 @@ pub(crate) async fn register_route(
"Guest user \"{user_id}\" with device display name \"{device_display_name}\" registered on \
this server from IP {client}"
)))
.await;
.await
.ok();
}
} else {
#[allow(clippy::collapsible_else_if)]
@ -357,7 +369,8 @@ pub(crate) async fn register_route(
"Guest user \"{user_id}\" with no device display name registered on this server from IP \
{client}",
)))
.await;
.await
.ok();
}
}
}
@ -365,10 +378,15 @@ pub(crate) async fn register_route(
// If this is the first real user, grant them admin privileges except for guest
// users Note: the server user, @conduit:servername, is generated first
if !is_guest {
if let Some(admin_room) = services.admin.get_admin_room()? {
if services.rooms.state_cache.room_joined_count(&admin_room)? == Some(1) {
if let Ok(admin_room) = services.admin.get_admin_room().await {
if services
.rooms
.state_cache
.room_joined_count(&admin_room)
.await
.is_ok_and(is_equal_to!(1))
{
services.admin.make_user_admin(&user_id).await?;
warn!("Granting {user_id} admin privileges as the first user");
}
}
@ -382,7 +400,8 @@ pub(crate) async fn register_route(
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room)?
.server_in_room(services.globals.server_name(), room)
.await
{
warn!("Skipping room {room} to automatically join as we have never joined before.");
continue;
@ -398,6 +417,7 @@ pub(crate) async fn register_route(
None,
&body.appservice_info,
)
.boxed()
.await
{
// don't return this error so we don't fail registrations
@ -461,16 +481,20 @@ pub(crate) async fn change_password_route(
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
// Success!
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
.create(sender_user, sender_device, &uiaainfo, &json);
return Err(Error::Uiaa(uiaainfo));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
@ -482,14 +506,12 @@ pub(crate) async fn change_password_route(
if body.logout_devices {
// Logout all devices except the current one
for id in services
services
.users
.all_device_ids(sender_user)
.filter_map(Result::ok)
.filter(|id| id != sender_device)
{
services.users.remove_device(sender_user, &id)?;
}
.ready_filter(|id| id != sender_device)
.for_each(|id| services.users.remove_device(sender_user, id))
.await;
}
info!("User {sender_user} changed their password.");
@ -500,7 +522,8 @@ pub(crate) async fn change_password_route(
.send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} changed their password."
)))
.await;
.await
.ok();
}
Ok(change_password::v3::Response {})
@ -520,7 +543,7 @@ pub(crate) async fn whoami_route(
Ok(whoami::v3::Response {
user_id: sender_user.clone(),
device_id,
is_guest: services.users.is_deactivated(sender_user)? && body.appservice_info.is_none(),
is_guest: services.users.is_deactivated(sender_user).await? && body.appservice_info.is_none(),
})
}
@ -561,7 +584,9 @@ pub(crate) async fn deactivate_route(
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
@ -570,7 +595,8 @@ pub(crate) async fn deactivate_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
.create(sender_user, sender_device, &uiaainfo, &json);
return Err(Error::Uiaa(uiaainfo));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
@ -581,10 +607,14 @@ pub(crate) async fn deactivate_route(
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
full_user_deactivate(&services, sender_user, all_joined_rooms).await?;
super::update_displayname(&services, sender_user, None, &all_joined_rooms).await?;
super::update_avatar_url(&services, sender_user, None, None, &all_joined_rooms).await?;
full_user_deactivate(&services, sender_user, &all_joined_rooms).await?;
info!("User {sender_user} deactivated their account.");
@ -594,7 +624,8 @@ pub(crate) async fn deactivate_route(
.send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} deactivated their account."
)))
.await;
.await
.ok();
}
Ok(deactivate::v3::Response {
@ -674,34 +705,27 @@ pub(crate) async fn check_registration_token_validity(
/// - Removing all profile data
/// - Leaving all rooms (and forgets all of them)
pub async fn full_user_deactivate(
services: &Services, user_id: &UserId, all_joined_rooms: Vec<OwnedRoomId>,
services: &Services, user_id: &UserId, all_joined_rooms: &[OwnedRoomId],
) -> Result<()> {
services.users.deactivate_account(user_id)?;
services.users.deactivate_account(user_id).await?;
super::update_displayname(services, user_id, None, all_joined_rooms).await?;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms).await?;
super::update_displayname(services, user_id, None, all_joined_rooms.clone()).await?;
super::update_avatar_url(services, user_id, None, None, all_joined_rooms.clone()).await?;
let all_profile_keys = services
services
.users
.all_profile_keys(user_id)
.filter_map(Result::ok);
for (profile_key, _profile_value) in all_profile_keys {
if let Err(e) = services.users.set_profile_key(user_id, &profile_key, None) {
warn!("Failed removing {user_id} profile key {profile_key}: {e}");
}
}
.ready_for_each(|(profile_key, _)| services.users.set_profile_key(user_id, &profile_key, None))
.await;
for room_id in all_joined_rooms {
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let room_power_levels = services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomPowerLevels, "")?
.as_ref()
.and_then(|event| serde_json::from_str(event.content.get()).ok()?)
.and_then(|content: RoomPowerLevelsEventContent| content.into());
.room_state_get_content::<RoomPowerLevelsEventContent>(room_id, &StateEventType::RoomPowerLevels, "")
.await
.ok();
let user_can_demote_self = room_power_levels
.as_ref()
@ -710,9 +734,9 @@ pub async fn full_user_deactivate(
}) || services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
.as_ref()
.is_some_and(|event| event.sender == user_id);
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
.is_ok_and(|event| event.sender == user_id);
if user_can_demote_self {
let mut power_levels_content = room_power_levels.unwrap_or_default();
@ -732,7 +756,7 @@ pub async fn full_user_deactivate(
timestamp: None,
},
user_id,
&room_id,
room_id,
&state_lock,
)
.await

View file

@ -1,11 +1,9 @@
use axum::extract::State;
use conduit::{debug, Error, Result};
use conduit::{debug, Err, Result};
use futures::StreamExt;
use rand::seq::SliceRandom;
use ruma::{
api::client::{
alias::{create_alias, delete_alias, get_alias},
error::ErrorKind,
},
api::client::alias::{create_alias, delete_alias, get_alias},
OwnedServerName, RoomAliasId, RoomId,
};
use service::Services;
@ -33,16 +31,17 @@ pub(crate) async fn create_alias_route(
.forbidden_alias_names()
.is_match(body.room_alias.alias())
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room alias is forbidden."));
return Err!(Request(Forbidden("Room alias is forbidden.")));
}
if services
.rooms
.alias
.resolve_local_alias(&body.room_alias)?
.is_some()
.resolve_local_alias(&body.room_alias)
.await
.is_ok()
{
return Err(Error::Conflict("Alias already exists."));
return Err!(Conflict("Alias already exists."));
}
services
@ -95,16 +94,16 @@ pub(crate) async fn get_alias_route(
.resolve_alias(&room_alias, servers.as_ref())
.await
else {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room with alias not found."));
return Err!(Request(NotFound("Room with alias not found.")));
};
let servers = room_available_servers(&services, &room_id, &room_alias, &pre_servers);
let servers = room_available_servers(&services, &room_id, &room_alias, &pre_servers).await;
debug!(?room_alias, ?room_id, "available servers: {servers:?}");
Ok(get_alias::v3::Response::new(room_id, servers))
}
fn room_available_servers(
async fn room_available_servers(
services: &Services, room_id: &RoomId, room_alias: &RoomAliasId, pre_servers: &Option<Vec<OwnedServerName>>,
) -> Vec<OwnedServerName> {
// find active servers in room state cache to suggest
@ -112,8 +111,9 @@ fn room_available_servers(
.rooms
.state_cache
.room_servers(room_id)
.filter_map(Result::ok)
.collect();
.map(ToOwned::to_owned)
.collect()
.await;
// push any servers we want in the list already (e.g. responded remote alias
// servers, room alias server itself)

View file

@ -1,18 +1,16 @@
use axum::extract::State;
use conduit::{err, Err};
use ruma::{
api::client::{
backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
get_latest_backup_info, update_backup_version,
},
error::ErrorKind,
api::client::backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session, create_backup_version,
delete_backup_keys, delete_backup_keys_for_room, delete_backup_keys_for_session, delete_backup_version,
get_backup_info, get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
get_latest_backup_info, update_backup_version,
},
UInt,
};
use crate::{Error, Result, Ruma};
use crate::{Result, Ruma};
/// # `POST /_matrix/client/r0/room_keys/version`
///
@ -40,7 +38,8 @@ pub(crate) async fn update_backup_version_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services
.key_backups
.update_backup(sender_user, &body.version, &body.algorithm)?;
.update_backup(sender_user, &body.version, &body.algorithm)
.await?;
Ok(update_backup_version::v3::Response {})
}
@ -55,14 +54,15 @@ pub(crate) async fn get_latest_backup_info_route(
let (version, algorithm) = services
.key_backups
.get_latest_backup(sender_user)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
.get_latest_backup(sender_user)
.await
.map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?;
Ok(get_latest_backup_info::v3::Response {
algorithm,
count: (UInt::try_from(services.key_backups.count_keys(sender_user, &version)?)
count: (UInt::try_from(services.key_backups.count_keys(sender_user, &version).await)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &version)?,
etag: services.key_backups.get_etag(sender_user, &version).await,
version,
})
}
@ -76,18 +76,21 @@ pub(crate) async fn get_backup_info_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let algorithm = services
.key_backups
.get_backup(sender_user, &body.version)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Key backup does not exist."))?;
.get_backup(sender_user, &body.version)
.await
.map_err(|_| err!(Request(NotFound("Key backup does not exist at version {:?}", body.version))))?;
Ok(get_backup_info::v3::Response {
algorithm,
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
version: body.version.clone(),
})
}
@ -105,7 +108,8 @@ pub(crate) async fn delete_backup_version_route(
services
.key_backups
.delete_backup(sender_user, &body.version)?;
.delete_backup(sender_user, &body.version)
.await;
Ok(delete_backup_version::v3::Response {})
}
@ -123,34 +127,36 @@ pub(crate) async fn add_backup_keys_route(
) -> Result<add_backup_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
!= services
.key_backups
.get_latest_backup_version(sender_user)?
.as_ref()
if services
.key_backups
.get_latest_backup_version(sender_user)
.await
.is_ok_and(|version| version != body.version)
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"You may only manipulate the most recently created version of the backup.",
));
return Err!(Request(InvalidParam(
"You may only manipulate the most recently created version of the backup."
)));
}
for (room_id, room) in &body.rooms {
for (session_id, key_data) in &room.sessions {
services
.key_backups
.add_key(sender_user, &body.version, room_id, session_id, key_data)?;
.add_key(sender_user, &body.version, room_id, session_id, key_data)
.await?;
}
}
Ok(add_backup_keys::v3::Response {
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
})
}
@ -167,32 +173,34 @@ pub(crate) async fn add_backup_keys_for_room_route(
) -> Result<add_backup_keys_for_room::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
!= services
.key_backups
.get_latest_backup_version(sender_user)?
.as_ref()
if services
.key_backups
.get_latest_backup_version(sender_user)
.await
.is_ok_and(|version| version != body.version)
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"You may only manipulate the most recently created version of the backup.",
));
return Err!(Request(InvalidParam(
"You may only manipulate the most recently created version of the backup."
)));
}
for (session_id, key_data) in &body.sessions {
services
.key_backups
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)?;
.add_key(sender_user, &body.version, &body.room_id, session_id, key_data)
.await?;
}
Ok(add_backup_keys_for_room::v3::Response {
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
})
}
@ -209,30 +217,32 @@ pub(crate) async fn add_backup_keys_for_session_route(
) -> Result<add_backup_keys_for_session::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
!= services
.key_backups
.get_latest_backup_version(sender_user)?
.as_ref()
if services
.key_backups
.get_latest_backup_version(sender_user)
.await
.is_ok_and(|version| version != body.version)
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"You may only manipulate the most recently created version of the backup.",
));
return Err!(Request(InvalidParam(
"You may only manipulate the most recently created version of the backup."
)));
}
services
.key_backups
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)?;
.add_key(sender_user, &body.version, &body.room_id, &body.session_id, &body.session_data)
.await?;
Ok(add_backup_keys_for_session::v3::Response {
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
})
}
@ -244,7 +254,10 @@ pub(crate) async fn get_backup_keys_route(
) -> Result<get_backup_keys::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let rooms = services.key_backups.get_all(sender_user, &body.version)?;
let rooms = services
.key_backups
.get_all(sender_user, &body.version)
.await;
Ok(get_backup_keys::v3::Response {
rooms,
@ -261,7 +274,8 @@ pub(crate) async fn get_backup_keys_for_room_route(
let sessions = services
.key_backups
.get_room(sender_user, &body.version, &body.room_id)?;
.get_room(sender_user, &body.version, &body.room_id)
.await;
Ok(get_backup_keys_for_room::v3::Response {
sessions,
@ -278,8 +292,9 @@ pub(crate) async fn get_backup_keys_for_session_route(
let key_data = services
.key_backups
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Backup key not found for this user's session."))?;
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)
.await
.map_err(|_| err!(Request(NotFound(debug_error!("Backup key not found for this user's session.")))))?;
Ok(get_backup_keys_for_session::v3::Response {
key_data,
@ -296,16 +311,19 @@ pub(crate) async fn delete_backup_keys_route(
services
.key_backups
.delete_all_keys(sender_user, &body.version)?;
.delete_all_keys(sender_user, &body.version)
.await;
Ok(delete_backup_keys::v3::Response {
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
})
}
@ -319,16 +337,19 @@ pub(crate) async fn delete_backup_keys_for_room_route(
services
.key_backups
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
.delete_room_keys(sender_user, &body.version, &body.room_id)
.await;
Ok(delete_backup_keys_for_room::v3::Response {
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
})
}
@ -342,15 +363,18 @@ pub(crate) async fn delete_backup_keys_for_session_route(
services
.key_backups
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)?;
.delete_room_key(sender_user, &body.version, &body.room_id, &body.session_id)
.await;
Ok(delete_backup_keys_for_session::v3::Response {
count: (UInt::try_from(
services
.key_backups
.count_keys(sender_user, &body.version)?,
)
.expect("user backup keys count should not be that high")),
etag: services.key_backups.get_etag(sender_user, &body.version)?,
count: services
.key_backups
.count_keys(sender_user, &body.version)
.await
.try_into()?,
etag: services
.key_backups
.get_etag(sender_user, &body.version)
.await,
})
}

View file

@ -1,4 +1,5 @@
use axum::extract::State;
use conduit::err;
use ruma::{
api::client::{
config::{get_global_account_data, get_room_account_data, set_global_account_data, set_room_account_data},
@ -25,7 +26,8 @@ pub(crate) async fn set_global_account_data_route(
&body.sender_user,
&body.event_type.to_string(),
body.data.json(),
)?;
)
.await?;
Ok(set_global_account_data::v3::Response {})
}
@ -42,7 +44,8 @@ pub(crate) async fn set_room_account_data_route(
&body.sender_user,
&body.event_type.to_string(),
body.data.json(),
)?;
)
.await?;
Ok(set_room_account_data::v3::Response {})
}
@ -57,8 +60,9 @@ pub(crate) async fn get_global_account_data_route(
let event: Box<RawJsonValue> = services
.account_data
.get(None, sender_user, body.event_type.to_string().into())?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
.get(None, sender_user, body.event_type.to_string().into())
.await
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
@ -79,8 +83,9 @@ pub(crate) async fn get_room_account_data_route(
let event: Box<RawJsonValue> = services
.account_data
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
.ok_or_else(|| Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
.get(Some(&body.room_id), sender_user, body.event_type.clone())
.await
.map_err(|_| err!(Request(NotFound("Data not found."))))?;
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
@ -91,7 +96,7 @@ pub(crate) async fn get_room_account_data_route(
})
}
fn set_account_data(
async fn set_account_data(
services: &Services, room_id: Option<&RoomId>, sender_user: &Option<OwnedUserId>, event_type: &str,
data: &RawJsonValue,
) -> Result<()> {
@ -100,15 +105,18 @@ fn set_account_data(
let data: serde_json::Value =
serde_json::from_str(data.get()).map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
services.account_data.update(
room_id,
sender_user,
event_type.into(),
&json!({
"type": event_type,
"content": data,
}),
)?;
services
.account_data
.update(
room_id,
sender_user,
event_type.into(),
&json!({
"type": event_type,
"content": data,
}),
)
.await?;
Ok(())
}

View file

@ -1,13 +1,14 @@
use std::collections::HashSet;
use axum::extract::State;
use conduit::{err, error, Err};
use futures::StreamExt;
use ruma::{
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
api::client::{context::get_context, filter::LazyLoadOptions},
events::StateEventType,
};
use tracing::error;
use crate::{Error, Result, Ruma};
use crate::{Result, Ruma};
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
///
@ -35,34 +36,33 @@ pub(crate) async fn get_context_route(
let base_token = services
.rooms
.timeline
.get_pdu_count(&body.event_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event id not found."))?;
.get_pdu_count(&body.event_id)
.await
.map_err(|_| err!(Request(NotFound("Base event id not found."))))?;
let base_event = services
.rooms
.timeline
.get_pdu(&body.event_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Base event not found."))?;
.get_pdu(&body.event_id)
.await
.map_err(|_| err!(Request(NotFound("Base event not found."))))?;
let room_id = base_event.room_id.clone();
let room_id = &base_event.room_id;
if !services
.rooms
.state_accessor
.user_can_see_event(sender_user, &room_id, &body.event_id)?
.user_can_see_event(sender_user, room_id, &body.event_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this event.",
));
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
&room_id,
&base_event.sender,
)? || lazy_load_send_redundant
if !services
.rooms
.lazy_loading
.lazy_load_was_sent_before(sender_user, sender_device, room_id, &base_event.sender)
.await || lazy_load_send_redundant
{
lazy_loaded.insert(base_event.sender.as_str().to_owned());
}
@ -75,25 +75,26 @@ pub(crate) async fn get_context_route(
let events_before: Vec<_> = services
.rooms
.timeline
.pdus_until(sender_user, &room_id, base_token)?
.pdus_until(sender_user, room_id, base_token)
.await?
.take(limit / 2)
.filter_map(Result::ok) // Remove buggy events
.filter(|(_, pdu)| {
.filter_map(|(count, pdu)| async move {
services
.rooms
.state_accessor
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
.unwrap_or(false)
.user_can_see_event(sender_user, room_id, &pdu.event_id)
.await
.then_some((count, pdu))
})
.collect();
.collect()
.await;
for (_, event) in &events_before {
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
&room_id,
&event.sender,
)? || lazy_load_send_redundant
if !services
.rooms
.lazy_loading
.lazy_load_was_sent_before(sender_user, sender_device, room_id, &event.sender)
.await || lazy_load_send_redundant
{
lazy_loaded.insert(event.sender.as_str().to_owned());
}
@ -111,25 +112,26 @@ pub(crate) async fn get_context_route(
let events_after: Vec<_> = services
.rooms
.timeline
.pdus_after(sender_user, &room_id, base_token)?
.pdus_after(sender_user, room_id, base_token)
.await?
.take(limit / 2)
.filter_map(Result::ok) // Remove buggy events
.filter(|(_, pdu)| {
.filter_map(|(count, pdu)| async move {
services
.rooms
.state_accessor
.user_can_see_event(sender_user, &room_id, &pdu.event_id)
.unwrap_or(false)
.user_can_see_event(sender_user, room_id, &pdu.event_id)
.await
.then_some((count, pdu))
})
.collect();
.collect()
.await;
for (_, event) in &events_after {
if !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
&room_id,
&event.sender,
)? || lazy_load_send_redundant
if !services
.rooms
.lazy_loading
.lazy_load_was_sent_before(sender_user, sender_device, room_id, &event.sender)
.await || lazy_load_send_redundant
{
lazy_loaded.insert(event.sender.as_str().to_owned());
}
@ -142,12 +144,14 @@ pub(crate) async fn get_context_route(
events_after
.last()
.map_or(&*body.event_id, |(_, e)| &*e.event_id),
)?
)
.await
.map_or(
services
.rooms
.state
.get_room_shortstatehash(&room_id)?
.get_room_shortstatehash(room_id)
.await
.expect("All rooms have state"),
|hash| hash,
);
@ -156,7 +160,8 @@ pub(crate) async fn get_context_route(
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.await?;
.await
.map_err(|e| err!(Database("State not found: {e}")))?;
let end_token = events_after
.last()
@ -173,18 +178,19 @@ pub(crate) async fn get_context_route(
let (event_type, state_key) = services
.rooms
.short
.get_statekey_from_short(shortstatekey)?;
.get_statekey_from_short(shortstatekey)
.await?;
if event_type != StateEventType::RoomMember {
let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id);
let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else {
error!("Pdu in state not found: {id}");
continue;
};
state.push(pdu.to_state_event());
} else if !lazy_load_enabled || lazy_loaded.contains(&state_key) {
let Some(pdu) = services.rooms.timeline.get_pdu(&id)? else {
error!("Pdu in state not found: {}", id);
let Ok(pdu) = services.rooms.timeline.get_pdu(&id).await else {
error!("Pdu in state not found: {id}");
continue;
};

View file

@ -1,4 +1,6 @@
use axum::extract::State;
use conduit::{err, Err};
use futures::StreamExt;
use ruma::api::client::{
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
error::ErrorKind,
@ -19,8 +21,8 @@ pub(crate) async fn get_devices_route(
let devices: Vec<device::Device> = services
.users
.all_devices_metadata(sender_user)
.filter_map(Result::ok) // Filter out buggy devices
.collect();
.collect()
.await;
Ok(get_devices::v3::Response {
devices,
@ -37,8 +39,9 @@ pub(crate) async fn get_device_route(
let device = services
.users
.get_device_metadata(sender_user, &body.body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
.get_device_metadata(sender_user, &body.body.device_id)
.await
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
Ok(get_device::v3::Response {
device,
@ -55,14 +58,16 @@ pub(crate) async fn update_device_route(
let mut device = services
.users
.get_device_metadata(sender_user, &body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
.get_device_metadata(sender_user, &body.device_id)
.await
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
device.display_name.clone_from(&body.display_name);
services
.users
.update_device_metadata(sender_user, &body.device_id, &device)?;
.update_device_metadata(sender_user, &body.device_id, &device)
.await?;
Ok(update_device::v3::Response {})
}
@ -97,22 +102,28 @@ pub(crate) async fn delete_device_route(
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err!(Uiaa(uiaainfo));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
.create(sender_user, sender_device, &uiaainfo, &json);
return Err!(Uiaa(uiaainfo));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
return Err!(Request(NotJson("Not json.")));
}
services.users.remove_device(sender_user, &body.device_id)?;
services
.users
.remove_device(sender_user, &body.device_id)
.await;
Ok(delete_device::v3::Response {})
}
@ -149,7 +160,9 @@ pub(crate) async fn delete_devices_route(
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
@ -158,14 +171,15 @@ pub(crate) async fn delete_devices_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
.create(sender_user, sender_device, &uiaainfo, &json);
return Err(Error::Uiaa(uiaainfo));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
for device_id in &body.devices {
services.users.remove_device(sender_user, device_id)?;
services.users.remove_device(sender_user, device_id).await;
}
Ok(delete_devices::v3::Response {})

View file

@ -1,6 +1,7 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{err, info, warn, Err, Error, Result};
use conduit::{info, warn, Err, Error, Result};
use futures::{StreamExt, TryFutureExt};
use ruma::{
api::{
client::{
@ -18,7 +19,7 @@ use ruma::{
},
StateEventType,
},
uint, RoomId, ServerName, UInt, UserId,
uint, OwnedRoomId, RoomId, ServerName, UInt, UserId,
};
use service::Services;
@ -119,16 +120,22 @@ pub(crate) async fn set_room_visibility_route(
) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.rooms.metadata.exists(&body.room_id)? {
if !services.rooms.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
}
if services.users.is_deactivated(sender_user).unwrap_or(false) && body.appservice_info.is_none() {
if services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& body.appservice_info.is_none()
{
return Err!(Request(Forbidden("Guests cannot publish to room directories")));
}
if !user_can_publish_room(&services, sender_user, &body.room_id)? {
if !user_can_publish_room(&services, sender_user, &body.room_id).await? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"User is not allowed to publish this room",
@ -138,7 +145,7 @@ pub(crate) async fn set_room_visibility_route(
match &body.visibility {
room::Visibility::Public => {
if services.globals.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user)?
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
info!(
@ -164,7 +171,7 @@ pub(crate) async fn set_room_visibility_route(
));
}
services.rooms.directory.set_public(&body.room_id)?;
services.rooms.directory.set_public(&body.room_id);
if services.globals.config.admin_room_notices {
services
@ -174,7 +181,7 @@ pub(crate) async fn set_room_visibility_route(
}
info!("{sender_user} made {0} public to the room directory", body.room_id);
},
room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id)?,
room::Visibility::Private => services.rooms.directory.set_not_public(&body.room_id),
_ => {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@ -192,13 +199,13 @@ pub(crate) async fn set_room_visibility_route(
pub(crate) async fn get_room_visibility_route(
State(services): State<crate::State>, body: Ruma<get_room_visibility::v3::Request>,
) -> Result<get_room_visibility::v3::Response> {
if !services.rooms.metadata.exists(&body.room_id)? {
if !services.rooms.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
}
Ok(get_room_visibility::v3::Response {
visibility: if services.rooms.directory.is_public_room(&body.room_id)? {
visibility: if services.rooms.directory.is_public_room(&body.room_id).await {
room::Visibility::Public
} else {
room::Visibility::Private
@ -257,101 +264,41 @@ pub(crate) async fn get_public_rooms_filtered_helper(
}
}
let mut all_rooms: Vec<_> = services
let mut all_rooms: Vec<PublicRoomsChunk> = services
.rooms
.directory
.public_rooms()
.map(|room_id| {
let room_id = room_id?;
let chunk = PublicRoomsChunk {
canonical_alias: services
.rooms
.state_accessor
.get_canonical_alias(&room_id)?,
name: services.rooms.state_accessor.get_name(&room_id)?,
num_joined_members: services
.rooms
.state_cache
.room_joined_count(&room_id)?
.unwrap_or_else(|| {
warn!("Room {} has no member count", room_id);
0
})
.try_into()
.expect("user count should not be that big"),
topic: services
.rooms
.state_accessor
.get_room_topic(&room_id)
.unwrap_or(None),
world_readable: services.rooms.state_accessor.is_world_readable(&room_id)?,
guest_can_join: services
.rooms
.state_accessor
.guest_can_join(&room_id)?,
avatar_url: services
.rooms
.state_accessor
.get_avatar(&room_id)?
.into_option()
.unwrap_or_default()
.url,
join_rule: services
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomJoinRulesEventContent| match c.join_rule {
JoinRule::Public => Some(PublicRoomJoinRule::Public),
JoinRule::Knock => Some(PublicRoomJoinRule::Knock),
_ => None,
})
.map_err(|e| {
err!(Database(error!("Invalid room join rule event in database: {e}")))
})
})
.transpose()?
.flatten()
.ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
room_type: services
.rooms
.state_accessor
.get_room_type(&room_id)?,
room_id,
};
Ok(chunk)
})
.filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms
.filter(|chunk| {
.map(ToOwned::to_owned)
.then(|room_id| public_rooms_chunk(services, room_id))
.filter_map(|chunk| async move {
if let Some(query) = filter.generic_search_term.as_ref().map(|q| q.to_lowercase()) {
if let Some(name) = &chunk.name {
if name.as_str().to_lowercase().contains(&query) {
return true;
return Some(chunk);
}
}
if let Some(topic) = &chunk.topic {
if topic.to_lowercase().contains(&query) {
return true;
return Some(chunk);
}
}
if let Some(canonical_alias) = &chunk.canonical_alias {
if canonical_alias.as_str().to_lowercase().contains(&query) {
return true;
return Some(chunk);
}
}
false
} else {
// No search term
true
return None;
}
// No search term
Some(chunk)
})
// We need to collect all, so we can sort by member count
.collect();
.collect()
.await;
all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
@ -394,22 +341,23 @@ pub(crate) async fn get_public_rooms_filtered_helper(
/// Check whether the user can publish to the room directory via power levels of
/// room history visibility event or room creator
fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Some(event) = services
async fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId) -> Result<bool> {
if let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")?
.room_state_get(room_id, &StateEventType::RoomPowerLevels, "")
.await
{
serde_json::from_str(event.content.get())
.map_err(|_| Error::bad_database("Invalid event content for m.room.power_levels"))
.map(|content: RoomPowerLevelsEventContent| {
RoomPowerLevels::from(content).user_can_send_state(user_id, StateEventType::RoomHistoryVisibility)
})
} else if let Some(event) =
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")?
} else if let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomCreate, "")
.await
{
Ok(event.sender == user_id)
} else {
@ -419,3 +367,61 @@ fn user_can_publish_room(services: &Services, user_id: &UserId, room_id: &RoomId
));
}
}
async fn public_rooms_chunk(services: &Services, room_id: OwnedRoomId) -> PublicRoomsChunk {
PublicRoomsChunk {
canonical_alias: services
.rooms
.state_accessor
.get_canonical_alias(&room_id)
.await
.ok(),
name: services.rooms.state_accessor.get_name(&room_id).await.ok(),
num_joined_members: services
.rooms
.state_cache
.room_joined_count(&room_id)
.await
.unwrap_or(0)
.try_into()
.expect("joined count overflows ruma UInt"),
topic: services
.rooms
.state_accessor
.get_room_topic(&room_id)
.await
.ok(),
world_readable: services
.rooms
.state_accessor
.is_world_readable(&room_id)
.await,
guest_can_join: services.rooms.state_accessor.guest_can_join(&room_id).await,
avatar_url: services
.rooms
.state_accessor
.get_avatar(&room_id)
.await
.into_option()
.unwrap_or_default()
.url,
join_rule: services
.rooms
.state_accessor
.room_state_get_content(&room_id, &StateEventType::RoomJoinRules, "")
.map_ok(|c: RoomJoinRulesEventContent| match c.join_rule {
JoinRule::Public => PublicRoomJoinRule::Public,
JoinRule::Knock => PublicRoomJoinRule::Knock,
_ => "invite".into(),
})
.await
.unwrap_or_default(),
room_type: services
.rooms
.state_accessor
.get_room_type(&room_id)
.await
.ok(),
room_id,
}
}

View file

@ -1,10 +1,8 @@
use axum::extract::State;
use ruma::api::client::{
error::ErrorKind,
filter::{create_filter, get_filter},
};
use conduit::err;
use ruma::api::client::filter::{create_filter, get_filter};
use crate::{Error, Result, Ruma};
use crate::{Result, Ruma};
/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
///
@ -15,11 +13,13 @@ pub(crate) async fn get_filter_route(
State(services): State<crate::State>, body: Ruma<get_filter::v3::Request>,
) -> Result<get_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let Some(filter) = services.users.get_filter(sender_user, &body.filter_id)? else {
return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found."));
};
Ok(get_filter::v3::Response::new(filter))
services
.users
.get_filter(sender_user, &body.filter_id)
.await
.map(get_filter::v3::Response::new)
.map_err(|_| err!(Request(NotFound("Filter not found."))))
}
/// # `PUT /_matrix/client/r0/user/{userId}/filter`
@ -29,7 +29,8 @@ pub(crate) async fn create_filter_route(
State(services): State<crate::State>, body: Ruma<create_filter::v3::Request>,
) -> Result<create_filter::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(create_filter::v3::Response::new(
services.users.create_filter(sender_user, &body.filter)?,
))
let filter_id = services.users.create_filter(sender_user, &body.filter);
Ok(create_filter::v3::Response::new(filter_id))
}

View file

@ -4,8 +4,8 @@ use std::{
};
use axum::extract::State;
use conduit::{utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
use futures_util::{stream::FuturesUnordered, StreamExt};
use conduit::{err, utils, utils::math::continue_exponential_backoff_secs, Err, Error, Result};
use futures::{stream::FuturesUnordered, StreamExt};
use ruma::{
api::{
client::{
@ -21,7 +21,10 @@ use ruma::{
use serde_json::json;
use super::SESSION_ID_LENGTH;
use crate::{service::Services, Ruma};
use crate::{
service::{users::parse_master_key, Services},
Ruma,
};
/// # `POST /_matrix/client/r0/keys/upload`
///
@ -39,7 +42,8 @@ pub(crate) async fn upload_keys_route(
for (key_key, key_value) in &body.one_time_keys {
services
.users
.add_one_time_key(sender_user, sender_device, key_key, key_value)?;
.add_one_time_key(sender_user, sender_device, key_key, key_value)
.await?;
}
if let Some(device_keys) = &body.device_keys {
@ -47,19 +51,22 @@ pub(crate) async fn upload_keys_route(
// This check is needed to assure that signatures are kept
if services
.users
.get_device_keys(sender_user, sender_device)?
.is_none()
.get_device_keys(sender_user, sender_device)
.await
.is_err()
{
services
.users
.add_device_keys(sender_user, sender_device, device_keys)?;
.add_device_keys(sender_user, sender_device, device_keys)
.await;
}
}
Ok(upload_keys::v3::Response {
one_time_key_counts: services
.users
.count_one_time_keys(sender_user, sender_device)?,
.count_one_time_keys(sender_user, sender_device)
.await,
})
}
@ -120,7 +127,9 @@ pub(crate) async fn upload_signing_keys_route(
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
.try_auth(sender_user, sender_device, auth, &uiaainfo)
.await?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
}
@ -129,20 +138,24 @@ pub(crate) async fn upload_signing_keys_route(
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
.create(sender_user, sender_device, &uiaainfo, &json);
return Err(Error::Uiaa(uiaainfo));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
if let Some(master_key) = &body.master_key {
services.users.add_cross_signing_keys(
sender_user,
master_key,
&body.self_signing_key,
&body.user_signing_key,
true, // notify so that other users see the new keys
)?;
services
.users
.add_cross_signing_keys(
sender_user,
master_key,
&body.self_signing_key,
&body.user_signing_key,
true, // notify so that other users see the new keys
)
.await?;
}
Ok(upload_signing_keys::v3::Response {})
@ -179,9 +192,11 @@ pub(crate) async fn upload_signatures_route(
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Invalid signature value."))?
.to_owned(),
);
services
.users
.sign_key(user_id, key_id, signature, sender_user)?;
.sign_key(user_id, key_id, signature, sender_user)
.await?;
}
}
}
@ -204,56 +219,51 @@ pub(crate) async fn get_key_changes_route(
let mut device_list_updates = HashSet::new();
let from = body
.from
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?;
let to = body
.to
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?;
device_list_updates.extend(
services
.users
.keys_changed(
sender_user.as_str(),
body.from
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?,
Some(
body.to
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?,
),
)
.filter_map(Result::ok),
.keys_changed(sender_user.as_str(), from, Some(to))
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
for room_id in services
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
{
let mut rooms_joined = services.rooms.state_cache.rooms_joined(sender_user).boxed();
while let Some(room_id) = rooms_joined.next().await {
device_list_updates.extend(
services
.users
.keys_changed(
room_id.as_ref(),
body.from
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?,
Some(
body.to
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?,
),
)
.filter_map(Result::ok),
.keys_changed(room_id.as_ref(), from, Some(to))
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
}
Ok(get_key_changes::v3::Response {
changed: device_list_updates.into_iter().collect(),
left: Vec::new(), // TODO
})
}
pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
pub(crate) async fn get_keys_helper<F>(
services: &Services, sender_user: Option<&UserId>, device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
allowed_signatures: F, include_display_names: bool,
) -> Result<get_keys::v3::Response> {
) -> Result<get_keys::v3::Response>
where
F: Fn(&UserId) -> bool + Send + Sync,
{
let mut master_keys = BTreeMap::new();
let mut self_signing_keys = BTreeMap::new();
let mut user_signing_keys = BTreeMap::new();
@ -274,56 +284,60 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
if device_ids.is_empty() {
let mut container = BTreeMap::new();
for device_id in services.users.all_device_ids(user_id) {
let device_id = device_id?;
if let Some(mut keys) = services.users.get_device_keys(user_id, &device_id)? {
let mut devices = services.users.all_device_ids(user_id).boxed();
while let Some(device_id) = devices.next().await {
if let Ok(mut keys) = services.users.get_device_keys(user_id, device_id).await {
let metadata = services
.users
.get_device_metadata(user_id, &device_id)?
.ok_or_else(|| Error::bad_database("all_device_keys contained nonexistent device."))?;
.get_device_metadata(user_id, device_id)
.await
.map_err(|_| err!(Database("all_device_keys contained nonexistent device.")))?;
add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
.map_err(|_| Error::bad_database("invalid device keys in database"))?;
.map_err(|_| err!(Database("invalid device keys in database")))?;
container.insert(device_id, keys);
container.insert(device_id.to_owned(), keys);
}
}
device_keys.insert(user_id.to_owned(), container);
} else {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = services.users.get_device_keys(user_id, device_id)? {
if let Ok(mut keys) = services.users.get_device_keys(user_id, device_id).await {
let metadata = services
.users
.get_device_metadata(user_id, device_id)?
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to get keys for nonexistent device.",
))?;
.get_device_metadata(user_id, device_id)
.await
.map_err(|_| err!(Request(InvalidParam("Tried to get keys for nonexistent device."))))?;
add_unsigned_device_display_name(&mut keys, metadata, include_display_names)
.map_err(|_| Error::bad_database("invalid device keys in database"))?;
.map_err(|_| err!(Database("invalid device keys in database")))?;
container.insert(device_id.to_owned(), keys);
}
device_keys.insert(user_id.to_owned(), container);
}
}
if let Some(master_key) = services
if let Ok(master_key) = services
.users
.get_master_key(sender_user, user_id, &allowed_signatures)?
.get_master_key(sender_user, user_id, &allowed_signatures)
.await
{
master_keys.insert(user_id.to_owned(), master_key);
}
if let Some(self_signing_key) =
services
.users
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
if let Ok(self_signing_key) = services
.users
.get_self_signing_key(sender_user, user_id, &allowed_signatures)
.await
{
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
}
if Some(user_id) == sender_user {
if let Some(user_signing_key) = services.users.get_user_signing_key(user_id)? {
if let Ok(user_signing_key) = services.users.get_user_signing_key(user_id).await {
user_signing_keys.insert(user_id.to_owned(), user_signing_key);
}
}
@ -386,23 +400,26 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool + Send>(
while let Some((server, response)) = futures.next().await {
if let Ok(Ok(response)) = response {
for (user, masterkey) in response.master_keys {
let (master_key_id, mut master_key) = services.users.parse_master_key(&user, &masterkey)?;
let (master_key_id, mut master_key) = parse_master_key(&user, &masterkey)?;
if let Some(our_master_key) =
services
.users
.get_key(&master_key_id, sender_user, &user, &allowed_signatures)?
if let Ok(our_master_key) = services
.users
.get_key(&master_key_id, sender_user, &user, &allowed_signatures)
.await
{
let (_, our_master_key) = services.users.parse_master_key(&user, &our_master_key)?;
let (_, our_master_key) = parse_master_key(&user, &our_master_key)?;
master_key.signatures.extend(our_master_key.signatures);
}
let json = serde_json::to_value(master_key).expect("to_value always works");
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
services.users.add_cross_signing_keys(
&user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key request resulting in an
* endless loop */
)?;
services
.users
.add_cross_signing_keys(
&user, &raw, &None, &None,
false, /* Dont notify. A notification would trigger another key request resulting in an
* endless loop */
)
.await?;
master_keys.insert(user.clone(), raw);
}
@ -465,9 +482,10 @@ pub(crate) async fn claim_keys_helper(
let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map {
if let Some(one_time_keys) = services
if let Ok(one_time_keys) = services
.users
.take_one_time_key(user_id, device_id, key_algorithm)?
.take_one_time_key(user_id, device_id, key_algorithm)
.await
{
let mut c = BTreeMap::new();
c.insert(one_time_keys.0, one_time_keys.1);

View file

@ -11,9 +11,10 @@ use conduit::{
debug, debug_error, debug_warn, err, error, info,
pdu::{gen_event_id_canonical_json, PduBuilder},
trace, utils,
utils::math::continue_exponential_backoff_secs,
utils::{math::continue_exponential_backoff_secs, IterStream, ReadyExt},
warn, Err, Error, PduEvent, Result,
};
use futures::{FutureExt, StreamExt};
use ruma::{
api::{
client::{
@ -55,9 +56,9 @@ async fn banned_room_check(
services: &Services, user_id: &UserId, room_id: Option<&RoomId>, server_name: Option<&ServerName>,
client_ip: IpAddr,
) -> Result<()> {
if !services.users.is_admin(user_id)? {
if !services.users.is_admin(user_id).await {
if let Some(room_id) = room_id {
if services.rooms.metadata.is_banned(room_id)?
if services.rooms.metadata.is_banned(room_id).await
|| services
.globals
.config
@ -79,23 +80,22 @@ async fn banned_room_check(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
.await;
.await
.ok();
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
full_user_deactivate(services, user_id, all_joined_rooms).await?;
full_user_deactivate(services, user_id, &all_joined_rooms).await?;
}
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This room is banned on this homeserver.",
));
return Err!(Request(Forbidden("This room is banned on this homeserver.")));
}
} else if let Some(server_name) = server_name {
if services
@ -119,23 +119,22 @@ async fn banned_room_check(
"Automatically deactivating user {user_id} due to attempted banned room join from IP \
{client_ip}"
)))
.await;
.await
.ok();
}
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(user_id)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
full_user_deactivate(services, user_id, all_joined_rooms).await?;
full_user_deactivate(services, user_id, &all_joined_rooms).await?;
}
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"This remote server is banned on this homeserver.",
));
return Err!(Request(Forbidden("This remote server is banned on this homeserver.")));
}
}
}
@ -172,14 +171,16 @@ pub(crate) async fn join_room_by_id_route(
.rooms
.state_cache
.servers_invite_via(&body.room_id)
.filter_map(Result::ok)
.collect::<Vec<_>>();
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await;
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &body.room_id)?
.invite_state(sender_user, &body.room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| serde_json::from_str(event.json().get()).ok())
@ -202,6 +203,7 @@ pub(crate) async fn join_room_by_id_route(
body.third_party_signed.as_ref(),
&body.appservice_info,
)
.boxed()
.await
}
@ -233,14 +235,17 @@ pub(crate) async fn join_room_by_id_or_alias_route(
.rooms
.state_cache
.servers_invite_via(&room_id)
.filter_map(Result::ok),
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)?
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| serde_json::from_str(event.json().get()).ok())
@ -270,19 +275,23 @@ pub(crate) async fn join_room_by_id_or_alias_route(
if let Some(pre_servers) = &mut pre_servers {
servers.append(pre_servers);
}
servers.extend(
services
.rooms
.state_cache
.servers_invite_via(&room_id)
.filter_map(Result::ok),
.map(ToOwned::to_owned)
.collect::<Vec<_>>()
.await,
);
servers.extend(
services
.rooms
.state_cache
.invite_state(sender_user, &room_id)?
.invite_state(sender_user, &room_id)
.await
.unwrap_or_default()
.iter()
.filter_map(|event| serde_json::from_str(event.json().get()).ok())
@ -305,6 +314,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
body.third_party_signed.as_ref(),
appservice_info,
)
.boxed()
.await?;
Ok(join_room_by_id_or_alias::v3::Response {
@ -337,7 +347,7 @@ pub(crate) async fn invite_user_route(
) -> Result<invite_user::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services.users.is_admin(sender_user)? && services.globals.block_non_admin_invites() {
if !services.users.is_admin(sender_user).await && services.globals.block_non_admin_invites() {
info!(
"User {sender_user} is not an admin and attempted to send an invite to room {}",
&body.room_id
@ -375,15 +385,13 @@ pub(crate) async fn kick_user_route(
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())?
.ok_or(Error::BadRequest(
ErrorKind::BadState,
"Cannot kick member that's not in the room.",
))?
.room_state_get(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())
.await
.map_err(|_| err!(Request(BadState("Cannot kick member that's not in the room."))))?
.content
.get(),
)
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
.map_err(|_| err!(Database("Invalid member event in database.")))?;
event.membership = MembershipState::Leave;
event.reason.clone_from(&body.reason);
@ -421,10 +429,13 @@ pub(crate) async fn ban_user_route(
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let blurhash = services.users.blurhash(&body.user_id).await.ok();
let event = services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())?
.room_state_get(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())
.await
.map_or(
Ok(RoomMemberEventContent {
membership: MembershipState::Ban,
@ -432,7 +443,7 @@ pub(crate) async fn ban_user_route(
avatar_url: None,
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(&body.user_id).unwrap_or_default(),
blurhash: blurhash.clone(),
reason: body.reason.clone(),
join_authorized_via_users_server: None,
}),
@ -442,12 +453,12 @@ pub(crate) async fn ban_user_route(
membership: MembershipState::Ban,
displayname: None,
avatar_url: None,
blurhash: services.users.blurhash(&body.user_id).unwrap_or_default(),
blurhash: blurhash.clone(),
reason: body.reason.clone(),
join_authorized_via_users_server: None,
..event
})
.map_err(|_| Error::bad_database("Invalid member event in database."))
.map_err(|e| err!(Database("Invalid member event in database: {e:?}")))
},
)?;
@ -488,12 +499,13 @@ pub(crate) async fn unban_user_route(
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())?
.ok_or(Error::BadRequest(ErrorKind::BadState, "Cannot unban a user who is not banned."))?
.room_state_get(&body.room_id, &StateEventType::RoomMember, body.user_id.as_ref())
.await
.map_err(|_| err!(Request(BadState("Cannot unban a user who is not banned."))))?
.content
.get(),
)
.map_err(|_| Error::bad_database("Invalid member event in database."))?;
.map_err(|e| err!(Database("Invalid member event in database: {e:?}")))?;
event.membership = MembershipState::Leave;
event.reason.clone_from(&body.reason);
@ -539,18 +551,16 @@ pub(crate) async fn forget_room_route(
if services
.rooms
.state_cache
.is_joined(sender_user, &body.room_id)?
.is_joined(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::Unknown,
"You must leave the room before forgetting it",
));
return Err!(Request(Unknown("You must leave the room before forgetting it")));
}
services
.rooms
.state_cache
.forget(&body.room_id, sender_user)?;
.forget(&body.room_id, sender_user);
Ok(forget_room::v3::Response::new())
}
@ -568,8 +578,9 @@ pub(crate) async fn joined_rooms_route(
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
.collect(),
.map(ToOwned::to_owned)
.collect()
.await,
})
}
@ -587,12 +598,10 @@ pub(crate) async fn get_member_events_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
.user_can_see_state_events(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this room.",
));
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
Ok(get_member_events::v3::Response {
@ -622,30 +631,27 @@ pub(crate) async fn joined_members_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
.user_can_see_state_events(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this room.",
));
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
let joined: BTreeMap<OwnedUserId, RoomMember> = services
.rooms
.state_cache
.room_members(&body.room_id)
.filter_map(|user| {
let user = user.ok()?;
Some((
user.clone(),
.then(|user| async move {
(
user.to_owned(),
RoomMember {
display_name: services.users.displayname(&user).unwrap_or_default(),
avatar_url: services.users.avatar_url(&user).unwrap_or_default(),
display_name: services.users.displayname(user).await.ok(),
avatar_url: services.users.avatar_url(user).await.ok(),
},
))
)
})
.collect();
.collect()
.await;
Ok(joined_members::v3::Response {
joined,
@ -658,13 +664,23 @@ pub async fn join_room_by_id_helper(
) -> Result<join_room_by_id::v3::Response> {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let user_is_guest = services.users.is_deactivated(sender_user).unwrap_or(false) && appservice_info.is_none();
let user_is_guest = services
.users
.is_deactivated(sender_user)
.await
.unwrap_or(false)
&& appservice_info.is_none();
if matches!(services.rooms.state_accessor.guest_can_join(room_id), Ok(false)) && user_is_guest {
if user_is_guest && !services.rooms.state_accessor.guest_can_join(room_id).await {
return Err!(Request(Forbidden("Guests are not allowed to join this room")));
}
if matches!(services.rooms.state_cache.is_joined(sender_user, room_id), Ok(true)) {
if services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
debug_warn!("{sender_user} is already joined in {room_id}");
return Ok(join_room_by_id::v3::Response {
room_id: room_id.into(),
@ -674,15 +690,17 @@ pub async fn join_room_by_id_helper(
if services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)?
|| servers.is_empty()
.server_in_room(services.globals.server_name(), room_id)
.await || servers.is_empty()
|| (servers.len() == 1 && services.globals.server_is_ours(&servers[0]))
{
join_room_by_id_helper_local(services, sender_user, room_id, reason, servers, third_party_signed, state_lock)
.boxed()
.await
} else {
// Ask a remote server if we are not participating in this room
join_room_by_id_helper_remote(services, sender_user, room_id, reason, servers, third_party_signed, state_lock)
.boxed()
.await
}
}
@ -739,11 +757,11 @@ async fn join_room_by_id_helper_remote(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server: join_authorized_via_users_server.clone(),
})
@ -791,10 +809,11 @@ async fn join_room_by_id_helper_remote(
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.to_owned(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone()),
omit_members: false,
.convert_to_outgoing_federation_event(join_event.clone())
.await,
},
)
.await?;
@ -864,7 +883,11 @@ async fn join_room_by_id_helper_remote(
}
}
services.rooms.short.get_or_create_shortroomid(room_id)?;
services
.rooms
.short
.get_or_create_shortroomid(room_id)
.await;
info!("Parsing join event");
let parsed_join_pdu = PduEvent::from_id_val(event_id, join_event.clone())
@ -895,12 +918,13 @@ async fn join_room_by_id_helper_remote(
err!(BadServerResponse("Invalid PDU in send_join response: {e:?}"))
})?;
services.rooms.outlier.add_pdu_outlier(&event_id, &value)?;
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
if let Some(state_key) = &pdu.state_key {
let shortstatekey = services
.rooms
.short
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)?;
.get_or_create_shortstatekey(&pdu.kind.to_string().into(), state_key)
.await;
state.insert(shortstatekey, pdu.event_id.clone());
}
}
@ -916,50 +940,53 @@ async fn join_room_by_id_helper_remote(
continue;
};
services.rooms.outlier.add_pdu_outlier(&event_id, &value)?;
services.rooms.outlier.add_pdu_outlier(&event_id, &value);
}
debug!("Running send_join auth check");
let fetch_state = &state;
let state_fetch = |k: &'static StateEventType, s: String| async move {
let shortstatekey = services.rooms.short.get_shortstatekey(k, &s).await.ok()?;
let event_id = fetch_state.get(&shortstatekey)?;
services.rooms.timeline.get_pdu(event_id).await.ok()
};
let auth_check = state_res::event_auth::auth_check(
&state_res::RoomVersion::new(&room_version_id).expect("room version is supported"),
&parsed_join_pdu,
None::<PduEvent>, // TODO: third party invite
|k, s| {
services
.rooms
.timeline
.get_pdu(
state.get(
&services
.rooms
.short
.get_or_create_shortstatekey(&k.to_string().into(), s)
.ok()?,
)?,
)
.ok()?
},
None, // TODO: third party invite
|k, s| state_fetch(k, s.to_owned()),
)
.map_err(|e| {
warn!("Auth check failed: {e}");
Error::BadRequest(ErrorKind::forbidden(), "Auth check failed")
})?;
.await
.map_err(|e| err!(Request(Forbidden(warn!("Auth check failed: {e:?}")))))?;
if !auth_check {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Auth check failed"));
return Err!(Request(Forbidden("Auth check failed")));
}
info!("Saving state from send_join");
let (statehash_before_join, new, removed) = services.rooms.state_compressor.save_state(
room_id,
Arc::new(
state
.into_iter()
.map(|(k, id)| services.rooms.state_compressor.compress_state_event(k, &id))
.collect::<Result<_>>()?,
),
)?;
let (statehash_before_join, new, removed) = services
.rooms
.state_compressor
.save_state(
room_id,
Arc::new(
state
.into_iter()
.stream()
.then(|(k, id)| async move {
services
.rooms
.state_compressor
.compress_state_event(k, &id)
.await
})
.collect()
.await,
),
)
.await?;
services
.rooms
@ -968,12 +995,20 @@ async fn join_room_by_id_helper_remote(
.await?;
info!("Updating joined counts for new room");
services.rooms.state_cache.update_joined_count(room_id)?;
services
.rooms
.state_cache
.update_joined_count(room_id)
.await;
// We append to state before appending the pdu, so we don't have a moment in
// time with the pdu without it's state. This is okay because append_pdu can't
// fail.
let statehash_after_join = services.rooms.state.append_to_state(&parsed_join_pdu)?;
let statehash_after_join = services
.rooms
.state
.append_to_state(&parsed_join_pdu)
.await?;
info!("Appending new room join event");
services
@ -993,7 +1028,7 @@ async fn join_room_by_id_helper_remote(
services
.rooms
.state
.set_room_state(room_id, statehash_after_join, &state_lock)?;
.set_room_state(room_id, statehash_after_join, &state_lock);
Ok(join_room_by_id::v3::Response::new(room_id.to_owned()))
}
@ -1005,23 +1040,15 @@ async fn join_room_by_id_helper_local(
) -> Result<join_room_by_id::v3::Response> {
debug!("We can join locally");
let join_rules_event = services
let join_rules_event_content = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomJoinRules, "")?;
let join_rules_event_content: Option<RoomJoinRulesEventContent> = join_rules_event
.as_ref()
.map(|join_rules_event| {
serde_json::from_str(join_rules_event.content.get()).map_err(|e| {
warn!("Invalid join rules event: {}", e);
Error::bad_database("Invalid join rules event in db.")
})
})
.transpose()?;
.room_state_get_content(room_id, &StateEventType::RoomJoinRules, "")
.await
.map(|content: RoomJoinRulesEventContent| content);
let restriction_rooms = match join_rules_event_content {
Some(RoomJoinRulesEventContent {
Ok(RoomJoinRulesEventContent {
join_rule: JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted),
}) => restricted
.allow
@ -1034,29 +1061,34 @@ async fn join_room_by_id_helper_local(
_ => Vec::new(),
};
let local_members = services
let local_members: Vec<_> = services
.rooms
.state_cache
.room_members(room_id)
.filter_map(Result::ok)
.filter(|user| services.globals.user_is_local(user))
.collect::<Vec<OwnedUserId>>();
.ready_filter(|user| services.globals.user_is_local(user))
.map(ToOwned::to_owned)
.collect()
.await;
let mut join_authorized_via_users_server: Option<OwnedUserId> = None;
if restriction_rooms.iter().any(|restriction_room_id| {
services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
.unwrap_or(false)
}) {
if restriction_rooms
.iter()
.stream()
.any(|restriction_room_id| {
services
.rooms
.state_cache
.is_joined(sender_user, restriction_room_id)
})
.await
{
for user in local_members {
if services
.rooms
.state_accessor
.user_can_invite(room_id, &user, sender_user, &state_lock)
.unwrap_or(false)
.await
{
join_authorized_via_users_server = Some(user);
break;
@ -1066,11 +1098,11 @@ async fn join_room_by_id_helper_local(
let event = RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: reason.clone(),
join_authorized_via_users_server,
};
@ -1144,11 +1176,11 @@ async fn join_room_by_id_helper_local(
"content".to_owned(),
to_canonical_value(RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason,
join_authorized_via_users_server,
})
@ -1195,10 +1227,11 @@ async fn join_room_by_id_helper_local(
federation::membership::create_join_event::v2::Request {
room_id: room_id.to_owned(),
event_id: event_id.to_owned(),
omit_members: false,
pdu: services
.sending
.convert_to_outgoing_federation_event(join_event.clone()),
omit_members: false,
.convert_to_outgoing_federation_event(join_event.clone())
.await,
},
)
.await?;
@ -1369,7 +1402,7 @@ pub(crate) async fn invite_helper(
services: &Services, sender_user: &UserId, user_id: &UserId, room_id: &RoomId, reason: Option<String>,
is_direct: bool,
) -> Result<()> {
if !services.users.is_admin(user_id)? && services.globals.block_non_admin_invites() {
if !services.users.is_admin(user_id).await && services.globals.block_non_admin_invites() {
info!("User {sender_user} is not an admin and attempted to send an invite to room {room_id}");
return Err(Error::BadRequest(
ErrorKind::forbidden(),
@ -1381,7 +1414,7 @@ pub(crate) async fn invite_helper(
let (pdu, pdu_json, invite_room_state) = {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let content = to_raw_value(&RoomMemberEventContent {
avatar_url: services.users.avatar_url(user_id)?,
avatar_url: services.users.avatar_url(user_id).await.ok(),
displayname: None,
is_direct: Some(is_direct),
membership: MembershipState::Invite,
@ -1392,28 +1425,32 @@ pub(crate) async fn invite_helper(
})
.expect("member event is valid value");
let (pdu, pdu_json) = services.rooms.timeline.create_hash_and_sign_event(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content,
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
timestamp: None,
},
sender_user,
room_id,
&state_lock,
)?;
let (pdu, pdu_json) = services
.rooms
.timeline
.create_hash_and_sign_event(
PduBuilder {
event_type: TimelineEventType::RoomMember,
content,
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
timestamp: None,
},
sender_user,
room_id,
&state_lock,
)
.await?;
let invite_room_state = services.rooms.state.calculate_invite_state(&pdu)?;
let invite_room_state = services.rooms.state.calculate_invite_state(&pdu).await?;
drop(state_lock);
(pdu, pdu_json, invite_room_state)
};
let room_version_id = services.rooms.state.get_room_version(room_id)?;
let room_version_id = services.rooms.state.get_room_version(room_id).await?;
let response = services
.sending
@ -1425,9 +1462,15 @@ pub(crate) async fn invite_helper(
room_version: room_version_id.clone(),
event: services
.sending
.convert_to_outgoing_federation_event(pdu_json.clone()),
.convert_to_outgoing_federation_event(pdu_json.clone())
.await,
invite_room_state,
via: services.rooms.state_cache.servers_route_via(room_id).ok(),
via: services
.rooms
.state_cache
.servers_route_via(room_id)
.await
.ok(),
},
)
.await?;
@ -1478,11 +1521,16 @@ pub(crate) async fn invite_helper(
"Could not accept incoming PDU as timeline event.",
))?;
services.sending.send_pdu_room(room_id, &pdu_id)?;
services.sending.send_pdu_room(room_id, &pdu_id).await?;
return Ok(());
}
if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this room.",
@ -1499,11 +1547,11 @@ pub(crate) async fn invite_helper(
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Invite,
displayname: services.users.displayname(user_id)?,
avatar_url: services.users.avatar_url(user_id)?,
displayname: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
is_direct: Some(is_direct),
third_party_invite: None,
blurhash: services.users.blurhash(user_id)?,
blurhash: services.users.blurhash(user_id).await.ok(),
reason,
join_authorized_via_users_server: None,
})
@ -1531,36 +1579,37 @@ pub async fn leave_all_rooms(services: &Services, user_id: &UserId) {
.rooms
.state_cache
.rooms_joined(user_id)
.map(ToOwned::to_owned)
.chain(
services
.rooms
.state_cache
.rooms_invited(user_id)
.map(|t| t.map(|(r, _)| r)),
.map(|(r, _)| r),
)
.collect::<Vec<_>>();
.collect::<Vec<_>>()
.await;
for room_id in all_rooms {
let Ok(room_id) = room_id else {
continue;
};
// ignore errors
if let Err(e) = leave_room(services, user_id, &room_id, None).await {
warn!(%room_id, %user_id, %e, "Failed to leave room");
}
if let Err(e) = services.rooms.state_cache.forget(&room_id, user_id) {
warn!(%room_id, %user_id, %e, "Failed to forget room");
}
services.rooms.state_cache.forget(&room_id, user_id);
}
}
pub async fn leave_room(services: &Services, user_id: &UserId, room_id: &RoomId, reason: Option<String>) -> Result<()> {
//use conduit::utils::stream::OptionStream;
use futures::TryFutureExt;
// Ask a remote server if we don't have this room
if !services
.rooms
.state_cache
.server_in_room(services.globals.server_name(), room_id)?
.server_in_room(services.globals.server_name(), room_id)
.await
{
if let Err(e) = remote_leave_room(services, user_id, room_id).await {
warn!("Failed to leave room {} remotely: {}", user_id, e);
@ -1570,34 +1619,42 @@ pub async fn leave_room(services: &Services, user_id: &UserId, room_id: &RoomId,
let last_state = services
.rooms
.state_cache
.invite_state(user_id, room_id)?
.map_or_else(|| services.rooms.state_cache.left_state(user_id, room_id), |s| Ok(Some(s)))?;
.invite_state(user_id, room_id)
.map_err(|_| services.rooms.state_cache.left_state(user_id, room_id))
.await
.ok();
// We always drop the invite, we can't rely on other servers
services.rooms.state_cache.update_membership(
room_id,
user_id,
RoomMemberEventContent::new(MembershipState::Leave),
user_id,
last_state,
None,
true,
)?;
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
RoomMemberEventContent::new(MembershipState::Leave),
user_id,
last_state,
None,
true,
)
.await?;
} else {
let state_lock = services.rooms.state.mutex.lock(room_id).await;
let member_event =
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?;
let member_event = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
.await;
// Fix for broken rooms
let member_event = match member_event {
None => {
error!("Trying to leave a room you are not a member of.");
let Ok(member_event) = member_event else {
error!("Trying to leave a room you are not a member of.");
services.rooms.state_cache.update_membership(
services
.rooms
.state_cache
.update_membership(
room_id,
user_id,
RoomMemberEventContent::new(MembershipState::Leave),
@ -1605,16 +1662,14 @@ pub async fn leave_room(services: &Services, user_id: &UserId, room_id: &RoomId,
None,
None,
true,
)?;
return Ok(());
},
Some(e) => e,
)
.await?;
return Ok(());
};
let mut event: RoomMemberEventContent = serde_json::from_str(member_event.content.get()).map_err(|e| {
error!("Invalid room member event in database: {}", e);
Error::bad_database("Invalid member event in database.")
})?;
let mut event: RoomMemberEventContent = serde_json::from_str(member_event.content.get())
.map_err(|e| err!(Database(error!("Invalid room member event in database: {e}"))))?;
event.membership = MembershipState::Leave;
event.reason = reason;
@ -1647,15 +1702,17 @@ async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &Room
let invite_state = services
.rooms
.state_cache
.invite_state(user_id, room_id)?
.ok_or(Error::BadRequest(ErrorKind::BadState, "User is not invited."))?;
.invite_state(user_id, room_id)
.await
.map_err(|_| err!(Request(BadState("User is not invited."))))?;
let mut servers: HashSet<OwnedServerName> = services
.rooms
.state_cache
.servers_invite_via(room_id)
.filter_map(Result::ok)
.collect();
.map(ToOwned::to_owned)
.collect()
.await;
servers.extend(
invite_state
@ -1760,7 +1817,8 @@ async fn remote_leave_room(services: &Services, user_id: &UserId, room_id: &Room
event_id,
pdu: services
.sending
.convert_to_outgoing_federation_event(leave_event.clone()),
.convert_to_outgoing_federation_event(leave_event.clone())
.await,
},
)
.await?;

View file

@ -1,7 +1,8 @@
use std::collections::{BTreeMap, HashSet};
use axum::extract::State;
use conduit::PduCount;
use conduit::{err, utils::ReadyExt, Err, PduCount};
use futures::{FutureExt, StreamExt};
use ruma::{
api::client::{
error::ErrorKind,
@ -9,13 +10,14 @@ use ruma::{
message::{get_message_events, send_message_event},
},
events::{MessageLikeEventType, StateEventType},
RoomId, UserId,
UserId,
};
use serde_json::{from_str, Value};
use service::rooms::timeline::PdusIterItem;
use crate::{
service::{pdu::PduBuilder, Services},
utils, Error, PduEvent, Result, Ruma,
utils, Error, Result, Ruma,
};
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
@ -30,79 +32,78 @@ use crate::{
pub(crate) async fn send_message_event_route(
State(services): State<crate::State>, body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user.as_deref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref();
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
let appservice_info = body.appservice_info.as_ref();
// Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services.globals.allow_encryption() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Encryption has been disabled"));
return Err!(Request(Forbidden("Encryption has been disabled")));
}
if body.event_type == MessageLikeEventType::CallInvite && services.rooms.directory.is_public_room(&body.room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"Room call invites are not allowed in public rooms",
));
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
if body.event_type == MessageLikeEventType::CallInvite
&& services.rooms.directory.is_public_room(&body.room_id).await
{
return Err!(Request(Forbidden("Room call invites are not allowed in public rooms")));
}
// Check if this is a new transaction id
if let Some(response) = services
if let Ok(response) = services
.transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)?
.existing_txnid(sender_user, sender_device, &body.txn_id)
.await
{
// The client might have sent a txnid of the /sendToDevice endpoint
// This txnid has no response associated with it
if response.is_empty() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to use txn id already used for an incompatible endpoint.",
));
return Err!(Request(InvalidParam(
"Tried to use txn id already used for an incompatible endpoint."
)));
}
let event_id = utils::string_from_bytes(&response)
.map_err(|_| Error::bad_database("Invalid txnid bytes in database."))?
.try_into()
.map_err(|_| Error::bad_database("Invalid event id in txnid data."))?;
return Ok(send_message_event::v3::Response {
event_id,
event_id: utils::string_from_bytes(&response)
.map(TryInto::try_into)
.map_err(|e| err!(Database("Invalid event_id in txnid data: {e:?}")))??,
});
}
let mut unsigned = BTreeMap::new();
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
let content = from_str(body.body.body.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?;
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
PduBuilder {
event_type: body.event_type.to_string().into(),
content: from_str(body.body.body.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
content,
unsigned: Some(unsigned),
state_key: None,
redacts: None,
timestamp: if body.appservice_info.is_some() {
body.timestamp
} else {
None
},
timestamp: appservice_info.and(body.timestamp),
},
sender_user,
&body.room_id,
&state_lock,
)
.await?;
.await
.map(|event_id| (*event_id).to_owned())?;
services
.transaction_ids
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes())?;
.add_txnid(sender_user, sender_device, &body.txn_id, event_id.as_bytes());
drop(state_lock);
Ok(send_message_event::v3::Response::new((*event_id).to_owned()))
Ok(send_message_event::v3::Response {
event_id,
})
}
/// # `GET /_matrix/client/r0/rooms/{roomId}/messages`
@ -117,8 +118,12 @@ pub(crate) async fn get_message_events_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let from = match body.from.clone() {
Some(from) => PduCount::try_from_string(&from)?,
let room_id = &body.room_id;
let filter = &body.filter;
let limit = usize::try_from(body.limit).unwrap_or(10).min(100);
let from = match body.from.as_ref() {
Some(from) => PduCount::try_from_string(from)?,
None => match body.dir {
ruma::api::Direction::Forward => PduCount::min(),
ruma::api::Direction::Backward => PduCount::max(),
@ -133,30 +138,25 @@ pub(crate) async fn get_message_events_route(
services
.rooms
.lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
.await?;
let limit = usize::try_from(body.limit).unwrap_or(10).min(100);
let next_token;
.lazy_load_confirm_delivery(sender_user, sender_device, room_id, from);
let mut resp = get_message_events::v3::Response::new();
let mut lazy_loaded = HashSet::new();
let next_token;
match body.dir {
ruma::api::Direction::Forward => {
let events_after: Vec<_> = services
let events_after: Vec<PdusIterItem> = services
.rooms
.timeline
.pdus_after(sender_user, &body.room_id, from)?
.filter_map(Result::ok) // Filter out buggy events
.filter(|(_, pdu)| { contains_url_filter(pdu, &body.filter) && visibility_filter(&services, pdu, sender_user, &body.room_id)
})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.pdus_after(sender_user, room_id, from)
.await?
.ready_filter_map(|item| contains_url_filter(item, filter))
.filter_map(|item| visibility_filter(&services, item, sender_user))
.ready_take_while(|(count, _)| Some(*count) != to) // Stop at `to`
.take(limit)
.collect();
.collect()
.boxed()
.await;
for (_, event) in &events_after {
/* TODO: Remove the not "element_hacks" check when these are resolved:
@ -164,16 +164,18 @@ pub(crate) async fn get_message_events_route(
* https://github.com/vector-im/element-web/issues/21034
*/
if !cfg!(feature = "element_hacks")
&& !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
&body.room_id,
&event.sender,
)? {
&& !services
.rooms
.lazy_loading
.lazy_load_was_sent_before(sender_user, sender_device, room_id, &event.sender)
.await
{
lazy_loaded.insert(event.sender.clone());
}
lazy_loaded.insert(event.sender.clone());
if cfg!(features = "element_hacks") {
lazy_loaded.insert(event.sender.clone());
}
}
next_token = events_after.last().map(|(count, _)| count).copied();
@ -191,17 +193,22 @@ pub(crate) async fn get_message_events_route(
services
.rooms
.timeline
.backfill_if_required(&body.room_id, from)
.backfill_if_required(room_id, from)
.boxed()
.await?;
let events_before: Vec<_> = services
let events_before: Vec<PdusIterItem> = services
.rooms
.timeline
.pdus_until(sender_user, &body.room_id, from)?
.filter_map(Result::ok) // Filter out buggy events
.filter(|(_, pdu)| {contains_url_filter(pdu, &body.filter) && visibility_filter(&services, pdu, sender_user, &body.room_id)})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.pdus_until(sender_user, room_id, from)
.await?
.ready_filter_map(|item| contains_url_filter(item, filter))
.filter_map(|item| visibility_filter(&services, item, sender_user))
.ready_take_while(|(count, _)| Some(*count) != to) // Stop at `to`
.take(limit)
.collect();
.collect()
.boxed()
.await;
for (_, event) in &events_before {
/* TODO: Remove the not "element_hacks" check when these are resolved:
@ -209,16 +216,18 @@ pub(crate) async fn get_message_events_route(
* https://github.com/vector-im/element-web/issues/21034
*/
if !cfg!(feature = "element_hacks")
&& !services.rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
&body.room_id,
&event.sender,
)? {
&& !services
.rooms
.lazy_loading
.lazy_load_was_sent_before(sender_user, sender_device, room_id, &event.sender)
.await
{
lazy_loaded.insert(event.sender.clone());
}
lazy_loaded.insert(event.sender.clone());
if cfg!(features = "element_hacks") {
lazy_loaded.insert(event.sender.clone());
}
}
next_token = events_before.last().map(|(count, _)| count).copied();
@ -236,11 +245,11 @@ pub(crate) async fn get_message_events_route(
resp.state = Vec::new();
for ll_id in &lazy_loaded {
if let Some(member_event) =
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomMember, ll_id.as_str())?
if let Ok(member_event) = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, ll_id.as_str())
.await
{
resp.state.push(member_event.to_state_event());
}
@ -249,34 +258,43 @@ pub(crate) async fn get_message_events_route(
// remove the feature check when we are sure clients like element can handle it
if !cfg!(feature = "element_hacks") {
if let Some(next_token) = next_token {
services
.rooms
.lazy_loading
.lazy_load_mark_sent(sender_user, sender_device, &body.room_id, lazy_loaded, next_token)
.await;
services.rooms.lazy_loading.lazy_load_mark_sent(
sender_user,
sender_device,
room_id,
lazy_loaded,
next_token,
);
}
}
Ok(resp)
}
fn visibility_filter(services: &Services, pdu: &PduEvent, user_id: &UserId, room_id: &RoomId) -> bool {
async fn visibility_filter(services: &Services, item: PdusIterItem, user_id: &UserId) -> Option<PdusIterItem> {
let (_, pdu) = &item;
services
.rooms
.state_accessor
.user_can_see_event(user_id, room_id, &pdu.event_id)
.unwrap_or(false)
.user_can_see_event(user_id, &pdu.room_id, &pdu.event_id)
.await
.then_some(item)
}
fn contains_url_filter(pdu: &PduEvent, filter: &RoomEventFilter) -> bool {
fn contains_url_filter(item: PdusIterItem, filter: &RoomEventFilter) -> Option<PdusIterItem> {
let (_, pdu) = &item;
if filter.url_filter.is_none() {
return true;
return Some(item);
}
let content: Value = from_str(pdu.content.get()).unwrap();
match filter.url_filter {
let res = match filter.url_filter {
Some(UrlFilter::EventsWithoutUrl) => !content["url"].is_string(),
Some(UrlFilter::EventsWithUrl) => content["url"].is_string(),
None => true,
}
};
res.then_some(item)
}

View file

@ -28,7 +28,8 @@ pub(crate) async fn set_presence_route(
services
.presence
.set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())?;
.set_presence(sender_user, &body.presence, None, None, body.status_msg.clone())
.await?;
Ok(set_presence::v3::Response {})
}
@ -49,14 +50,15 @@ pub(crate) async fn get_presence_route(
let mut presence_event = None;
for _room_id in services
let has_shared_rooms = services
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
{
if let Some(presence) = services.presence.get_presence(&body.user_id)? {
.has_shared_rooms(sender_user, &body.user_id)
.await;
if has_shared_rooms {
if let Ok(presence) = services.presence.get_presence(&body.user_id).await {
presence_event = Some(presence);
break;
}
}

View file

@ -1,5 +1,10 @@
use axum::extract::State;
use conduit::{pdu::PduBuilder, warn, Err, Error, Result};
use conduit::{
pdu::PduBuilder,
utils::{stream::TryIgnore, IterStream},
warn, Err, Error, Result,
};
use futures::{StreamExt, TryStreamExt};
use ruma::{
api::{
client::{
@ -35,16 +40,18 @@ pub(crate) async fn set_displayname_route(
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
.map(ToOwned::to_owned)
.collect()
.await;
update_displayname(&services, &body.user_id, body.displayname.clone(), all_joined_rooms).await?;
update_displayname(&services, &body.user_id, body.displayname.clone(), &all_joined_rooms).await?;
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(set_display_name::v3::Response {})
@ -72,22 +79,19 @@ pub(crate) async fn get_displayname_route(
)
.await
{
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
.set_blurhash(&body.user_id, response.blurhash.clone());
return Ok(get_display_name::v3::Response {
displayname: response.displayname,
@ -95,14 +99,14 @@ pub(crate) async fn get_displayname_route(
}
}
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_display_name::v3::Response {
displayname: services.users.displayname(&body.user_id)?,
displayname: services.users.displayname(&body.user_id).await.ok(),
})
}
@ -124,15 +128,16 @@ pub(crate) async fn set_avatar_url_route(
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
.map(ToOwned::to_owned)
.collect()
.await;
update_avatar_url(
&services,
&body.user_id,
body.avatar_url.clone(),
body.blurhash.clone(),
all_joined_rooms,
&all_joined_rooms,
)
.await?;
@ -140,7 +145,9 @@ pub(crate) async fn set_avatar_url_route(
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
.ping_presence(&body.user_id, &PresenceState::Online)
.await
.ok();
}
Ok(set_avatar_url::v3::Response {})
@ -168,22 +175,21 @@ pub(crate) async fn get_avatar_url_route(
)
.await
{
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
.set_blurhash(&body.user_id, response.blurhash.clone());
return Ok(get_avatar_url::v3::Response {
avatar_url: response.avatar_url,
@ -192,15 +198,15 @@ pub(crate) async fn get_avatar_url_route(
}
}
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_avatar_url::v3::Response {
avatar_url: services.users.avatar_url(&body.user_id)?,
blurhash: services.users.blurhash(&body.user_id)?,
avatar_url: services.users.avatar_url(&body.user_id).await.ok(),
blurhash: services.users.blurhash(&body.user_id).await.ok(),
})
}
@ -226,31 +232,30 @@ pub(crate) async fn get_profile_route(
)
.await
{
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone())
.await?;
.set_timezone(&body.user_id, response.tz.clone());
for (profile_key, profile_key_value) in &response.custom_profile_fields {
services
.users
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()))?;
.set_profile_key(&body.user_id, profile_key, Some(profile_key_value.clone()));
}
return Ok(get_profile::v3::Response {
@ -263,104 +268,93 @@ pub(crate) async fn get_profile_route(
}
}
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_profile::v3::Response {
avatar_url: services.users.avatar_url(&body.user_id)?,
blurhash: services.users.blurhash(&body.user_id)?,
displayname: services.users.displayname(&body.user_id)?,
tz: services.users.timezone(&body.user_id)?,
avatar_url: services.users.avatar_url(&body.user_id).await.ok(),
blurhash: services.users.blurhash(&body.user_id).await.ok(),
displayname: services.users.displayname(&body.user_id).await.ok(),
tz: services.users.timezone(&body.user_id).await.ok(),
custom_profile_fields: services
.users
.all_profile_keys(&body.user_id)
.filter_map(Result::ok)
.collect(),
.collect()
.await,
})
}
pub async fn update_displayname(
services: &Services, user_id: &UserId, displayname: Option<String>, all_joined_rooms: Vec<OwnedRoomId>,
services: &Services, user_id: &UserId, displayname: Option<String>, all_joined_rooms: &[OwnedRoomId],
) -> Result<()> {
let current_display_name = services.users.displayname(user_id).unwrap_or_default();
let current_display_name = services.users.displayname(user_id).await.ok();
if displayname == current_display_name {
return Ok(());
}
services
.users
.set_displayname(user_id, displayname.clone())
.await?;
services.users.set_displayname(user_id, displayname.clone());
// Send a new join membership event into all joined rooms
let all_joined_rooms: Vec<_> = all_joined_rooms
.iter()
.map(|room_id| {
Ok::<_, Error>((
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
displayname: displayname.clone(),
join_authorized_via_users_server: None,
..serde_json::from_str(
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
.ok_or_else(|| {
Error::bad_database("Tried to send display name update for user not in the room.")
})?
.content
.get(),
)
.map_err(|_| Error::bad_database("Database contains invalid PDU."))?
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
timestamp: None,
},
room_id,
))
})
.filter_map(Result::ok)
.collect();
let mut joined_rooms = Vec::new();
for room_id in all_joined_rooms {
let Ok(event) = services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
.await
else {
continue;
};
update_all_rooms(services, all_joined_rooms, user_id).await;
let pdu = PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
displayname: displayname.clone(),
join_authorized_via_users_server: None,
..serde_json::from_str(event.content.get()).expect("Database contains invalid PDU.")
})
.expect("event is valid, we just created it"),
unsigned: None,
state_key: Some(user_id.to_string()),
redacts: None,
timestamp: None,
};
joined_rooms.push((pdu, room_id));
}
update_all_rooms(services, joined_rooms, user_id).await;
Ok(())
}
pub async fn update_avatar_url(
services: &Services, user_id: &UserId, avatar_url: Option<OwnedMxcUri>, blurhash: Option<String>,
all_joined_rooms: Vec<OwnedRoomId>,
all_joined_rooms: &[OwnedRoomId],
) -> Result<()> {
let current_avatar_url = services.users.avatar_url(user_id).unwrap_or_default();
let current_blurhash = services.users.blurhash(user_id).unwrap_or_default();
let current_avatar_url = services.users.avatar_url(user_id).await.ok();
let current_blurhash = services.users.blurhash(user_id).await.ok();
if current_avatar_url == avatar_url && current_blurhash == blurhash {
return Ok(());
}
services
.users
.set_avatar_url(user_id, avatar_url.clone())
.await?;
services
.users
.set_blurhash(user_id, blurhash.clone())
.await?;
services.users.set_avatar_url(user_id, avatar_url.clone());
services.users.set_blurhash(user_id, blurhash.clone());
// Send a new join membership event into all joined rooms
let avatar_url = &avatar_url;
let blurhash = &blurhash;
let all_joined_rooms: Vec<_> = all_joined_rooms
.iter()
.map(|room_id| {
Ok::<_, Error>((
.try_stream()
.and_then(|room_id: &OwnedRoomId| async move {
Ok((
PduBuilder {
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
@ -371,8 +365,9 @@ pub async fn update_avatar_url(
services
.rooms
.state_accessor
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())?
.ok_or_else(|| {
.room_state_get(room_id, &StateEventType::RoomMember, user_id.as_str())
.await
.map_err(|_| {
Error::bad_database("Tried to send avatar URL update for user not in the room.")
})?
.content
@ -389,8 +384,9 @@ pub async fn update_avatar_url(
room_id,
))
})
.filter_map(Result::ok)
.collect();
.ignore_err()
.collect()
.await;
update_all_rooms(services, all_joined_rooms, user_id).await;

View file

@ -29,41 +29,37 @@ pub(crate) async fn get_pushrules_all_route(
let global_ruleset: Ruleset;
let Ok(event) =
services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
else {
// push rules event doesn't exist, create it and return default
return recreate_push_rules_and_return(&services, sender_user);
};
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await;
if let Some(event) = event {
let value = serde_json::from_str::<CanonicalJsonObject>(event.get())
.map_err(|e| err!(Database(warn!("Invalid push rules account data event in database: {e}"))))?;
let Some(content_value) = value.get("content") else {
// user somehow has a push rule event with no content key, recreate it and
// return server default silently
return recreate_push_rules_and_return(&services, sender_user);
};
if content_value.to_string().is_empty() {
// user somehow has a push rule event with empty content, recreate it and return
// server default silently
return recreate_push_rules_and_return(&services, sender_user);
}
let account_data_content = serde_json::from_value::<PushRulesEventContent>(content_value.clone().into())
.map_err(|e| err!(Database(warn!("Invalid push rules account data event in database: {e}"))))?;
global_ruleset = account_data_content.global;
} else {
let Ok(event) = event else {
// user somehow has non-existent push rule event. recreate it and return server
// default silently
return recreate_push_rules_and_return(&services, sender_user);
return recreate_push_rules_and_return(&services, sender_user).await;
};
let value = serde_json::from_str::<CanonicalJsonObject>(event.get())
.map_err(|e| err!(Database(warn!("Invalid push rules account data event in database: {e}"))))?;
let Some(content_value) = value.get("content") else {
// user somehow has a push rule event with no content key, recreate it and
// return server default silently
return recreate_push_rules_and_return(&services, sender_user).await;
};
if content_value.to_string().is_empty() {
// user somehow has a push rule event with empty content, recreate it and return
// server default silently
return recreate_push_rules_and_return(&services, sender_user).await;
}
let account_data_content = serde_json::from_value::<PushRulesEventContent>(content_value.clone().into())
.map_err(|e| err!(Database(warn!("Invalid push rules account data event in database: {e}"))))?;
global_ruleset = account_data_content.global;
Ok(get_pushrules_all::v3::Response {
global: global_ruleset,
})
@ -79,8 +75,9 @@ pub(crate) async fn get_pushrule_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
@ -118,8 +115,9 @@ pub(crate) async fn set_pushrule_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
@ -155,12 +153,15 @@ pub(crate) async fn set_pushrule_route(
return Err(err);
}
services.account_data.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)?;
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?;
Ok(set_pushrule::v3::Response {})
}
@ -182,8 +183,9 @@ pub(crate) async fn get_pushrule_actions_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
@ -217,8 +219,9 @@ pub(crate) async fn set_pushrule_actions_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
@ -232,12 +235,15 @@ pub(crate) async fn set_pushrule_actions_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found."));
}
services.account_data.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)?;
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?;
Ok(set_pushrule_actions::v3::Response {})
}
@ -259,8 +265,9 @@ pub(crate) async fn get_pushrule_enabled_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
@ -293,8 +300,9 @@ pub(crate) async fn set_pushrule_enabled_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
@ -308,12 +316,15 @@ pub(crate) async fn set_pushrule_enabled_route(
return Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found."));
}
services.account_data.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)?;
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?;
Ok(set_pushrule_enabled::v3::Response {})
}
@ -335,8 +346,9 @@ pub(crate) async fn delete_pushrule_route(
let event = services
.account_data
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
.get(None, sender_user, GlobalAccountDataEventType::PushRules.to_string().into())
.await
.map_err(|_| Error::BadRequest(ErrorKind::NotFound, "PushRules event not found."))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
@ -357,12 +369,15 @@ pub(crate) async fn delete_pushrule_route(
return Err(err);
}
services.account_data.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)?;
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
)
.await?;
Ok(delete_pushrule::v3::Response {})
}
@ -376,7 +391,7 @@ pub(crate) async fn get_pushers_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(get_pushers::v3::Response {
pushers: services.pusher.get_pushers(sender_user)?,
pushers: services.pusher.get_pushers(sender_user).await,
})
}
@ -390,27 +405,30 @@ pub(crate) async fn set_pushers_route(
) -> Result<set_pusher::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services.pusher.set_pusher(sender_user, &body.action)?;
services.pusher.set_pusher(sender_user, &body.action);
Ok(set_pusher::v3::Response::default())
}
/// user somehow has bad push rules, these must always exist per spec.
/// so recreate it and return server default silently
fn recreate_push_rules_and_return(
async fn recreate_push_rules_and_return(
services: &Services, sender_user: &ruma::UserId,
) -> Result<get_pushrules_all::v3::Response> {
services.account_data.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent {
global: Ruleset::server_default(sender_user),
},
})
.expect("to json always works"),
)?;
services
.account_data
.update(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(PushRulesEvent {
content: PushRulesEventContent {
global: Ruleset::server_default(sender_user),
},
})
.expect("to json always works"),
)
.await?;
Ok(get_pushrules_all::v3::Response {
global: Ruleset::server_default(sender_user),

View file

@ -31,27 +31,32 @@ pub(crate) async fn set_read_marker_route(
event_id: fully_read.clone(),
},
};
services.account_data.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"),
)?;
services
.account_data
.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"),
)
.await?;
}
if body.private_read_receipt.is_some() || body.read_receipt.is_some() {
services
.rooms
.user
.reset_notification_counts(sender_user, &body.room_id)?;
.reset_notification_counts(sender_user, &body.room_id);
}
if let Some(event) = &body.private_read_receipt {
let count = services
.rooms
.timeline
.get_pdu_count(event)?
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."))?;
.get_pdu_count(event)
.await
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."))?;
let count = match count {
PduCount::Backfilled(_) => {
return Err(Error::BadRequest(
@ -64,7 +69,7 @@ pub(crate) async fn set_read_marker_route(
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count)?;
.private_read_set(&body.room_id, sender_user, count);
}
if let Some(event) = &body.read_receipt {
@ -83,14 +88,18 @@ pub(crate) async fn set_read_marker_route(
let mut receipt_content = BTreeMap::new();
receipt_content.insert(event.to_owned(), receipts);
services.rooms.read_receipt.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)?;
services
.rooms
.read_receipt
.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)
.await;
}
Ok(set_read_marker::v3::Response {})
@ -111,7 +120,7 @@ pub(crate) async fn create_receipt_route(
services
.rooms
.user
.reset_notification_counts(sender_user, &body.room_id)?;
.reset_notification_counts(sender_user, &body.room_id);
}
match body.receipt_type {
@ -121,12 +130,15 @@ pub(crate) async fn create_receipt_route(
event_id: body.event_id.clone(),
},
};
services.account_data.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"),
)?;
services
.account_data
.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"),
)
.await?;
},
create_receipt::v3::ReceiptType::Read => {
let mut user_receipts = BTreeMap::new();
@ -143,21 +155,27 @@ pub(crate) async fn create_receipt_route(
let mut receipt_content = BTreeMap::new();
receipt_content.insert(body.event_id.clone(), receipts);
services.rooms.read_receipt.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)?;
services
.rooms
.read_receipt
.readreceipt_update(
sender_user,
&body.room_id,
&ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
room_id: body.room_id.clone(),
},
)
.await;
},
create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services
.rooms
.timeline
.get_pdu_count(&body.event_id)?
.ok_or(Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."))?;
.get_pdu_count(&body.event_id)
.await
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."))?;
let count = match count {
PduCount::Backfilled(_) => {
return Err(Error::BadRequest(
@ -170,7 +188,7 @@ pub(crate) async fn create_receipt_route(
services
.rooms
.read_receipt
.private_read_set(&body.room_id, sender_user, count)?;
.private_read_set(&body.room_id, sender_user, count);
},
_ => return Err(Error::bad_database("Unsupported receipt type")),
}

View file

@ -9,20 +9,24 @@ use crate::{Result, Ruma};
pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
State(services): State<crate::State>, body: Ruma<get_relating_events_with_rel_type_and_event_type::v1::Request>,
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user.as_deref().expect("user is authenticated");
let res = services.rooms.pdu_metadata.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&Some(body.event_type.clone()),
&Some(body.rel_type.clone()),
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)?;
let res = services
.rooms
.pdu_metadata
.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
body.event_type.clone().into(),
body.rel_type.clone().into(),
body.from.as_ref(),
body.to.as_ref(),
body.limit,
body.recurse,
body.dir,
)
.await?;
Ok(get_relating_events_with_rel_type_and_event_type::v1::Response {
chunk: res.chunk,
@ -36,20 +40,24 @@ pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
pub(crate) async fn get_relating_events_with_rel_type_route(
State(services): State<crate::State>, body: Ruma<get_relating_events_with_rel_type::v1::Request>,
) -> Result<get_relating_events_with_rel_type::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user.as_deref().expect("user is authenticated");
let res = services.rooms.pdu_metadata.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&None,
&Some(body.rel_type.clone()),
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)?;
let res = services
.rooms
.pdu_metadata
.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
None,
body.rel_type.clone().into(),
body.from.as_ref(),
body.to.as_ref(),
body.limit,
body.recurse,
body.dir,
)
.await?;
Ok(get_relating_events_with_rel_type::v1::Response {
chunk: res.chunk,
@ -63,18 +71,22 @@ pub(crate) async fn get_relating_events_with_rel_type_route(
pub(crate) async fn get_relating_events_route(
State(services): State<crate::State>, body: Ruma<get_relating_events::v1::Request>,
) -> Result<get_relating_events::v1::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_user = body.sender_user.as_deref().expect("user is authenticated");
services.rooms.pdu_metadata.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
&None,
&None,
&body.from,
&body.to,
&body.limit,
body.recurse,
body.dir,
)
services
.rooms
.pdu_metadata
.paginate_relations_with_filter(
sender_user,
&body.room_id,
&body.event_id,
None,
None,
body.from.as_ref(),
body.to.as_ref(),
body.limit,
body.recurse,
body.dir,
)
.await
}

View file

@ -1,6 +1,7 @@
use std::time::Duration;
use axum::extract::State;
use conduit::{utils::ReadyExt, Err};
use rand::Rng;
use ruma::{
api::client::{error::ErrorKind, room::report_content},
@ -34,11 +35,8 @@ pub(crate) async fn report_event_route(
delay_response().await;
// check if we know about the reported event ID or if it's invalid
let Some(pdu) = services.rooms.timeline.get_pdu(&body.event_id)? else {
return Err(Error::BadRequest(
ErrorKind::NotFound,
"Event ID is not known to us or Event ID is invalid",
));
let Ok(pdu) = services.rooms.timeline.get_pdu(&body.event_id).await else {
return Err!(Request(NotFound("Event ID is not known to us or Event ID is invalid")));
};
is_report_valid(
@ -49,7 +47,8 @@ pub(crate) async fn report_event_route(
&body.reason,
body.score,
&pdu,
)?;
)
.await?;
// send admin room message that we received the report with an @room ping for
// urgency
@ -81,7 +80,8 @@ pub(crate) async fn report_event_route(
HtmlEscape(body.reason.as_deref().unwrap_or(""))
),
))
.await;
.await
.ok();
Ok(report_content::v3::Response {})
}
@ -92,7 +92,7 @@ pub(crate) async fn report_event_route(
/// check if score is in valid range
/// check if report reasoning is less than or equal to 750 characters
/// check if reporting user is in the reporting room
fn is_report_valid(
async fn is_report_valid(
services: &Services, event_id: &EventId, room_id: &RoomId, sender_user: &UserId, reason: &Option<String>,
score: Option<ruma::Int>, pdu: &std::sync::Arc<PduEvent>,
) -> Result<()> {
@ -123,8 +123,8 @@ fn is_report_valid(
.rooms
.state_cache
.room_members(room_id)
.filter_map(Result::ok)
.any(|user_id| user_id == *sender_user)
.ready_any(|user_id| user_id == sender_user)
.await
{
return Err(Error::BadRequest(
ErrorKind::NotFound,

View file

@ -2,6 +2,7 @@ use std::{cmp::max, collections::BTreeMap};
use axum::extract::State;
use conduit::{debug_info, debug_warn, err, Err};
use futures::{FutureExt, StreamExt};
use ruma::{
api::client::{
error::ErrorKind,
@ -74,7 +75,7 @@ pub(crate) async fn create_room_route(
if !services.globals.allow_room_creation()
&& body.appservice_info.is_none()
&& !services.users.is_admin(sender_user)?
&& !services.users.is_admin(sender_user).await
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "Room creation has been disabled."));
}
@ -86,7 +87,7 @@ pub(crate) async fn create_room_route(
};
// check if room ID doesn't already exist instead of erroring on auth check
if services.rooms.short.get_shortroomid(&room_id)?.is_some() {
if services.rooms.short.get_shortroomid(&room_id).await.is_ok() {
return Err(Error::BadRequest(
ErrorKind::RoomInUse,
"Room with that custom room ID already exists",
@ -95,7 +96,7 @@ pub(crate) async fn create_room_route(
if body.visibility == room::Visibility::Public
&& services.globals.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user)?
&& !services.users.is_admin(sender_user).await
&& body.appservice_info.is_none()
{
info!(
@ -118,7 +119,11 @@ pub(crate) async fn create_room_route(
return Err!(Request(Forbidden("Publishing rooms to the room directory is not allowed")));
}
let _short_id = services.rooms.short.get_or_create_shortroomid(&room_id)?;
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&room_id)
.await;
let state_lock = services.rooms.state.mutex.lock(&room_id).await;
let alias: Option<OwnedRoomAliasId> = if let Some(alias) = &body.room_alias_name {
@ -218,6 +223,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
// 2. Let the room creator join
@ -229,11 +235,11 @@ pub(crate) async fn create_room_route(
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: Some(body.is_direct),
third_party_invite: None,
blurhash: services.users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: None,
join_authorized_via_users_server: None,
})
@ -247,6 +253,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
// 3. Power levels
@ -284,6 +291,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
// 4. Canonical room alias
@ -308,6 +316,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
}
@ -335,6 +344,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
// 5.2 History Visibility
@ -355,6 +365,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
// 5.3 Guest Access
@ -378,6 +389,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
// 6. Events listed in initial_state
@ -410,6 +422,7 @@ pub(crate) async fn create_room_route(
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
.boxed()
.await?;
}
@ -432,6 +445,7 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
}
@ -455,13 +469,17 @@ pub(crate) async fn create_room_route(
&room_id,
&state_lock,
)
.boxed()
.await?;
}
// 8. Events implied by invite (and TODO: invite_3pid)
drop(state_lock);
for user_id in &body.invite {
if let Err(e) = invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct).await {
if let Err(e) = invite_helper(&services, sender_user, user_id, &room_id, None, body.is_direct)
.boxed()
.await
{
warn!(%e, "Failed to send invite");
}
}
@ -475,7 +493,7 @@ pub(crate) async fn create_room_route(
}
if body.visibility == room::Visibility::Public {
services.rooms.directory.set_public(&room_id)?;
services.rooms.directory.set_public(&room_id);
if services.globals.config.admin_room_notices {
services
@ -505,13 +523,15 @@ pub(crate) async fn get_room_event_route(
let event = services
.rooms
.timeline
.get_pdu(&body.event_id)?
.ok_or_else(|| err!(Request(NotFound("Event {} not found.", &body.event_id))))?;
.get_pdu(&body.event_id)
.await
.map_err(|_| err!(Request(NotFound("Event {} not found.", &body.event_id))))?;
if !services
.rooms
.state_accessor
.user_can_see_event(sender_user, &event.room_id, &body.event_id)?
.user_can_see_event(sender_user, &event.room_id, &body.event_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
@ -541,7 +561,8 @@ pub(crate) async fn get_room_aliases_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
.user_can_see_state_events(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
@ -554,8 +575,9 @@ pub(crate) async fn get_room_aliases_route(
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.filter_map(Result::ok)
.collect(),
.map(ToOwned::to_owned)
.collect()
.await,
})
}
@ -591,7 +613,8 @@ pub(crate) async fn upgrade_room_route(
let _short_id = services
.rooms
.short
.get_or_create_shortroomid(&replacement_room)?;
.get_or_create_shortroomid(&replacement_room)
.await;
let state_lock = services.rooms.state.mutex.lock(&body.room_id).await;
@ -629,12 +652,12 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomCreate, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.room_state_get(&body.room_id, &StateEventType::RoomCreate, "")
.await
.map_err(|_| err!(Database("Found room without m.room.create event.")))?
.content
.get(),
)
.map_err(|_| Error::bad_database("Invalid room event in database."))?;
)?;
// Use the m.room.tombstone event as the predecessor
let predecessor = Some(ruma::events::room::create::PreviousRoom::new(
@ -714,11 +737,11 @@ pub(crate) async fn upgrade_room_route(
event_type: TimelineEventType::RoomMember,
content: to_raw_value(&RoomMemberEventContent {
membership: MembershipState::Join,
displayname: services.users.displayname(sender_user)?,
avatar_url: services.users.avatar_url(sender_user)?,
displayname: services.users.displayname(sender_user).await.ok(),
avatar_url: services.users.avatar_url(sender_user).await.ok(),
is_direct: None,
third_party_invite: None,
blurhash: services.users.blurhash(sender_user)?,
blurhash: services.users.blurhash(sender_user).await.ok(),
reason: None,
join_authorized_via_users_server: None,
})
@ -739,10 +762,11 @@ pub(crate) async fn upgrade_room_route(
let event_content = match services
.rooms
.state_accessor
.room_state_get(&body.room_id, event_type, "")?
.room_state_get(&body.room_id, event_type, "")
.await
{
Some(v) => v.content.clone(),
None => continue, // Skipping missing events.
Ok(v) => v.content.clone(),
Err(_) => continue, // Skipping missing events.
};
services
@ -765,21 +789,23 @@ pub(crate) async fn upgrade_room_route(
}
// Moves any local aliases to the new room
for alias in services
let mut local_aliases = services
.rooms
.alias
.local_aliases_for_room(&body.room_id)
.filter_map(Result::ok)
{
.boxed();
while let Some(alias) = local_aliases.next().await {
services
.rooms
.alias
.remove_alias(&alias, sender_user)
.remove_alias(alias, sender_user)
.await?;
services
.rooms
.alias
.set_alias(&alias, &replacement_room, sender_user)?;
.set_alias(alias, &replacement_room, sender_user)?;
}
// Get the old room power levels
@ -787,12 +813,12 @@ pub(crate) async fn upgrade_room_route(
services
.rooms
.state_accessor
.room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")?
.ok_or_else(|| Error::bad_database("Found room without m.room.create event."))?
.room_state_get(&body.room_id, &StateEventType::RoomPowerLevels, "")
.await
.map_err(|_| err!(Database("Found room without m.room.create event.")))?
.content
.get(),
)
.map_err(|_| Error::bad_database("Invalid room event in database."))?;
)?;
// Setting events_default and invite to the greater of 50 and users_default + 1
let new_level = max(
@ -800,9 +826,7 @@ pub(crate) async fn upgrade_room_route(
power_levels_event_content
.users_default
.checked_add(int!(1))
.ok_or_else(|| {
Error::BadRequest(ErrorKind::BadJson, "users_default power levels event content is not valid")
})?,
.ok_or_else(|| err!(Request(BadJson("users_default power levels event content is not valid"))))?,
);
power_levels_event_content.events_default = new_level;
power_levels_event_content.invite = new_level;
@ -921,8 +945,9 @@ async fn room_alias_check(
if services
.rooms
.alias
.resolve_local_alias(&full_room_alias)?
.is_some()
.resolve_local_alias(&full_room_alias)
.await
.is_ok()
{
return Err(Error::BadRequest(ErrorKind::RoomInUse, "Room alias already exists."));
}

View file

@ -1,6 +1,12 @@
use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{
debug,
utils::{IterStream, ReadyExt},
Err,
};
use futures::{FutureExt, StreamExt};
use ruma::{
api::client::{
error::ErrorKind,
@ -13,7 +19,6 @@ use ruma::{
serde::Raw,
uint, OwnedRoomId,
};
use tracing::debug;
use crate::{Error, Result, Ruma};
@ -32,14 +37,17 @@ pub(crate) async fn search_events_route(
let filter = &search_criteria.filter;
let include_state = &search_criteria.include_state;
let room_ids = filter.rooms.clone().unwrap_or_else(|| {
let room_ids = if let Some(room_ids) = &filter.rooms {
room_ids.clone()
} else {
services
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(Result::ok)
.map(ToOwned::to_owned)
.collect()
});
.await
};
// Use limit or else 10, with maximum 100
let limit: usize = filter
@ -53,18 +61,21 @@ pub(crate) async fn search_events_route(
if include_state.is_some_and(|include_state| include_state) {
for room_id in &room_ids {
if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this room.",
));
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
}
// check if sender_user can see state events
if services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, room_id)?
.user_can_see_state_events(sender_user, room_id)
.await
{
let room_state = services
.rooms
@ -87,10 +98,15 @@ pub(crate) async fn search_events_route(
}
}
let mut searches = Vec::new();
let mut search_vecs = Vec::new();
for room_id in &room_ids {
if !services.rooms.state_cache.is_joined(sender_user, room_id)? {
if !services
.rooms
.state_cache
.is_joined(sender_user, room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view this room.",
@ -100,12 +116,18 @@ pub(crate) async fn search_events_route(
if let Some(search) = services
.rooms
.search
.search_pdus(room_id, &search_criteria.search_term)?
.search_pdus(room_id, &search_criteria.search_term)
.await
{
searches.push(search.0.peekable());
search_vecs.push(search.0);
}
}
let mut searches: Vec<_> = search_vecs
.iter()
.map(|vec| vec.iter().peekable())
.collect();
let skip: usize = match body.next_batch.as_ref().map(|s| s.parse()) {
Some(Ok(s)) => s,
Some(Err(_)) => return Err(Error::BadRequest(ErrorKind::InvalidParam, "Invalid next_batch token.")),
@ -118,8 +140,8 @@ pub(crate) async fn search_events_route(
for _ in 0..next_batch {
if let Some(s) = searches
.iter_mut()
.map(|s| (s.peek().cloned(), s))
.max_by_key(|(peek, _)| peek.clone())
.map(|s| (s.peek().copied(), s))
.max_by_key(|(peek, _)| *peek)
.and_then(|(_, i)| i.next())
{
results.push(s);
@ -127,42 +149,38 @@ pub(crate) async fn search_events_route(
}
let results: Vec<_> = results
.iter()
.into_iter()
.skip(skip)
.filter_map(|result| {
.stream()
.filter_map(|id| services.rooms.timeline.get_pdu_from_id(id).map(Result::ok))
.ready_filter(|pdu| !pdu.is_redacted())
.filter_map(|pdu| async move {
services
.rooms
.timeline
.get_pdu_from_id(result)
.ok()?
.filter(|pdu| {
!pdu.is_redacted()
&& services
.rooms
.state_accessor
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id)
.unwrap_or(false)
})
.map(|pdu| pdu.to_room_event())
.state_accessor
.user_can_see_event(sender_user, &pdu.room_id, &pdu.event_id)
.await
.then_some(pdu)
})
.map(|result| {
Ok::<_, Error>(SearchResult {
context: EventContextResult {
end: None,
events_after: Vec::new(),
events_before: Vec::new(),
profile_info: BTreeMap::new(),
start: None,
},
rank: None,
result: Some(result),
})
})
.filter_map(Result::ok)
.take(limit)
.collect();
.map(|pdu| pdu.to_room_event())
.map(|result| SearchResult {
context: EventContextResult {
end: None,
events_after: Vec::new(),
events_before: Vec::new(),
profile_info: BTreeMap::new(),
start: None,
},
rank: None,
result: Some(result),
})
.collect()
.boxed()
.await;
let more_unloaded_results = searches.iter_mut().any(|s| s.peek().is_some());
let next_batch = more_unloaded_results.then(|| next_batch.to_string());
Ok(search_events::v3::Response::new(ResultCategories {

View file

@ -1,5 +1,7 @@
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{debug, err, info, utils::ReadyExt, warn, Err};
use futures::StreamExt;
use ruma::{
api::client::{
error::ErrorKind,
@ -19,7 +21,6 @@ use ruma::{
UserId,
};
use serde::Deserialize;
use tracing::{debug, info, warn};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{utils, utils::hash, Error, Result, Ruma};
@ -79,21 +80,22 @@ pub(crate) async fn login_route(
UserId::parse(user)
} else {
warn!("Bad login type: {:?}", &body.login_info);
return Err(Error::BadRequest(ErrorKind::forbidden(), "Bad login type."));
return Err!(Request(Forbidden("Bad login type.")));
}
.map_err(|_| Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid."))?;
let hash = services
.users
.password_hash(&user_id)?
.ok_or(Error::BadRequest(ErrorKind::forbidden(), "Wrong username or password."))?;
.password_hash(&user_id)
.await
.map_err(|_| err!(Request(Forbidden("Wrong username or password."))))?;
if hash.is_empty() {
return Err(Error::BadRequest(ErrorKind::UserDeactivated, "The user has been deactivated"));
return Err!(Request(UserDeactivated("The user has been deactivated")));
}
if hash::verify_password(password, &hash).is_err() {
return Err(Error::BadRequest(ErrorKind::forbidden(), "Wrong username or password."));
return Err!(Request(Forbidden("Wrong username or password.")));
}
user_id
@ -112,15 +114,12 @@ pub(crate) async fn login_route(
let username = token.claims.sub.to_lowercase();
UserId::parse_with_server_name(username, services.globals.server_name()).map_err(|e| {
warn!("Failed to parse username from user logging in: {e}");
Error::BadRequest(ErrorKind::InvalidUsername, "Username is invalid.")
})?
UserId::parse_with_server_name(username, services.globals.server_name())
.map_err(|e| err!(Request(InvalidUsername(debug_error!(?e, "Failed to parse login username")))))?
} else {
return Err(Error::BadRequest(
ErrorKind::Unknown,
"Token login is not supported (server has no jwt decoding key).",
));
return Err!(Request(Unknown(
"Token login is not supported (server has no jwt decoding key)."
)));
}
},
#[allow(deprecated)]
@ -169,23 +168,32 @@ pub(crate) async fn login_route(
let token = utils::random_string(TOKEN_LENGTH);
// Determine if device_id was provided and exists in the db for this user
let device_exists = body.device_id.as_ref().map_or(false, |device_id| {
let device_exists = if body.device_id.is_some() {
services
.users
.all_device_ids(&user_id)
.any(|x| x.as_ref().map_or(false, |v| v == device_id))
});
.ready_any(|v| v == device_id)
.await
} else {
false
};
if device_exists {
services.users.set_token(&user_id, &device_id, &token)?;
services
.users
.set_token(&user_id, &device_id, &token)
.await?;
} else {
services.users.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)?;
services
.users
.create_device(
&user_id,
&device_id,
&token,
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
.await?;
}
// send client well-known if specified so the client knows to reconfigure itself
@ -228,10 +236,13 @@ pub(crate) async fn logout_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
services.users.remove_device(sender_user, sender_device)?;
services
.users
.remove_device(sender_user, sender_device)
.await;
// send device list update for user after logout
services.users.mark_device_key_update(sender_user)?;
services.users.mark_device_key_update(sender_user).await;
Ok(logout::v3::Response::new())
}
@ -256,12 +267,14 @@ pub(crate) async fn logout_all_route(
) -> Result<logout_all::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
for device_id in services.users.all_device_ids(sender_user).flatten() {
services.users.remove_device(sender_user, &device_id)?;
}
services
.users
.all_device_ids(sender_user)
.for_each(|device_id| services.users.remove_device(sender_user, device_id))
.await;
// send device list update for user after logout
services.users.mark_device_key_update(sender_user)?;
services.users.mark_device_key_update(sender_user).await;
Ok(logout_all::v3::Response::new())
}

View file

@ -1,7 +1,7 @@
use std::sync::Arc;
use axum::extract::State;
use conduit::{debug_info, error, pdu::PduBuilder, Error, Result};
use conduit::{err, error, pdu::PduBuilder, Err, Error, Result};
use ruma::{
api::client::{
error::ErrorKind,
@ -84,12 +84,10 @@ pub(crate) async fn get_state_events_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
.user_can_see_state_events(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view the room state.",
));
return Err!(Request(Forbidden("You don't have permission to view the room state.")));
}
Ok(get_state_events::v3::Response {
@ -120,22 +118,25 @@ pub(crate) async fn get_state_events_for_key_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(sender_user, &body.room_id)?
.user_can_see_state_events(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You don't have permission to view the room state.",
));
return Err!(Request(Forbidden("You don't have permission to view the room state.")));
}
let event = services
.rooms
.state_accessor
.room_state_get(&body.room_id, &body.event_type, &body.state_key)?
.ok_or_else(|| {
debug_info!("State event {:?} not found in room {:?}", &body.event_type, &body.room_id);
Error::BadRequest(ErrorKind::NotFound, "State event not found.")
.room_state_get(&body.room_id, &body.event_type, &body.state_key)
.await
.map_err(|_| {
err!(Request(NotFound(error!(
room_id = ?body.room_id,
event_type = ?body.event_type,
"State event not found in room.",
))))
})?;
if body
.format
.as_ref()
@ -204,7 +205,7 @@ async fn send_state_event_for_key_helper(
async fn allowed_to_send_state_event(
services: &Services, room_id: &RoomId, event_type: &StateEventType, json: &Raw<AnyStateEventContent>,
) -> Result<()> {
) -> Result {
match event_type {
// Forbid m.room.encryption if encryption is disabled
StateEventType::RoomEncryption => {
@ -214,7 +215,7 @@ async fn allowed_to_send_state_event(
},
// admin room is a sensitive room, it should not ever be made public
StateEventType::RoomJoinRules => {
if let Some(admin_room_id) = services.admin.get_admin_room()? {
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
if admin_room_id == room_id {
if let Ok(join_rule) = serde_json::from_str::<RoomJoinRulesEventContent>(json.json().get()) {
if join_rule.join_rule == JoinRule::Public {
@ -229,7 +230,7 @@ async fn allowed_to_send_state_event(
},
// admin room is a sensitive room, it should not ever be made world readable
StateEventType::RoomHistoryVisibility => {
if let Some(admin_room_id) = services.admin.get_admin_room()? {
if let Ok(admin_room_id) = services.admin.get_admin_room().await {
if admin_room_id == room_id {
if let Ok(visibility_content) =
serde_json::from_str::<RoomHistoryVisibilityEventContent>(json.json().get())
@ -254,23 +255,27 @@ async fn allowed_to_send_state_event(
}
for alias in aliases {
if !services.globals.server_is_ours(alias.server_name())
|| services
.rooms
.alias
.resolve_local_alias(&alias)?
.filter(|room| room == room_id) // Make sure it's the right room
.is_none()
if !services.globals.server_is_ours(alias.server_name()) {
return Err!(Request(Forbidden("canonical_alias must be for this server")));
}
if !services
.rooms
.alias
.resolve_local_alias(&alias)
.await
.is_ok_and(|room| room == room_id)
// Make sure it's the right room
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
"You are only allowed to send canonical_alias events when its aliases already exist",
));
return Err!(Request(Forbidden(
"You are only allowed to send canonical_alias events when its aliases already exist"
)));
}
}
}
},
_ => (),
}
Ok(())
}

File diff suppressed because it is too large Load diff

View file

@ -23,10 +23,11 @@ pub(crate) async fn update_tag_route(
let event = services
.account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)
.await;
let mut tags_event = event.map_or_else(
|| {
|_| {
Ok(TagEvent {
content: TagEventContent {
tags: BTreeMap::new(),
@ -41,12 +42,15 @@ pub(crate) async fn update_tag_route(
.tags
.insert(body.tag.clone().into(), body.tag_info.clone());
services.account_data.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::Tag,
&serde_json::to_value(tags_event).expect("to json value always works"),
)?;
services
.account_data
.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::Tag,
&serde_json::to_value(tags_event).expect("to json value always works"),
)
.await?;
Ok(create_tag::v3::Response {})
}
@ -63,10 +67,11 @@ pub(crate) async fn delete_tag_route(
let event = services
.account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)
.await;
let mut tags_event = event.map_or_else(
|| {
|_| {
Ok(TagEvent {
content: TagEventContent {
tags: BTreeMap::new(),
@ -78,12 +83,15 @@ pub(crate) async fn delete_tag_route(
tags_event.content.tags.remove(&body.tag.clone().into());
services.account_data.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::Tag,
&serde_json::to_value(tags_event).expect("to json value always works"),
)?;
services
.account_data
.update(
Some(&body.room_id),
sender_user,
RoomAccountDataEventType::Tag,
&serde_json::to_value(tags_event).expect("to json value always works"),
)
.await?;
Ok(delete_tag::v3::Response {})
}
@ -100,10 +108,11 @@ pub(crate) async fn get_tags_route(
let event = services
.account_data
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)?;
.get(Some(&body.room_id), sender_user, RoomAccountDataEventType::Tag)
.await;
let tags_event = event.map_or_else(
|| {
|_| {
Ok(TagEvent {
content: TagEventContent {
tags: BTreeMap::new(),

View file

@ -1,4 +1,6 @@
use axum::extract::State;
use conduit::PduEvent;
use futures::StreamExt;
use ruma::{
api::client::{error::ErrorKind, threads::get_threads},
uint,
@ -27,20 +29,23 @@ pub(crate) async fn get_threads_route(
u64::MAX
};
let threads = services
let room_id = &body.room_id;
let threads: Vec<(u64, PduEvent)> = services
.rooms
.threads
.threads_until(sender_user, &body.room_id, from, &body.include)?
.threads_until(sender_user, &body.room_id, from, &body.include)
.await?
.take(limit)
.filter_map(Result::ok)
.filter(|(_, pdu)| {
.filter_map(|(count, pdu)| async move {
services
.rooms
.state_accessor
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
.unwrap_or(false)
.user_can_see_event(sender_user, room_id, &pdu.event_id)
.await
.then_some((count, pdu))
})
.collect::<Vec<_>>();
.collect()
.await;
let next_batch = threads.last().map(|(count, _)| count.to_string());

View file

@ -2,6 +2,7 @@ use std::collections::BTreeMap;
use axum::extract::State;
use conduit::{Error, Result};
use futures::StreamExt;
use ruma::{
api::{
client::{error::ErrorKind, to_device::send_event_to_device},
@ -24,8 +25,9 @@ pub(crate) async fn send_event_to_device_route(
// Check if this is a new transaction id
if services
.transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)?
.is_some()
.existing_txnid(sender_user, sender_device, &body.txn_id)
.await
.is_ok()
{
return Ok(send_event_to_device::v3::Response {});
}
@ -53,31 +55,35 @@ pub(crate) async fn send_event_to_device_route(
continue;
}
let event_type = &body.event_type.to_string();
let event = event
.deserialize_as()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?;
match target_device_id_maybe {
DeviceIdOrAllDevices::DeviceId(target_device_id) => {
services.users.add_to_device_event(
sender_user,
target_user_id,
target_device_id,
&body.event_type.to_string(),
event
.deserialize_as()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?,
)?;
services
.users
.add_to_device_event(sender_user, target_user_id, target_device_id, event_type, event)
.await;
},
DeviceIdOrAllDevices::AllDevices => {
for target_device_id in services.users.all_device_ids(target_user_id) {
services.users.add_to_device_event(
sender_user,
target_user_id,
&target_device_id?,
&body.event_type.to_string(),
event
.deserialize_as()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Event is invalid"))?,
)?;
}
let (event_type, event) = (&event_type, &event);
services
.users
.all_device_ids(target_user_id)
.for_each(|target_device_id| {
services.users.add_to_device_event(
sender_user,
target_user_id,
target_device_id,
event_type,
event.clone(),
)
})
.await;
},
}
}
@ -86,7 +92,7 @@ pub(crate) async fn send_event_to_device_route(
// Save transaction id with empty data
services
.transaction_ids
.add_txnid(sender_user, sender_device, &body.txn_id, &[])?;
.add_txnid(sender_user, sender_device, &body.txn_id, &[]);
Ok(send_event_to_device::v3::Response {})
}

View file

@ -16,7 +16,8 @@ pub(crate) async fn create_typing_event_route(
if !services
.rooms
.state_cache
.is_joined(sender_user, &body.room_id)?
.is_joined(sender_user, &body.room_id)
.await
{
return Err(Error::BadRequest(ErrorKind::forbidden(), "You are not in this room."));
}

View file

@ -2,7 +2,8 @@ use std::collections::BTreeMap;
use axum::extract::State;
use axum_client_ip::InsecureClientIp;
use conduit::{warn, Err};
use conduit::Err;
use futures::StreamExt;
use ruma::{
api::{
client::{
@ -45,7 +46,7 @@ pub(crate) async fn get_mutual_rooms_route(
));
}
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
return Ok(mutual_rooms::unstable::Response {
joined: vec![],
next_batch_token: None,
@ -55,9 +56,10 @@ pub(crate) async fn get_mutual_rooms_route(
let mutual_rooms: Vec<OwnedRoomId> = services
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
.filter_map(Result::ok)
.collect();
.get_shared_rooms(sender_user, &body.user_id)
.map(ToOwned::to_owned)
.collect()
.await;
Ok(mutual_rooms::unstable::Response {
joined: mutual_rooms,
@ -99,7 +101,7 @@ pub(crate) async fn get_room_summary(
let room_id = services.rooms.alias.resolve(&body.room_id_or_alias).await?;
if !services.rooms.metadata.exists(&room_id)? {
if !services.rooms.metadata.exists(&room_id).await {
return Err(Error::BadRequest(ErrorKind::NotFound, "Room is unknown to this server"));
}
@ -108,7 +110,7 @@ pub(crate) async fn get_room_summary(
.rooms
.state_accessor
.is_world_readable(&room_id)
.unwrap_or(false)
.await
{
return Err(Error::BadRequest(
ErrorKind::forbidden(),
@ -122,50 +124,58 @@ pub(crate) async fn get_room_summary(
.rooms
.state_accessor
.get_canonical_alias(&room_id)
.unwrap_or(None),
.await
.ok(),
avatar_url: services
.rooms
.state_accessor
.get_avatar(&room_id)?
.get_avatar(&room_id)
.await
.into_option()
.unwrap_or_default()
.url,
guest_can_join: services.rooms.state_accessor.guest_can_join(&room_id)?,
name: services
.rooms
.state_accessor
.get_name(&room_id)
.unwrap_or(None),
guest_can_join: services.rooms.state_accessor.guest_can_join(&room_id).await,
name: services.rooms.state_accessor.get_name(&room_id).await.ok(),
num_joined_members: services
.rooms
.state_cache
.room_joined_count(&room_id)
.unwrap_or_default()
.unwrap_or_else(|| {
warn!("Room {room_id} has no member count");
0
})
.try_into()
.expect("user count should not be that big"),
.await
.unwrap_or(0)
.try_into()?,
topic: services
.rooms
.state_accessor
.get_room_topic(&room_id)
.unwrap_or(None),
.await
.ok(),
world_readable: services
.rooms
.state_accessor
.is_world_readable(&room_id)
.unwrap_or(false),
join_rule: services.rooms.state_accessor.get_join_rule(&room_id)?.0,
room_type: services.rooms.state_accessor.get_room_type(&room_id)?,
room_version: Some(services.rooms.state.get_room_version(&room_id)?),
.await,
join_rule: services
.rooms
.state_accessor
.get_join_rule(&room_id)
.await
.unwrap_or_default()
.0,
room_type: services
.rooms
.state_accessor
.get_room_type(&room_id)
.await
.ok(),
room_version: services.rooms.state.get_room_version(&room_id).await.ok(),
membership: if let Some(sender_user) = sender_user {
services
.rooms
.state_accessor
.get_member(&room_id, sender_user)?
.map_or_else(|| Some(MembershipState::Leave), |content| Some(content.membership))
.get_member(&room_id, sender_user)
.await
.map_or_else(|_| MembershipState::Leave, |content| content.membership)
.into()
} else {
None
},
@ -173,7 +183,8 @@ pub(crate) async fn get_room_summary(
.rooms
.state_accessor
.get_room_encryption(&room_id)
.unwrap_or_else(|_e| None),
.await
.ok(),
})
}
@ -191,13 +202,14 @@ pub(crate) async fn delete_timezone_key_route(
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services.users.set_timezone(&body.user_id, None).await?;
services.users.set_timezone(&body.user_id, None);
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(delete_timezone_key::unstable::Response {})
@ -217,16 +229,14 @@ pub(crate) async fn set_timezone_key_route(
return Err!(Request(Forbidden("You cannot update the profile of another user")));
}
services
.users
.set_timezone(&body.user_id, body.tz.clone())
.await?;
services.users.set_timezone(&body.user_id, body.tz.clone());
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(set_timezone_key::unstable::Response {})
@ -280,10 +290,11 @@ pub(crate) async fn set_profile_key_route(
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
update_displayname(&services, &body.user_id, Some(profile_key_value.to_string()), all_joined_rooms).await?;
update_displayname(&services, &body.user_id, Some(profile_key_value.to_string()), &all_joined_rooms).await?;
} else if body.key == "avatar_url" {
let mxc = ruma::OwnedMxcUri::from(profile_key_value.to_string());
@ -291,21 +302,23 @@ pub(crate) async fn set_profile_key_route(
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
update_avatar_url(&services, &body.user_id, Some(mxc), None, all_joined_rooms).await?;
update_avatar_url(&services, &body.user_id, Some(mxc), None, &all_joined_rooms).await?;
} else {
services
.users
.set_profile_key(&body.user_id, &body.key, Some(profile_key_value.clone()))?;
.set_profile_key(&body.user_id, &body.key, Some(profile_key_value.clone()));
}
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(set_profile_key::unstable::Response {})
@ -335,30 +348,33 @@ pub(crate) async fn delete_profile_key_route(
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
update_displayname(&services, &body.user_id, None, all_joined_rooms).await?;
update_displayname(&services, &body.user_id, None, &all_joined_rooms).await?;
} else if body.key == "avatar_url" {
let all_joined_rooms: Vec<OwnedRoomId> = services
.rooms
.state_cache
.rooms_joined(&body.user_id)
.filter_map(Result::ok)
.collect();
.map(Into::into)
.collect()
.await;
update_avatar_url(&services, &body.user_id, None, None, all_joined_rooms).await?;
update_avatar_url(&services, &body.user_id, None, None, &all_joined_rooms).await?;
} else {
services
.users
.set_profile_key(&body.user_id, &body.key, None)?;
.set_profile_key(&body.user_id, &body.key, None);
}
if services.globals.allow_local_presence() {
// Presence update
services
.presence
.ping_presence(&body.user_id, &PresenceState::Online)?;
.ping_presence(&body.user_id, &PresenceState::Online)
.await?;
}
Ok(delete_profile_key::unstable::Response {})
@ -386,26 +402,25 @@ pub(crate) async fn get_timezone_key_route(
)
.await
{
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone())
.await?;
.set_timezone(&body.user_id, response.tz.clone());
return Ok(get_timezone_key::unstable::Response {
tz: response.tz,
@ -413,14 +428,14 @@ pub(crate) async fn get_timezone_key_route(
}
}
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
}
Ok(get_timezone_key::unstable::Response {
tz: services.users.timezone(&body.user_id)?,
tz: services.users.timezone(&body.user_id).await.ok(),
})
}
@ -448,32 +463,31 @@ pub(crate) async fn get_profile_key_route(
)
.await
{
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
services.users.create(&body.user_id, None)?;
}
services
.users
.set_displayname(&body.user_id, response.displayname.clone())
.await?;
.set_displayname(&body.user_id, response.displayname.clone());
services
.users
.set_avatar_url(&body.user_id, response.avatar_url.clone())
.await?;
.set_avatar_url(&body.user_id, response.avatar_url.clone());
services
.users
.set_blurhash(&body.user_id, response.blurhash.clone())
.await?;
.set_blurhash(&body.user_id, response.blurhash.clone());
services
.users
.set_timezone(&body.user_id, response.tz.clone())
.await?;
.set_timezone(&body.user_id, response.tz.clone());
if let Some(value) = response.custom_profile_fields.get(&body.key) {
profile_key_value.insert(body.key.clone(), value.clone());
services
.users
.set_profile_key(&body.user_id, &body.key, Some(value.clone()))?;
.set_profile_key(&body.user_id, &body.key, Some(value.clone()));
} else {
return Err!(Request(NotFound("The requested profile key does not exist.")));
}
@ -484,13 +498,13 @@ pub(crate) async fn get_profile_key_route(
}
}
if !services.users.exists(&body.user_id)? {
if !services.users.exists(&body.user_id).await {
// Return 404 if this user doesn't exist and we couldn't fetch it over
// federation
return Err(Error::BadRequest(ErrorKind::NotFound, "Profile was not found."));
return Err!(Request(NotFound("Profile was not found.")));
}
if let Some(value) = services.users.profile_key(&body.user_id, &body.key)? {
if let Ok(value) = services.users.profile_key(&body.user_id, &body.key).await {
profile_key_value.insert(body.key.clone(), value);
} else {
return Err!(Request(NotFound("The requested profile key does not exist.")));

View file

@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use axum::{extract::State, response::IntoResponse, Json};
use futures::StreamExt;
use ruma::api::client::{
discovery::{
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
@ -173,7 +174,7 @@ pub(crate) async fn conduwuit_server_version() -> Result<impl IntoResponse> {
/// homeserver. Endpoint is disabled if federation is disabled for privacy. This
/// only includes active users (not deactivated, no guests, etc)
pub(crate) async fn conduwuit_local_user_count(State(services): State<crate::State>) -> Result<impl IntoResponse> {
let user_count = services.users.list_local_users()?.len();
let user_count = services.users.list_local_users().count().await;
Ok(Json(serde_json::json!({
"count": user_count

View file

@ -1,4 +1,5 @@
use axum::extract::State;
use futures::{pin_mut, StreamExt};
use ruma::{
api::client::user_directory::search_users,
events::{
@ -21,14 +22,12 @@ pub(crate) async fn search_users_route(
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let limit = usize::try_from(body.limit).unwrap_or(10); // default limit is 10
let mut users = services.users.iter().filter_map(|user_id| {
let users = services.users.stream().filter_map(|user_id| async {
// Filter out buggy users (they should not exist, but you never know...)
let user_id = user_id.ok()?;
let user = search_users::v3::User {
user_id: user_id.clone(),
display_name: services.users.displayname(&user_id).ok()?,
avatar_url: services.users.avatar_url(&user_id).ok()?,
user_id: user_id.to_owned(),
display_name: services.users.displayname(user_id).await.ok(),
avatar_url: services.users.avatar_url(user_id).await.ok(),
};
let user_id_matches = user
@ -56,20 +55,19 @@ pub(crate) async fn search_users_route(
let user_is_in_public_rooms = services
.rooms
.state_cache
.rooms_joined(&user_id)
.filter_map(Result::ok)
.any(|room| {
.rooms_joined(&user.user_id)
.any(|room| async move {
services
.rooms
.state_accessor
.room_state_get(&room, &StateEventType::RoomJoinRules, "")
.room_state_get(room, &StateEventType::RoomJoinRules, "")
.await
.map_or(false, |event| {
event.map_or(false, |event| {
serde_json::from_str(event.content.get())
.map_or(false, |r: RoomJoinRulesEventContent| r.join_rule == JoinRule::Public)
})
serde_json::from_str(event.content.get())
.map_or(false, |r: RoomJoinRulesEventContent| r.join_rule == JoinRule::Public)
})
});
})
.await;
if user_is_in_public_rooms {
user_visible = true;
@ -77,25 +75,22 @@ pub(crate) async fn search_users_route(
let user_is_in_shared_rooms = services
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), user_id])
.ok()?
.next()
.is_some();
.has_shared_rooms(sender_user, &user.user_id)
.await;
if user_is_in_shared_rooms {
user_visible = true;
}
}
if !user_visible {
return None;
}
Some(user)
user_visible.then_some(user)
});
let results = users.by_ref().take(limit).collect();
let limited = users.next().is_some();
pin_mut!(users);
let limited = users.by_ref().next().await.is_some();
let results = users.take(limit).collect().await;
Ok(search_users::v3::Response {
results,