Compare commits

..

9 Commits

Author SHA1 Message Date
timedout 1ad0bd5d0d fix: Don't be so aggressive when validating policy server signatures 2026-05-26 08:20:06 -07:00
Jacob Taylor 8bea04b1ed feat: Merge ginger/oauth 2026-05-26 08:14:45 -07:00
Jacob Taylor 2e08ffe646 feat: Merge ginger/kill-sync-tokens 2026-05-26 08:14:16 -07:00
Jacob Taylor 3f83989c83 fix: Pre-Commit Lint Compliance Maneuver 2026-05-26 08:14:16 -07:00
Jacob Taylor e898d147ce feat: Bump one cache a bit 2026-05-26 08:14:16 -07:00
Jacob Taylor 4379d662b8 upgrade some logs to info 2026-05-26 08:14:16 -07:00
Jacob Taylor 5958c6c2dd exponential backoff is now just bees. did you want bees? no? well you have them now. congrats 2026-05-26 08:14:16 -07:00
Jacob Taylor 0b4c91ca2b enable converged 6g at the edge in continuwuity
sender_workers scaling. this time, with feeling!
2026-05-26 08:14:16 -07:00
Jacob Taylor b92f693717 bump the number of allowed immutable memtables by 1, to allow for greater flood protection
this should probably not be applied if you have rocksdb_atomic_flush = false (the default)
2026-05-26 08:14:16 -07:00
95 changed files with 777 additions and 851 deletions
+1
View File
@@ -0,0 +1 @@
Added support for Matrix 1.16's `state_after` feature, allowing clients which understand it to sync room state changes more reliably. Contributed by @ginger.
+1
View File
@@ -0,0 +1 @@
Adjusted legacy sync logic to no longer use the `roomsynctoken_shortstatehash` database column. Once this change has been confirmed to be stable and reliable, a future update will remove it entirely, significantly decreasing database sizes. Contributed by @ginger.
+2 -4
View File
@@ -1793,11 +1793,9 @@
#stream_amplification = 1024
# Number of sender task workers; determines sender parallelism. Default is
# '0' which means the value is determined internally, likely matching the
# number of tokio worker-threads or number of cores, etc. Override by
# setting a non-zero value.
# core count. Override by setting a different value.
#
#sender_workers = 0
#sender_workers = core count
# Enables listener sockets; can be set to false to disable listening. This
# option is intended for developer/diagnostic purposes only.
+1 -1
View File
@@ -13,7 +13,7 @@ pub(crate) async fn ban_room(
State(services): State<crate::State>,
body: Ruma<rooms::ban::v1::Request>,
) -> Result<rooms::ban::v1::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services.users.is_admin(sender_user).await {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
+1 -1
View File
@@ -13,7 +13,7 @@ pub(crate) async fn list_rooms(
State(services): State<crate::State>,
body: Ruma<rooms::list::v1::Request>,
) -> Result<rooms::list::v1::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services.users.is_admin(sender_user).await {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
+46 -35
View File
@@ -27,7 +27,7 @@ use ruma::{
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use crate::{Ruma, router::ClientIdentity};
use crate::Ruma;
pub(crate) mod register;
pub(crate) mod threepid;
@@ -49,16 +49,41 @@ pub(crate) async fn get_register_available_route(
ClientIp(client): ClientIp,
body: Ruma<get_username_availability::v3::Request>,
) -> Result<get_username_availability::v3::Response> {
let _ = services
.users
.determine_registration_user_id(
Some(body.username.clone()),
None,
body.identity
.as_ref()
.and_then(ClientIdentity::appservice_info),
)
.await?;
// Validate user id
let user_id =
match UserId::parse_with_server_name(&body.username, services.globals.server_name()) {
| Ok(user_id) => {
if let Err(e) = user_id.validate_strict() {
return Err!(Request(InvalidUsername(debug_warn!(
"Username {} contains disallowed characters or spaces: {e}",
body.username
))));
}
user_id
},
| Err(e) => {
return Err!(Request(InvalidUsername(debug_warn!(
"Username {} is not valid: {e}",
body.username
))));
},
};
// Check if username is creative enough
if services.users.exists(&user_id).await {
return Err!(Request(UserInUse("User ID is not available.")));
}
if let Some(ref info) = body.appservice_info {
if !info.is_user_match(&user_id) {
return Err!(Request(Exclusive("Username is not in an appservice namespace.")));
}
}
if services.appservice.is_exclusive_user_id(&user_id).await {
return Err!(Request(Exclusive("Username is reserved by an appservice.")));
}
Ok(get_username_availability::v3::Response::new(true))
}
@@ -86,7 +111,7 @@ pub(crate) async fn change_password_route(
ClientIp(client): ClientIp,
body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
let identity = if let Some(identity) = body.identity.as_ref() {
let identity = if let Some(ref user_id) = body.sender_user {
// A signed-in user is trying to change their password, prompt them for their
// existing one
@@ -96,7 +121,7 @@ pub(crate) async fn change_password_route(
&body.auth,
vec![AuthFlow::new(vec![AuthType::Password])],
Box::default(),
Some(UiaaInitiator::new(identity.sender_user(), identity.sender_device())),
Some(UiaaInitiator::new(user_id, body.sender_device())),
)
.await?
} else {
@@ -132,12 +157,7 @@ pub(crate) async fn change_password_route(
services
.users
.all_device_ids(&sender_user)
.ready_filter(|id| {
body.identity
.as_ref()
.and_then(|identity| identity.sender_device())
.is_none_or(|sender_device| sender_device != *id)
})
.ready_filter(|id| *id != body.sender_device())
.for_each(async |id| services.users.remove_device(&sender_user, &id).await)
.await;
@@ -153,12 +173,7 @@ pub(crate) async fn change_password_route(
.await
.ok()
.as_ref()
.is_some_and(|pusher_device| {
body.identity
.as_ref()
.and_then(|identity| identity.sender_device())
.is_none_or(|sender_device| sender_device != *pusher_device)
})
.is_some_and(|pusher_device| pusher_device != body.sender_device())
.then_some(pushkey)
})
.for_each(async |pushkey| {
@@ -226,11 +241,9 @@ pub(crate) async fn whoami_route(
State(_): State<crate::State>,
body: Ruma<whoami::v3::Request>,
) -> Result<whoami::v3::Response> {
Ok(
assign!(whoami::v3::Response::new(body.identity.sender_user().to_owned(), false), {
device_id: body.identity.sender_device().map(ToOwned::to_owned),
}),
)
Ok(assign!(whoami::v3::Response::new(body.sender_user().to_owned(), false), {
device_id: body.sender_device,
}))
}
/// # `POST /_matrix/client/r0/account/deactivate`
@@ -252,13 +265,11 @@ pub(crate) async fn deactivate_route(
) -> Result<deactivate::v3::Response> {
// Authentication for this endpoint is technically optional,
// but we require the user to be logged in
let identity = body
.identity
let sender_user = body
.sender_user
.as_ref()
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
let sender_user = identity.sender_user();
if !services.config.allow_deactivation {
return Err!(Request(Unauthorized(
"You may not deactivate your own account. Contact your server's administrator for \
@@ -269,7 +280,7 @@ pub(crate) async fn deactivate_route(
// Prompt the user to confirm with their password using UIAA
let _ = services
.uiaa
.authenticate_password(&body.auth, sender_user, identity.sender_device(), None)
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
.await?;
// Remove profile pictures and display name
+2 -2
View File
@@ -48,7 +48,7 @@ pub(crate) async fn register_route(
let allow_registration =
services.config.allow_registration || services.firstrun.is_first_run();
if !allow_registration && body.identity.is_none() {
if !allow_registration && body.appservice_info.is_none() {
info!(
?body.username,
?body.initial_device_display_name,
@@ -61,7 +61,7 @@ pub(crate) async fn register_route(
}
let user_id = if body.body.login_type == Some(LoginType::ApplicationService) {
let Some(appservice_info) = &body.identity else {
let Some(appservice_info) = &body.appservice_info else {
return Err!(Request(Forbidden(
"Only appservices can use the appservice login type."
)));
+10 -19
View File
@@ -13,7 +13,7 @@ use ruma::{
};
use service::mailer::messages;
use crate::{Ruma, router::ClientIdentity};
use crate::Ruma;
/// # `GET _matrix/client/v3/account/3pid`
///
@@ -22,7 +22,7 @@ pub(crate) async fn third_party_route(
State(services): State<crate::State>,
body: Ruma<get_3pids::v3::Request>,
) -> Result<get_3pids::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut threepids = vec![];
if let Some(email) = services
@@ -53,14 +53,6 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
State(services): State<crate::State>,
body: Ruma<request_3pid_management_token_via_email::v3::Request>,
) -> Result<request_3pid_management_token_via_email::v3::Response> {
// Authentication for this endpoint is technically optional,
// but we require the user to be logged in
let sender_user = body
.identity
.as_ref()
.map(ClientIdentity::sender_user)
.ok_or_else(|| err!(Request(MissingToken("Missing access token."))))?;
if !services.threepid.email_requirement().may_change() {
return Err!(Request(Forbidden("You may not change your email address.")));
}
@@ -84,7 +76,7 @@ pub(crate) async fn request_3pid_management_token_via_email_route(
Mailbox::new(None, email),
|verification_link| messages::ChangeEmail {
server_name: services.config.server_name.as_str(),
user_id: Some(sender_user),
user_id: body.sender_user.as_deref(),
verification_link,
},
&body.client_secret,
@@ -115,6 +107,8 @@ pub(crate) async fn add_3pid_route(
State(services): State<crate::State>,
body: Ruma<add_3pid::v3::Request>,
) -> Result<add_3pid::v3::Response> {
let sender_user = body.sender_user();
if !services.threepid.email_requirement().may_change() {
return Err!(Request(Forbidden("You may not change your email address.")));
}
@@ -122,12 +116,7 @@ pub(crate) async fn add_3pid_route(
// Require password auth to add an email
let _ = services
.uiaa
.authenticate_password(
&body.auth,
body.identity.sender_user(),
body.identity.sender_device(),
None,
)
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
.await?;
let email = services
@@ -139,7 +128,7 @@ pub(crate) async fn add_3pid_route(
services
.threepid
.associate_localpart_email(body.identity.sender_user().localpart(), &email)
.associate_localpart_email(sender_user.localpart(), &email)
.await?;
Ok(add_3pid::v3::Response::new())
@@ -150,6 +139,8 @@ pub(crate) async fn delete_3pid_route(
State(services): State<crate::State>,
body: Ruma<delete_3pid::v3::Request>,
) -> Result<delete_3pid::v3::Response> {
let sender_user = body.sender_user();
if body.medium != Medium::Email {
return Ok(delete_3pid::v3::Response::new(ThirdPartyIdRemovalStatus::NoSupport));
}
@@ -160,7 +151,7 @@ pub(crate) async fn delete_3pid_route(
if services
.threepid
.disassociate_localpart_email(body.identity.sender_user().localpart())
.disassociate_localpart_email(sender_user.localpart())
.await
.is_none()
{
+8 -8
View File
@@ -22,9 +22,9 @@ pub(crate) async fn set_global_account_data_route(
State(services): State<crate::State>,
body: Ruma<set_global_account_data::v3::Request>,
) -> Result<set_global_account_data::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user != body.user_id && !body.identity.is_appservice() {
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot set account data for other users.")));
}
@@ -47,9 +47,9 @@ pub(crate) async fn set_room_account_data_route(
State(services): State<crate::State>,
body: Ruma<set_room_account_data::v3::Request>,
) -> Result<set_room_account_data::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user != body.user_id && !body.identity.is_appservice() {
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot set account data for other users.")));
}
@@ -72,9 +72,9 @@ pub(crate) async fn get_global_account_data_route(
State(services): State<crate::State>,
body: Ruma<get_global_account_data::v3::Request>,
) -> Result<get_global_account_data::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user != body.user_id && !body.identity.is_appservice() {
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot get account data of other users.")));
}
@@ -94,9 +94,9 @@ pub(crate) async fn get_room_account_data_route(
State(services): State<crate::State>,
body: Ruma<get_room_account_data::v3::Request>,
) -> Result<get_room_account_data::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user != body.user_id && !body.identity.is_appservice() {
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot get account data of other users.")));
}
+5 -6
View File
@@ -12,11 +12,10 @@ pub(crate) async fn get_suspended_status(
State(services): State<crate::State>,
body: Ruma<get_suspended::v1::Request>,
) -> Result<get_suspended::v1::Response> {
let (admin, active) = join(
services.users.is_admin(body.identity.sender_user()),
services.users.is_active(&body.user_id),
)
.await;
let sender_user = body.sender_user();
let (admin, active) =
join(services.users.is_admin(sender_user), services.users.is_active(&body.user_id)).await;
if !admin {
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
}
@@ -38,7 +37,7 @@ pub(crate) async fn put_suspended_status(
State(services): State<crate::State>,
body: Ruma<set_suspended::v1::Request>,
) -> Result<set_suspended::v1::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let (sender_admin, active, target_admin) = join3(
services.users.is_admin(sender_user),
+4 -4
View File
@@ -11,7 +11,7 @@ pub(crate) async fn create_alias_route(
State(services): State<crate::State>,
body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -19,7 +19,7 @@ pub(crate) async fn create_alias_route(
services
.rooms
.alias
.appservice_checks(&body.room_alias, body.identity.appservice_info())
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
// this isn't apart of alias_checks or delete alias route because we should
@@ -59,7 +59,7 @@ pub(crate) async fn delete_alias_route(
State(services): State<crate::State>,
body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -67,7 +67,7 @@ pub(crate) async fn delete_alias_route(
services
.rooms
.alias
.appservice_checks(&body.room_alias, body.identity.appservice_info())
.appservice_checks(&body.room_alias, &body.appservice_info)
.await?;
services
+4 -2
View File
@@ -1,5 +1,5 @@
use axum::extract::State;
use conduwuit::{Err, Result};
use conduwuit::{Err, Result, err};
use ruma::{
api::{appservice::ping, client::appservice::request_ping},
assign,
@@ -15,7 +15,9 @@ pub(crate) async fn appservice_ping(
State(services): State<crate::State>,
body: Ruma<request_ping::v1::Request>,
) -> Result<request_ping::v1::Response> {
let appservice_info = &body.identity;
let appservice_info = body.appservice_info.as_ref().ok_or_else(|| {
err!(Request(Forbidden("This endpoint can only be called by appservices.")))
})?;
if body.appservice_id != appservice_info.registration.id {
return Err!(Request(Forbidden(
+26 -50
View File
@@ -25,7 +25,7 @@ pub(crate) async fn create_backup_version_route(
) -> Result<create_backup_version::v3::Response> {
let version = services
.key_backups
.create_backup(body.identity.sender_user(), &body.algorithm)?;
.create_backup(body.sender_user(), &body.algorithm)?;
Ok(create_backup_version::v3::Response::new(version))
}
@@ -40,7 +40,7 @@ pub(crate) async fn update_backup_version_route(
) -> Result<update_backup_version::v3::Response> {
services
.key_backups
.update_backup(body.identity.sender_user(), &body.version, &body.algorithm)
.update_backup(body.sender_user(), &body.version, &body.algorithm)
.await?;
Ok(update_backup_version::v3::Response::new())
@@ -55,11 +55,11 @@ pub(crate) async fn get_latest_backup_info_route(
) -> Result<get_latest_backup_info::v3::Response> {
let (version, algorithm) = services
.key_backups
.get_latest_backup(body.identity.sender_user())
.get_latest_backup(body.sender_user())
.await
.map_err(|_| err!(Request(NotFound("Key backup does not exist."))))?;
let (count, etag) = get_count_etag(&services, body.identity.sender_user(), &version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &version).await;
Ok(get_latest_backup_info::v3::Response::new(algorithm, count, etag, version))
}
@@ -73,14 +73,13 @@ pub(crate) async fn get_backup_info_route(
) -> Result<get_backup_info::v3::Response> {
let algorithm = services
.key_backups
.get_backup(body.identity.sender_user(), &body.version)
.get_backup(body.sender_user(), &body.version)
.await
.map_err(|_| {
err!(Request(NotFound("Key backup does not exist at version {:?}", body.version)))
})?;
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(get_backup_info::v3::Response::new(algorithm, count, etag, body.version.clone()))
}
@@ -97,7 +96,7 @@ pub(crate) async fn delete_backup_version_route(
) -> Result<delete_backup_version::v3::Response> {
services
.key_backups
.delete_backup(body.identity.sender_user(), &body.version)
.delete_backup(body.sender_user(), &body.version)
.await;
Ok(delete_backup_version::v3::Response::new())
@@ -117,7 +116,7 @@ pub(crate) async fn add_backup_keys_route(
) -> Result<add_backup_keys::v3::Response> {
if services
.key_backups
.get_latest_backup_version(body.identity.sender_user())
.get_latest_backup_version(body.sender_user())
.await
.is_ok_and(|version| version != body.version)
{
@@ -130,19 +129,12 @@ pub(crate) async fn add_backup_keys_route(
for (session_id, key_data) in &room.sessions {
services
.key_backups
.add_key(
body.identity.sender_user(),
&body.version,
room_id,
session_id,
key_data,
)
.add_key(body.sender_user(), &body.version, room_id, session_id, key_data)
.await?;
}
}
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(add_backup_keys::v3::Response::new(etag, count))
}
@@ -161,7 +153,7 @@ pub(crate) async fn add_backup_keys_for_room_route(
) -> Result<add_backup_keys_for_room::v3::Response> {
if services
.key_backups
.get_latest_backup_version(body.identity.sender_user())
.get_latest_backup_version(body.sender_user())
.await
.is_ok_and(|version| version != body.version)
{
@@ -173,18 +165,11 @@ pub(crate) async fn add_backup_keys_for_room_route(
for (session_id, key_data) in &body.sessions {
services
.key_backups
.add_key(
body.identity.sender_user(),
&body.version,
&body.room_id,
session_id,
key_data,
)
.add_key(body.sender_user(), &body.version, &body.room_id, session_id, key_data)
.await?;
}
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(add_backup_keys_for_room::v3::Response::new(etag, count))
}
@@ -203,7 +188,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
) -> Result<add_backup_keys_for_session::v3::Response> {
if services
.key_backups
.get_latest_backup_version(body.identity.sender_user())
.get_latest_backup_version(body.sender_user())
.await
.is_ok_and(|version| version != body.version)
{
@@ -216,7 +201,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
let mut ok_to_replace = true;
if let Some(old_key) = &services
.key_backups
.get_session(body.identity.sender_user(), &body.version, &body.room_id, &body.session_id)
.get_session(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.await
.ok()
{
@@ -275,7 +260,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
services
.key_backups
.add_key(
body.identity.sender_user(),
body.sender_user(),
&body.version,
&body.room_id,
&body.session_id,
@@ -284,8 +269,7 @@ pub(crate) async fn add_backup_keys_for_session_route(
.await?;
}
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(add_backup_keys_for_session::v3::Response::new(etag, count))
}
@@ -299,7 +283,7 @@ pub(crate) async fn get_backup_keys_route(
) -> Result<get_backup_keys::v3::Response> {
let rooms = services
.key_backups
.get_all(body.identity.sender_user(), &body.version)
.get_all(body.sender_user(), &body.version)
.await;
Ok(get_backup_keys::v3::Response::new(rooms))
@@ -314,7 +298,7 @@ pub(crate) async fn get_backup_keys_for_room_route(
) -> Result<get_backup_keys_for_room::v3::Response> {
let sessions = services
.key_backups
.get_room(body.identity.sender_user(), &body.version, &body.room_id)
.get_room(body.sender_user(), &body.version, &body.room_id)
.await;
Ok(get_backup_keys_for_room::v3::Response::new(sessions))
@@ -329,7 +313,7 @@ pub(crate) async fn get_backup_keys_for_session_route(
) -> Result<get_backup_keys_for_session::v3::Response> {
let key_data = services
.key_backups
.get_session(body.identity.sender_user(), &body.version, &body.room_id, &body.session_id)
.get_session(body.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."))))
@@ -347,11 +331,10 @@ pub(crate) async fn delete_backup_keys_route(
) -> Result<delete_backup_keys::v3::Response> {
services
.key_backups
.delete_all_keys(body.identity.sender_user(), &body.version)
.delete_all_keys(body.sender_user(), &body.version)
.await;
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(delete_backup_keys::v3::Response::new(etag, count))
}
@@ -365,11 +348,10 @@ pub(crate) async fn delete_backup_keys_for_room_route(
) -> Result<delete_backup_keys_for_room::v3::Response> {
services
.key_backups
.delete_room_keys(body.identity.sender_user(), &body.version, &body.room_id)
.delete_room_keys(body.sender_user(), &body.version, &body.room_id)
.await;
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(delete_backup_keys_for_room::v3::Response::new(etag, count))
}
@@ -383,16 +365,10 @@ pub(crate) async fn delete_backup_keys_for_session_route(
) -> Result<delete_backup_keys_for_session::v3::Response> {
services
.key_backups
.delete_room_key(
body.identity.sender_user(),
&body.version,
&body.room_id,
&body.session_id,
)
.delete_room_key(body.sender_user(), &body.version, &body.room_id, &body.session_id)
.await;
let (count, etag) =
get_count_etag(&services, body.identity.sender_user(), &body.version).await;
let (count, etag) = get_count_etag(&services, body.sender_user(), &body.version).await;
Ok(delete_backup_keys_for_session::v3::Response::new(etag, count))
}
+5 -1
View File
@@ -48,7 +48,11 @@ pub(crate) async fn get_capabilities_route(
json!({"enabled": services.config.forget_forced_upon_leave}),
)?;
if services.users.is_admin(body.identity.sender_user()).await {
if services
.users
.is_admin(body.sender_user.as_ref().unwrap())
.await
{
// Advertise suspension API
capabilities.set("uk.timedout.msc4323", json!({"suspend": true, "lock": false}))?;
}
+3 -3
View File
@@ -37,8 +37,8 @@ pub(crate) async fn get_context_route(
State(services): State<crate::State>,
body: Ruma<get_context::v3::Request>,
) -> Result<get_context::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.sender_device();
let sender = body.sender();
let (sender_user, sender_device) = sender;
let room_id = &body.room_id;
let event_id = &body.event_id;
let filter = &body.filter;
@@ -143,7 +143,7 @@ pub(crate) async fn get_context_route(
let lazy_loading_context = lazy_loading::Context {
user_id: sender_user,
device_id: sender_device,
device_id: Some(sender_device),
room_id,
token: Some(base_count.into_unsigned()),
options: Some(&filter.lazy_load_options),
+10 -5
View File
@@ -25,11 +25,16 @@ pub(crate) async fn put_dehydrated_device_route(
ClientIp(client): ClientIp,
body: Ruma<put_dehydrated_device::Request>,
) -> Result<put_dehydrated_device::Response> {
let device_id = body.device_id.clone();
let sender_user = body
.sender_user
.as_deref()
.expect("AccessToken authentication required");
let device_id = body.body.device_id.clone();
services
.users
.set_dehydrated_device(body.identity.sender_user(), body.body)
.set_dehydrated_device(sender_user, body.body)
.await?;
Ok(put_dehydrated_device::Response::new(device_id))
@@ -44,7 +49,7 @@ pub(crate) async fn delete_dehydrated_device_route(
ClientIp(client): ClientIp,
body: Ruma<delete_dehydrated_device::Request>,
) -> Result<delete_dehydrated_device::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let device_id = services.users.get_dehydrated_device_id(sender_user).await?;
@@ -62,7 +67,7 @@ pub(crate) async fn get_dehydrated_device_route(
ClientIp(client): ClientIp,
body: Ruma<get_dehydrated_device::Request>,
) -> Result<get_dehydrated_device::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let device = services.users.get_dehydrated_device(sender_user).await?;
@@ -78,7 +83,7 @@ pub(crate) async fn get_dehydrated_events_route(
ClientIp(client): ClientIp,
body: Ruma<get_events::Request>,
) -> Result<get_events::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let device_id = &body.body.device_id;
let existing_id = services.users.get_dehydrated_device_id(sender_user).await;
+12 -10
View File
@@ -20,7 +20,7 @@ pub(crate) async fn get_devices_route(
) -> Result<get_devices::v3::Response> {
let devices: Vec<device::Device> = services
.users
.all_devices_metadata(body.identity.sender_user())
.all_devices_metadata(body.sender_user())
.collect()
.await;
@@ -36,7 +36,7 @@ pub(crate) async fn get_device_route(
) -> Result<get_device::v3::Response> {
let device = services
.users
.get_device_metadata(body.identity.sender_user(), &body.body.device_id)
.get_device_metadata(body.sender_user(), &body.body.device_id)
.await
.map_err(|_| err!(Request(NotFound("Device not found."))))?;
@@ -52,8 +52,8 @@ pub(crate) async fn update_device_route(
ClientIp(client): ClientIp,
body: Ruma<update_device::v3::Request>,
) -> Result<update_device::v3::Response> {
let sender_user = body.identity.sender_user();
let appservice = body.identity.appservice_info();
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
match services
.users
@@ -118,14 +118,15 @@ pub(crate) async fn delete_device_route(
State(services): State<crate::State>,
body: Ruma<delete_device::v3::Request>,
) -> Result<delete_device::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
// Appservices get to skip UIAA for this endpoint
if let Some(sender_device) = body.identity.sender_device() {
if appservice.is_none() {
// Prompt the user to confirm with their password using UIAA
let _ = services
.uiaa
.authenticate_password(&body.auth, sender_user, Some(sender_device), None)
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
.await?;
}
@@ -153,14 +154,15 @@ pub(crate) async fn delete_devices_route(
State(services): State<crate::State>,
body: Ruma<delete_devices::v3::Request>,
) -> Result<delete_devices::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let appservice = body.appservice_info.as_ref();
// Appservices get to skip UIAA for this endpoint
if let Some(sender_device) = body.identity.sender_device() {
if appservice.is_none() {
// Prompt the user to confirm with their password using UIAA
let _ = services
.uiaa
.authenticate_password(&body.auth, sender_user, Some(sender_device), None)
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
.await?;
}
+2 -2
View File
@@ -112,7 +112,7 @@ pub(crate) async fn set_room_visibility_route(
ClientIp(client): ClientIp,
body: Ruma<set_room_visibility::v3::Request>,
) -> Result<set_room_visibility::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services.rooms.metadata.exists(&body.room_id).await {
// Return 404 if the room doesn't exist
@@ -130,7 +130,7 @@ pub(crate) async fn set_room_visibility_route(
| room::Visibility::Public => {
if services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& !body.identity.is_appservice()
&& body.appservice_info.is_none()
{
info!(
"Non-admin user {sender_user} tried to publish {0} to the room directory \
+2 -2
View File
@@ -15,7 +15,7 @@ pub(crate) async fn get_filter_route(
) -> Result<get_filter::v3::Response> {
services
.users
.get_filter(body.identity.sender_user(), &body.filter_id)
.get_filter(body.sender_user(), &body.filter_id)
.await
.map(get_filter::v3::Response::new)
.map_err(|_| err!(Request(NotFound("Filter not found."))))
@@ -30,7 +30,7 @@ pub(crate) async fn create_filter_route(
) -> Result<create_filter::v3::Response> {
let filter_id = services
.users
.create_filter(body.identity.sender_user(), &body.filter);
.create_filter(body.sender_user(), &body.filter);
Ok(create_filter::v3::Response::new(filter_id))
}
+6 -7
View File
@@ -41,8 +41,7 @@ pub(crate) async fn upload_keys_route(
State(services): State<crate::State>,
body: Ruma<upload_keys::v3::Request>,
) -> Result<upload_keys::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.expect_sender_device()?;
let (sender_user, sender_device) = body.sender();
for (key_id, one_time_key) in &body.one_time_keys {
if one_time_key
@@ -155,7 +154,7 @@ pub(crate) async fn get_keys_route(
State(services): State<crate::State>,
body: Ruma<get_keys::v3::Request>,
) -> Result<get_keys::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
get_keys_helper(
&services,
@@ -192,7 +191,7 @@ pub(crate) async fn upload_signing_keys_route(
State(services): State<crate::State>,
body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if uiaa_needed_to_upload_keys(
services,
@@ -208,7 +207,7 @@ pub(crate) async fn upload_signing_keys_route(
.authenticate_password(
&body.auth,
sender_user,
body.identity.sender_device(),
body.sender_device(),
Some(OAuthTicket::CrossSigningReset),
)
.await?;
@@ -293,7 +292,7 @@ pub(crate) async fn upload_signatures_route(
return Ok(upload_signatures::v3::Response::new());
}
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
for (user_id, keys) in &body.signed_keys {
for (key_id, key) in keys {
@@ -346,7 +345,7 @@ pub(crate) async fn get_key_changes_route(
State(services): State<crate::State>,
body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut device_list_updates = HashSet::new();
+5 -5
View File
@@ -53,7 +53,7 @@ pub(crate) async fn create_content_route(
ClientIp(client): ClientIp,
body: Ruma<create_content::v3::Request>,
) -> Result<create_content::v3::Response> {
let user = body.identity.sender_user();
let user = body.sender_user();
if services.users.is_suspended(user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -92,7 +92,7 @@ pub(crate) async fn get_content_thumbnail_route(
ClientIp(client): ClientIp,
body: Ruma<get_content_thumbnail::v1::Request>,
) -> Result<get_content_thumbnail::v1::Response> {
let user = body.identity.sender_user();
let user = body.sender_user();
let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
let mxc = Mxc {
@@ -142,7 +142,7 @@ pub(crate) async fn get_content_route(
ClientIp(client): ClientIp,
body: Ruma<get_content::v1::Request>,
) -> Result<get_content::v1::Response> {
let user = body.identity.sender_user();
let user = body.sender_user();
let mxc = Mxc {
server_name: &body.server_name,
@@ -189,7 +189,7 @@ pub(crate) async fn get_content_as_filename_route(
ClientIp(client): ClientIp,
body: Ruma<get_content_as_filename::v1::Request>,
) -> Result<get_content_as_filename::v1::Response> {
let user = body.identity.sender_user();
let user = body.sender_user();
let mxc = Mxc {
server_name: &body.server_name,
@@ -240,7 +240,7 @@ pub(crate) async fn get_media_preview_route(
ClientIp(client): ClientIp,
body: Ruma<get_media_preview::v1::Request>,
) -> Result<get_media_preview::v1::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| {
+1 -1
View File
@@ -56,7 +56,7 @@ pub(crate) async fn get_media_preview_legacy_route(
ClientIp(client): ClientIp,
body: Ruma<get_media_preview::v3::Request>,
) -> Result<get_media_preview::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let url = &body.url;
let url = Url::parse(&body.url).map_err(|e| {
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn ban_user_route(
State(services): State<crate::State>,
body: Ruma<ban_user::v3::Request>,
) -> Result<ban_user::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user == body.user_id {
return Err!(Request(Forbidden("You cannot ban yourself.")));
+1 -1
View File
@@ -18,7 +18,7 @@ pub(crate) async fn forget_room_route(
State(services): State<crate::State>,
body: Ruma<forget_room::v3::Request>,
) -> Result<forget_room::v3::Response> {
let user_id = body.identity.sender_user();
let user_id = body.sender_user();
let room_id = &body.room_id;
let joined = services.rooms.state_cache.is_joined(user_id, room_id);
+1 -1
View File
@@ -29,7 +29,7 @@ pub(crate) async fn invite_user_route(
ClientIp(client): ClientIp,
body: Ruma<invite_user::v3::Request>,
) -> Result<invite_user::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
+2 -2
View File
@@ -28,7 +28,7 @@ pub(crate) async fn join_room_by_id_route(
ClientIp(client): ClientIp,
body: Ruma<join_room_by_id::v3::Request>,
) -> Result<join_room_by_id::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -97,7 +97,7 @@ pub(crate) async fn join_room_by_id_or_alias_route(
ClientIp(client): ClientIp,
body: Ruma<join_room_by_id_or_alias::v3::Request>,
) -> Result<join_room_by_id_or_alias::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn kick_user_route(
State(services): State<crate::State>,
body: Ruma<kick_user::v3::Request>,
) -> Result<kick_user::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
+1 -1
View File
@@ -51,7 +51,7 @@ pub(crate) async fn knock_room_route(
ClientIp(client): ClientIp,
body: Ruma<knock_room::v3::Request>,
) -> Result<knock_room::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
+1 -1
View File
@@ -32,7 +32,7 @@ pub(crate) async fn leave_room_route(
State(services): State<crate::State>,
body: Ruma<leave_room::v3::Request>,
) -> Result<leave_room::v3::Response> {
leave_room(&services, body.identity.sender_user(), &body.room_id, body.reason.clone())
leave_room(&services, body.sender_user(), &body.room_id, body.reason.clone())
.boxed()
.await
.map(|()| leave_room::v3::Response::new())
+2 -2
View File
@@ -30,7 +30,7 @@ pub(crate) async fn get_member_events_route(
State(services): State<crate::State>,
body: Ruma<get_member_events::v3::Request>,
) -> Result<get_member_events::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let membership = body.membership.as_ref();
let not_membership = body.not_membership.as_ref();
@@ -72,7 +72,7 @@ pub(crate) async fn joined_members_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.identity.sender_user(), &body.room_id)
.user_can_see_state_events(body.sender_user(), &body.room_id)
.await
{
return Err!(Request(Forbidden("You don't have permission to view this room.")));
+1 -1
View File
@@ -40,7 +40,7 @@ pub(crate) async fn joined_rooms_route(
let joined_rooms = services
.rooms
.state_cache
.rooms_joined(body.identity.sender_user())
.rooms_joined(body.sender_user())
.collect()
.await;
+1 -1
View File
@@ -14,7 +14,7 @@ pub(crate) async fn unban_user_route(
State(services): State<crate::State>,
body: Ruma<unban_user::v3::Request>,
) -> Result<unban_user::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
+15 -4
View File
@@ -23,7 +23,7 @@ use conduwuit_service::{
};
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture, pin_mut};
use ruma::{
RoomId, UserId,
DeviceId, RoomId, UserId,
api::{
Direction,
client::{filter::RoomEventFilter, message::get_message_events},
@@ -37,6 +37,7 @@ use ruma::{
serde::Raw,
};
use ruminuwuity::invite_permission_config::FilterLevel;
use tracing::warn;
use crate::Ruma;
@@ -75,8 +76,8 @@ pub(crate) async fn get_message_events_route(
ClientIp(client_ip): ClientIp,
body: Ruma<get_message_events::v3::Request>,
) -> Result<get_message_events::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.sender_device();
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
let room_id = &body.room_id;
let filter = &body.filter;
@@ -157,7 +158,17 @@ pub(crate) async fn get_message_events_route(
let lazy_loading_context = lazy_loading::Context {
user_id: sender_user,
device_id: sender_device,
device_id: sender_device.or_else(|| {
if let Some(registration) = body.appservice_info.as_ref() {
Some(<&DeviceId>::from(registration.registration.id.as_str()))
} else {
warn!(
"No device_id provided and no appservice registration found, this should be \
unreachable"
);
None
}
}),
room_id,
token: Some(from.into_unsigned()),
options: Some(&filter.lazy_load_options),
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn get_mutual_rooms_route(
State(services): State<crate::State>,
body: Ruma<mutual_rooms::unstable::Request>,
) -> Result<mutual_rooms::unstable::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user == body.user_id {
return Err!(Request(Unknown("You cannot request rooms in common with yourself.")));
+1 -1
View File
@@ -16,7 +16,7 @@ pub(crate) async fn create_openid_token_route(
State(services): State<crate::State>,
body: Ruma<account::request_openid_token::v3::Request>,
) -> Result<account::request_openid_token::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if sender_user != body.user_id {
return Err!(Request(InvalidParam(
+3 -9
View File
@@ -20,19 +20,13 @@ pub(crate) async fn set_presence_route(
return Err!(Request(Forbidden("Presence is disabled on this server")));
}
if body.identity.sender_user() != body.user_id && !body.identity.is_appservice() {
if body.sender_user() != body.user_id && body.appservice_info.is_none() {
return Err!(Request(InvalidParam("Not allowed to set presence of other users")));
}
services
.presence
.set_presence(
body.identity.sender_user(),
&body.presence,
None,
None,
body.status_msg.clone(),
)
.set_presence(body.sender_user(), &body.presence, None, None, body.status_msg.clone())
.await?;
Ok(set_presence::v3::Response::new())
@@ -55,7 +49,7 @@ pub(crate) async fn get_presence_route(
let has_shared_rooms = services
.rooms
.state_cache
.user_sees_user(body.identity.sender_user(), &body.user_id)
.user_sees_user(body.sender_user(), &body.user_id)
.await;
if has_shared_rooms {
+6 -12
View File
@@ -51,12 +51,9 @@ pub(crate) async fn set_profile_field_route(
State(services): State<crate::State>,
body: Ruma<set_profile_field::v3::Request>,
) -> Result<set_profile_field::v3::Response> {
if body.user_id != body.identity.sender_user()
&& !(body.identity.is_appservice()
|| services
.admin
.user_is_admin(body.identity.sender_user())
.await)
if body.user_id != body.sender_user()
&& !(body.appservice_info.is_some()
|| services.admin.user_is_admin(body.sender_user()).await)
{
return Err!(Request(Forbidden("You may not change other users' profile data.")));
}
@@ -75,12 +72,9 @@ pub(crate) async fn delete_profile_field_route(
State(services): State<crate::State>,
body: Ruma<delete_profile_field::v3::Request>,
) -> Result<delete_profile_field::v3::Response> {
if body.user_id != body.identity.sender_user()
&& !(body.identity.is_appservice()
|| services
.admin
.user_is_admin(body.identity.sender_user())
.await)
if body.user_id != body.sender_user()
&& !(body.appservice_info.is_some()
|| services.admin.user_is_admin(body.sender_user()).await)
{
return Err!(Request(Forbidden("You may not change other users' profile data.")));
}
+12 -13
View File
@@ -30,7 +30,7 @@ pub(crate) async fn get_pushrules_all_route(
State(services): State<crate::State>,
body: Ruma<get_pushrules_all::v3::Request>,
) -> Result<get_pushrules_all::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let Some(content_value) = services
.account_data
@@ -101,7 +101,7 @@ pub(crate) async fn get_pushrules_global_route(
State(services): State<crate::State>,
body: Ruma<get_pushrules_global_scope::v3::Request>,
) -> Result<get_pushrules_global_scope::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let Some(content_value) = services
.account_data
@@ -189,7 +189,7 @@ pub(crate) async fn get_pushrule_route(
State(services): State<crate::State>,
body: Ruma<get_pushrule::v3::Request>,
) -> Result<get_pushrule::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
// remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)]
@@ -226,7 +226,7 @@ pub(crate) async fn set_pushrule_route(
State(services): State<crate::State>,
body: Ruma<set_pushrule::v3::Request>,
) -> Result<set_pushrule::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let body = &body.body;
let mut account_data: PushRulesEvent = services
.account_data
@@ -282,7 +282,7 @@ pub(crate) async fn get_pushrule_actions_route(
State(services): State<crate::State>,
body: Ruma<get_pushrule_actions::v3::Request>,
) -> Result<get_pushrule_actions::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
// remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)]
@@ -316,7 +316,7 @@ pub(crate) async fn set_pushrule_actions_route(
State(services): State<crate::State>,
body: Ruma<set_pushrule_actions::v3::Request>,
) -> Result<set_pushrule_actions::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut account_data: PushRulesEvent = services
.account_data
@@ -349,7 +349,7 @@ pub(crate) async fn get_pushrule_enabled_route(
State(services): State<crate::State>,
body: Ruma<get_pushrule_enabled::v3::Request>,
) -> Result<get_pushrule_enabled::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
// remove old deprecated mentions push rules as per MSC4210
#[allow(deprecated)]
@@ -383,7 +383,7 @@ pub(crate) async fn set_pushrule_enabled_route(
State(services): State<crate::State>,
body: Ruma<set_pushrule_enabled::v3::Request>,
) -> Result<set_pushrule_enabled::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut account_data: PushRulesEvent = services
.account_data
@@ -416,7 +416,7 @@ pub(crate) async fn delete_pushrule_route(
State(services): State<crate::State>,
body: Ruma<delete_pushrule::v3::Request>,
) -> Result<delete_pushrule::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut account_data: PushRulesEvent = services
.account_data
@@ -458,7 +458,7 @@ pub(crate) async fn get_pushers_route(
State(services): State<crate::State>,
body: Ruma<get_pushers::v3::Request>,
) -> Result<get_pushers::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
Ok(get_pushers::v3::Response::new(services.pusher.get_pushers(sender_user).await))
}
@@ -472,12 +472,11 @@ pub(crate) async fn set_pushers_route(
State(services): State<crate::State>,
body: Ruma<set_pusher::v3::Request>,
) -> Result<set_pusher::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.expect_sender_device()?;
let sender_user = body.sender_user();
services
.pusher
.set_pusher(sender_user, sender_device, &body.action)
.set_pusher(sender_user, body.sender_device(), &body.action)
.await?;
Ok(set_pusher::v3::Response::new())
+3 -3
View File
@@ -26,7 +26,7 @@ pub(crate) async fn set_read_marker_route(
State(services): State<crate::State>,
body: Ruma<set_read_marker::v3::Request>,
) -> Result<set_read_marker::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if let Some(event) = &body.fully_read {
let fully_read_event = FullyReadEvent::new(FullyReadEventContent::new(event.to_owned()));
@@ -118,10 +118,10 @@ pub(crate) async fn create_receipt_route(
ClientIp(client_ip): ClientIp,
body: Ruma<create_receipt::v3::Request>,
) -> Result<create_receipt::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
services
.users
.update_device_last_seen(sender_user, body.identity.sender_device(), client_ip)
.update_device_last_seen(sender_user, body.sender_device.as_deref(), client_ip)
.await;
if matches!(
+2 -2
View File
@@ -17,10 +17,10 @@ pub(crate) async fn redact_event_route(
ClientIp(client_ip): ClientIp,
body: Ruma<redact_event::v3::Request>,
) -> Result<redact_event::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
services
.users
.update_device_last_seen(sender_user, body.identity.sender_device(), client_ip)
.update_device_last_seen(sender_user, body.sender_device.as_deref(), client_ip)
.await;
let body = &body.body;
if services.users.is_suspended(sender_user).await? {
+3 -3
View File
@@ -28,7 +28,7 @@ pub(crate) async fn get_relating_events_with_rel_type_and_event_type_route(
) -> Result<get_relating_events_with_rel_type_and_event_type::v1::Response> {
paginate_relations_with_filter(
&services,
body.identity.sender_user(),
body.sender_user(),
&body.room_id,
&body.event_id,
body.event_type.clone().into(),
@@ -56,7 +56,7 @@ pub(crate) async fn get_relating_events_with_rel_type_route(
) -> Result<get_relating_events_with_rel_type::v1::Response> {
paginate_relations_with_filter(
&services,
body.identity.sender_user(),
body.sender_user(),
&body.room_id,
&body.event_id,
None,
@@ -84,7 +84,7 @@ pub(crate) async fn get_relating_events_route(
) -> Result<get_relating_events::v1::Response> {
paginate_relations_with_filter(
&services,
body.identity.sender_user(),
body.sender_user(),
&body.room_id,
&body.event_id,
None,
+4 -4
View File
@@ -36,7 +36,7 @@ pub(crate) async fn report_room_route(
ClientIp(client): ClientIp,
body: Ruma<report_room::v3::Request>,
) -> Result<report_room::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -92,7 +92,7 @@ pub(crate) async fn report_event_route(
body: Ruma<report_content::v3::Request>,
) -> Result<report_content::v3::Response> {
// user authentication
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
@@ -135,8 +135,8 @@ pub(crate) async fn report_user_route(
ClientIp(client): ClientIp,
body: Ruma<report_user::v3::Request>,
) -> Result<report_user::v3::Response> {
let sender_user = body.identity.sender_user();
// user authentication
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
+1 -1
View File
@@ -15,7 +15,7 @@ pub(crate) async fn get_room_aliases_route(
State(services): State<crate::State>,
body: Ruma<aliases::v3::Request>,
) -> Result<aliases::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services
.rooms
+4 -4
View File
@@ -61,10 +61,10 @@ pub(crate) async fn create_room_route(
) -> Result<create_room::v3::Response> {
use create_room::v3::RoomPreset;
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services.globals.allow_room_creation()
&& !body.identity.is_appservice()
&& body.appservice_info.is_none()
&& !services.users.is_admin(sender_user).await
{
return Err!(Request(Forbidden("Room creation has been disabled.",)));
@@ -130,7 +130,7 @@ pub(crate) async fn create_room_route(
if body.visibility == room::Visibility::Public
&& services.server.config.lockdown_public_room_directory
&& !services.users.is_admin(sender_user).await
&& !body.identity.is_appservice()
&& body.appservice_info.is_none()
{
warn!(
"Non-admin user {sender_user} tried to publish {room_id:?} to the room directory \
@@ -186,7 +186,7 @@ pub(crate) async fn create_room_route(
let alias: Option<OwnedRoomAliasId> = match body.room_alias_name.as_ref() {
| Some(alias) =>
Some(room_alias_check(&services, alias, body.identity.appservice_info()).await?),
Some(room_alias_check(&services, alias, body.appservice_info.as_ref()).await?),
| _ => None,
};
+4 -4
View File
@@ -24,25 +24,25 @@ pub(crate) async fn get_room_event_route(
let visible = services
.rooms
.state_accessor
.user_can_see_event(body.identity.sender_user(), room_id, event_id)
.user_can_see_event(body.sender_user(), room_id, event_id)
.map(Ok);
let (mut event, visible) = try_join(event, visible).await?;
if !visible || is_ignored_pdu(services, &event, body.identity.sender_user()).await? {
if !visible || is_ignored_pdu(services, &event, body.sender_user()).await? {
return Err!(Request(Forbidden("You don't have permission to view this event.")));
}
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(body.identity.sender_user(), &mut event)
.add_bundled_aggregations_to_pdu(body.sender_user(), &mut event)
.await
{
debug_warn!("Failed to add bundled aggregations to event: {e}");
}
event.set_unsigned(Some(body.identity.sender_user()));
event.set_unsigned(body.sender_user.as_deref());
Ok(get_room_event::v3::Response::new(event.into_format()))
}
+12 -10
View File
@@ -22,7 +22,7 @@ pub(crate) async fn room_initial_sync_route(
if !services
.rooms
.state_accessor
.user_can_see_state_events(body.identity.sender_user(), room_id)
.user_can_see_state_events(body.sender_user(), room_id)
.await
{
return Err!(Request(Forbidden("No room preview available.")));
@@ -31,7 +31,7 @@ pub(crate) async fn room_initial_sync_route(
let membership = services
.rooms
.state_cache
.user_membership(body.identity.sender_user(), room_id)
.user_membership(body.sender_user(), room_id)
.map(Ok);
let visibility = services.rooms.directory.visibility(room_id).map(Ok);
@@ -52,14 +52,16 @@ pub(crate) async fn room_initial_sync_route(
.pdus_rev(room_id, None)
.try_take(limit)
.and_then(async |mut pdu| {
pdu.1.set_unsigned(Some(body.identity.sender_user()));
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(body.identity.sender_user(), &mut pdu.1)
.await
{
debug_warn!("Failed to add bundled aggregations: {e}");
pdu.1.set_unsigned(body.sender_user.as_deref());
if let Some(sender_user) = body.sender_user.as_deref() {
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(sender_user, &mut pdu.1)
.await
{
debug_warn!("Failed to add bundled aggregations: {e}");
}
}
Ok(pdu)
})
+2 -6
View File
@@ -4,7 +4,7 @@ use conduwuit::{Err, Result};
use ruma::api::client::room::get_summary;
use service::rooms::summary::Accessibility;
use crate::{Ruma, router::ClientIdentity};
use crate::Ruma;
/// # `GET /_matrix/client/v1/room_summary/{roomIdOrAlias}`
///
@@ -28,11 +28,7 @@ pub(crate) async fn get_room_summary(
let summary = services
.rooms
.summary
.get_room_summary_for_user(
body.identity.as_ref().map(ClientIdentity::sender_user),
&room_id,
&servers,
)
.get_room_summary_for_user(body.sender_user.as_deref(), &room_id, &servers)
.await?;
match summary {
+1 -1
View File
@@ -277,7 +277,7 @@ pub(crate) async fn upgrade_room_route(
State(services): State<crate::State>,
body: Ruma<upgrade_room::v3::Request>,
) -> Result<upgrade_room::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let (supported, forbid_unstable, is_unstable) = (
services.server.supported_room_version(&body.new_version),
+1 -1
View File
@@ -43,7 +43,7 @@ pub(crate) async fn search_events_route(
State(services): State<crate::State>,
body: Ruma<Request>,
) -> Result<Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let next_batch = body.next_batch.as_deref();
let mut result_categories = ResultCategories::new();
+5 -9
View File
@@ -22,16 +22,16 @@ pub(crate) async fn send_message_event_route(
ClientIp(client_ip): ClientIp,
body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.sender_device();
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
let appservice_info = body.appservice_info.as_ref();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services
.users
.update_device_last_seen(sender_user, sender_device, client_ip)
.update_device_last_seen(sender_user, body.sender_device.as_deref(), client_ip)
.await;
// Forbid m.room.encrypted if encryption is disabled
@@ -83,11 +83,7 @@ pub(crate) async fn send_message_event_route(
event_type: body.event_type.clone().into(),
content,
unsigned: Some(unsigned),
timestamp: if body.identity.is_appservice() {
body.timestamp
} else {
None
},
timestamp: appservice_info.and(body.timestamp),
..Default::default()
},
sender_user,
+5 -7
View File
@@ -157,7 +157,7 @@ pub(crate) async fn login_route(
}) => {
debug!("Got appservice login type");
let Some(ref info) = body.identity else {
let Some(ref info) = body.appservice_info else {
return Err!(Request(MissingToken("Missing appservice token.")));
};
@@ -265,12 +265,12 @@ pub(crate) async fn login_token_route(
return Err!(Request(Forbidden("Login via an existing session is not enabled")));
}
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
// Prompt the user to confirm with their password using UIAA
let _ = services
.uiaa
.authenticate_password(&body.auth, sender_user, body.identity.sender_device(), None)
.authenticate_password(&body.auth, sender_user, body.sender_device(), None)
.await?;
let login_token = utils::random_string(TOKEN_LENGTH);
@@ -297,9 +297,7 @@ pub(crate) async fn logout_route(
ClientIp(client): ClientIp,
body: Ruma<logout::v3::Request>,
) -> Result<logout::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.expect_sender_device()?;
let (sender_user, sender_device) = body.sender();
services
.users
.remove_device(sender_user, sender_device)
@@ -345,7 +343,7 @@ pub(crate) async fn logout_all_route(
ClientIp(client): ClientIp,
body: Ruma<logout_all::v3::Request>,
) -> Result<logout_all::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
services
.users
.all_device_ids(sender_user)
+1 -1
View File
@@ -27,7 +27,7 @@ pub(crate) async fn get_hierarchy_route(
.rooms
.summary
.get_room_hierarchy_for_user(
body.identity.sender_user(),
body.sender_user(),
body.room_id.clone(),
max_depth,
body.suggested_only,
+5 -5
View File
@@ -38,10 +38,10 @@ pub(crate) async fn send_state_event_for_key_route(
ClientIp(ip): ClientIp,
body: Ruma<send_state_event::v3::Request>,
) -> Result<send_state_event::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
services
.users
.update_device_last_seen(sender_user, body.identity.sender_device(), ip)
.update_device_last_seen(sender_user, body.sender_device.as_deref(), ip)
.await;
if services.users.is_suspended(sender_user).await? {
@@ -55,7 +55,7 @@ pub(crate) async fn send_state_event_for_key_route(
&body.event_type,
&body.body.body,
&body.state_key,
if body.identity.is_appservice() {
if body.appservice_info.is_some() {
body.timestamp
} else {
None
@@ -91,7 +91,7 @@ pub(crate) async fn get_state_events_route(
State(services): State<crate::State>,
body: Ruma<get_state_events::v3::Request>,
) -> Result<get_state_events::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services
.rooms
@@ -125,7 +125,7 @@ pub(crate) async fn get_state_event_for_key_route(
State(services): State<crate::State>,
body: Ruma<get_state_event_for_key::v3::Request>,
) -> Result<get_state_event_for_key::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
if !services
.rooms
+7
View File
@@ -48,6 +48,13 @@ async fn load_timeline(
ending_count: Option<PduCount>,
limit: usize,
) -> Result<TimelinePdus> {
if let (Some(starting_count), Some(ending_count)) = (starting_count, ending_count) {
debug_assert!(
starting_count <= ending_count,
"starting count {starting_count} > ending count {ending_count}"
);
}
let mut pdu_stream = match starting_count {
| Some(starting_count) => {
let last_timeline_count = services
+76 -73
View File
@@ -38,6 +38,7 @@ use ruma::{
uint,
};
use service::{account_data::AnyRawAccountDataEvent, rooms::short::ShortStateHash};
use tokio::pin;
use super::{load_timeline, share_encrypted_room};
use crate::client::{
@@ -96,12 +97,19 @@ pub(super) async fn load_joined_room(
);
}
let state_events =
StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect());
let joined_room = assign!(JoinedRoom::new(), {
account_data,
summary: summary.unwrap_or_default(),
unread_notifications: notification_counts.unwrap_or_default(),
timeline,
state: RoomState::Before(StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect())),
state: if sync_context.use_state_after {
RoomState::After(state_events)
} else {
RoomState::Before(state_events)
},
ephemeral,
unread_thread_notifications: BTreeMap::new(),
});
@@ -344,7 +352,7 @@ struct ShortStateHashes {
#[tracing::instrument(level = "debug", skip_all)]
async fn fetch_shortstatehashes(
services: &Services,
SyncContext { last_sync_end_count, current_count, .. }: SyncContext<'_>,
SyncContext { last_sync_end_count, .. }: SyncContext<'_>,
room_id: &RoomId,
) -> Result<ShortStateHashes> {
// the room state currently.
@@ -354,46 +362,41 @@ async fn fetch_shortstatehashes(
.rooms
.state
.get_room_shortstatehash(room_id)
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))));
.map_err(|_| err!(Database(error!("Room {room_id} has no state"))))
.await?;
// the room state as of the end of the last sync.
// this will be None if we are doing an initial sync or if we just joined this
// room.
// The room state as of the end of the last sync.
// This will be None if we are doing an initial sync.
let last_sync_end_shortstatehash =
OptionFuture::from(last_sync_end_count.map(|last_sync_end_count| {
// look up the shortstatehash saved by the last sync's call to
// `associate_token_shortstatehash`
services
.rooms
.user
.get_token_shortstatehash(room_id, last_sync_end_count)
.inspect_err(move |_| {
debug_warn!(
token = last_sync_end_count,
"Room has no shortstatehash for this token"
);
})
.ok()
OptionFuture::from(last_sync_end_count.map(async |last_sync_end_count| {
pin! {
let pdus = services
.rooms
.timeline
.pdus(room_id, Some(PduCount::Normal(last_sync_end_count)))
.ignore_err();
}
match pdus.next().await {
| Some((_, pdu_after_last_sync_end)) => {
trace!(?pdu_after_last_sync_end.event_id, "pdu at last sync end");
services
.rooms
.state_accessor
.pdu_shortstatehash(&pdu_after_last_sync_end.event_id)
.await
.map_err(|err| err!("Last sync end PDU has no shortstatehash: {err}"))
},
| None => {
// No events have been sent since the last sync, or we just joined this room,
// so the state then is the same as the state now
Ok(current_shortstatehash)
},
}
}))
.map(Option::flatten)
.map(Ok);
let (current_shortstatehash, last_sync_end_shortstatehash) =
try_join(current_shortstatehash, last_sync_end_shortstatehash).await?;
/*
associate the `current_count` with the `current_shortstatehash`, so we can
use it on the next sync as the `last_sync_end_shortstatehash`.
TODO: the table written to by this call grows extremely fast, gaining one new entry for each
joined room on _every single sync request_. we need to find a better way to remember the shortstatehash
between syncs.
*/
services
.rooms
.user
.associate_token_shortstatehash(room_id, current_count, current_shortstatehash)
.await;
.await
.transpose()?;
Ok(ShortStateHashes {
current_shortstatehash,
@@ -452,6 +455,7 @@ async fn build_state_events(
syncing_user,
last_sync_end_count,
full_state,
use_state_after,
..
} = sync_context;
@@ -460,32 +464,28 @@ async fn build_state_events(
last_sync_end_shortstatehash,
} = shortstatehashes;
// the spec states that the `state` property only includes state events up to
// the beginning of the timeline, so we determine the state of the syncing room
// as of the first timeline event. NOTE: this explanation is not entirely
// accurate; see the implementation of `build_state_incremental`.
let timeline_start_shortstatehash = async {
if let Some((_, pdu)) = timeline.pdus.front() {
if let Ok(shortstatehash) = services
let timeline_start_shortstatehash = if let Some((count, pdu)) = timeline.pdus.front() {
if matches!(count, PduCount::Backfilled(_)) {
// We don't have shortstatehashes for backfilled PDUs, the best we can
// do is to use the current state
current_shortstatehash
} else {
services
.rooms
.state_accessor
.pdu_shortstatehash(&pdu.event_id)
.await
{
return shortstatehash;
}
.map_err(|err| err!("Timeline start has no shortstatehash: {err}"))?
}
current_shortstatehash
} else {
// if the timeline is empty there can't possibly be any changes to the state
return Ok(vec![]);
};
// the user IDs of members whose membership needs to be sent to the client, if
// lazy-loading is enabled.
let lazily_loaded_members =
prepare_lazily_loaded_members(services, sync_context, room_id, timeline.senders());
let (timeline_start_shortstatehash, lazily_loaded_members) =
join(timeline_start_shortstatehash, lazily_loaded_members).await;
prepare_lazily_loaded_members(services, sync_context, room_id, timeline.senders()).await;
// compute the state delta between the previous sync and this sync.
match (last_sync_end_count, last_sync_end_shortstatehash) {
@@ -494,16 +494,15 @@ async fn build_state_events(
is Some (meaning the syncing user didn't just join this room for the first time ever), and `full_state` is false,
then use `build_state_incremental`.
*/
| (Some(last_sync_end_count), Some(last_sync_end_shortstatehash)) if !full_state =>
| (Some(_), Some(last_sync_end_shortstatehash)) if !full_state =>
build_state_incremental(
services,
syncing_user,
room_id,
PduCount::Normal(last_sync_end_count),
last_sync_end_shortstatehash,
timeline_start_shortstatehash,
current_shortstatehash,
timeline,
use_state_after,
lazily_loaded_members.as_ref(),
)
.boxed()
@@ -518,6 +517,8 @@ async fn build_state_events(
services,
syncing_user,
timeline_start_shortstatehash,
current_shortstatehash,
use_state_after,
lazily_loaded_members.as_ref(),
)
.boxed()
@@ -598,23 +599,25 @@ async fn check_joined_since_last_sync(
ShortStateHashes { last_sync_end_shortstatehash, .. }: ShortStateHashes,
SyncContext { syncing_user, .. }: SyncContext<'_>,
) -> Result<bool> {
// fetch the syncing user's membership event during the last sync.
// this will be None if `previous_sync_end_shortstatehash` is None.
let membership_during_previous_sync = match last_sync_end_shortstatehash {
| Some(last_sync_end_shortstatehash) => services
.rooms
.state_accessor
.state_get_content(
last_sync_end_shortstatehash,
&StateEventType::RoomMember,
syncing_user.as_str(),
)
.await
.inspect_err(|_| debug_warn!("User has no previous membership"))
.ok(),
| None => None,
let Some(last_sync_end_shortstatehash) = last_sync_end_shortstatehash else {
// For initial syncs always return false, since there's no "last sync" for the
// user to have joined since.
return Ok(false);
};
// Fetch the syncing user's membership event during the last sync.
let membership_during_previous_sync = services
.rooms
.state_accessor
.state_get_content(
last_sync_end_shortstatehash,
&StateEventType::RoomMember,
syncing_user.as_str(),
)
.await
.inspect_err(|_| debug_warn!("User has no previous membership"))
.ok();
// TODO: If the requesting user got state-reset out of the room, this
// will be `true` when it shouldn't be. this function should never be called
// in that situation, but it may be if the membership cache didn't get updated.
+10 -1
View File
@@ -181,6 +181,9 @@ pub(super) async fn load_left_room(
.collect::<Vec<_>>()
.await;
let state_events =
StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect());
Ok(Some(assign!(LeftRoom::new(), {
account_data: RoomAccountData::new(),
timeline: assign!(Timeline::new(), {
@@ -188,7 +191,11 @@ pub(super) async fn load_left_room(
prev_batch: Some(current_count.to_string()),
events: raw_timeline_pdus,
}),
state: State::Before(StateEvents::with_events(state_events.into_iter().map(Event::into_format).collect())),
state: if sync_context.use_state_after {
State::After(state_events)
} else {
State::Before(state_events)
},
})))
}
@@ -264,6 +271,8 @@ async fn build_left_state_and_timeline(
services,
syncing_user,
timeline_start_shortstatehash,
leave_shortstatehash,
sync_context.use_state_after,
lazily_loaded_members.as_ref(),
)
.await?;
+9 -8
View File
@@ -11,12 +11,11 @@ use std::{
use axum::extract::State;
use axum_client_ip::ClientIp;
use conduwuit::{
Err, Result, at, extract_variant,
Err, Result, at, error, extract_variant,
utils::{
ReadyExt, TryFutureExtExt,
stream::{BroadbandExt, Tools, WidebandExt},
},
warn,
};
use conduwuit_service::Services;
use futures::{FutureExt, StreamExt, TryFutureExt, future::OptionFuture};
@@ -110,6 +109,9 @@ struct SyncContext<'a> {
/// The sync filter, which the client uses to specify what data should be
/// included in the sync response.
filter: &'a FilterDefinition,
/// Whether the state at the end of the timeline should be used when
/// calculating state diffs for sync.
use_state_after: bool,
}
impl<'a> SyncContext<'a> {
@@ -181,8 +183,7 @@ pub(crate) async fn sync_events_route(
ClientIp(client_ip): ClientIp,
body: Ruma<sync_events::v3::Request>,
) -> Result<sync_events::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.expect_sender_device()?;
let (sender_user, sender_device) = body.sender();
// Presence update
if services.config.allow_local_presence {
@@ -226,8 +227,7 @@ pub(crate) async fn build_sync_events(
services: &Services,
body: &Ruma<sync_events::v3::Request>,
) -> Result<sync_events::v3::Response> {
let syncing_user = body.identity.sender_user();
let syncing_device = body.identity.sender_device().expect("should have a device");
let (syncing_user, syncing_device) = body.sender();
let current_count = services.globals.current_count()?;
@@ -263,6 +263,7 @@ pub(crate) async fn build_sync_events(
current_count,
full_state,
filter: &filter,
use_state_after: body.use_state_after,
};
let joined_rooms = services
@@ -275,7 +276,7 @@ pub(crate) async fn build_sync_events(
match joined_room {
| Ok((room, updates)) => Some((room_id, room, updates)),
| Err(err) => {
warn!(?err, %room_id, "error loading joined room");
error!(?err, %room_id, "error loading joined room");
None
},
}
@@ -304,7 +305,7 @@ pub(crate) async fn build_sync_events(
| Ok(Some(left_room)) => Some((room_id, left_room)),
| Ok(None) => None,
| Err(err) => {
warn!(?err, %room_id, "error loading joined room");
error!(?err, %room_id, "error loading joined room");
None
},
}
+60 -143
View File
@@ -1,11 +1,8 @@
use std::{collections::BTreeSet, ops::ControlFlow};
use std::collections::HashSet;
use conduwuit::{
Result, at, is_equal_to,
matrix::{
Event,
pdu::{PduCount, PduEvent},
},
Result, at,
matrix::{Event, pdu::PduEvent},
utils::{
BoolExt, IterStream, ReadyExt, TryFutureExtExt,
stream::{BroadbandExt, TryIgnore},
@@ -16,9 +13,7 @@ use conduwuit_service::{
rooms::{lazy_loading::MemberSet, short::ShortStateHash},
};
use futures::{FutureExt, StreamExt};
use itertools::Itertools;
use ruma::{OwnedEventId, RoomId, UserId, events::StateEventType};
use service::rooms::short::ShortEventId;
use ruma::{OwnedEventId, UserId, events::StateEventType};
use tracing::trace;
use crate::client::TimelinePdus;
@@ -39,13 +34,19 @@ pub(super) async fn build_state_initial(
services: &Services,
sender_user: &UserId,
timeline_start_shortstatehash: ShortStateHash,
timeline_end_shortstatehash: ShortStateHash,
use_state_after: bool,
lazily_loaded_members: Option<&MemberSet>,
) -> Result<Vec<PduEvent>> {
// load the keys and event IDs of the state events at the start of the timeline
let (shortstatekeys, event_ids): (Vec<_>, Vec<_>) = services
.rooms
.state_accessor
.state_full_ids(timeline_start_shortstatehash)
.state_full_ids(if use_state_after {
timeline_end_shortstatehash
} else {
timeline_start_shortstatehash
})
.unzip()
.await;
@@ -92,82 +93,34 @@ pub(super) async fn build_state_initial(
pub(super) async fn build_state_incremental<'a>(
services: &Services,
sender_user: &'a UserId,
room_id: &RoomId,
last_sync_end_count: PduCount,
last_sync_end_shortstatehash: ShortStateHash,
timeline_start_shortstatehash: ShortStateHash,
timeline_end_shortstatehash: ShortStateHash,
timeline: &TimelinePdus,
use_state_after: bool,
lazily_loaded_members: Option<&'a MemberSet>,
) -> Result<Vec<PduEvent>> {
/*
NB: a limited sync is one where `timeline.limited == true`. Synapse calls this a "gappy" sync internally.
let mut state_event_ids: HashSet<OwnedEventId> = HashSet::new();
The algorithm implemented in this function is, currently, quite different from the algorithm vaguely described
by the Matrix specification. This is because the specification's description of the `state` property does not accurately
reflect how Synapse behaves, and therefore how client SDKs behave. Notable differences include:
1. We do not compute the delta using the naive approach of "every state event from the end of the last sync
up to the start of this sync's timeline". see below for details.
2. If lazy-loading is enabled, we include lazily-loaded membership events. The specific users to include are determined
elsewhere and supplied to this function in the `lazily_loaded_members` parameter.
*/
trace!(
%use_state_after,
%last_sync_end_shortstatehash,
%timeline_start_shortstatehash,
%timeline_end_shortstatehash,
"computing state for incremental sync"
);
/*
the `state` property of an incremental sync which isn't limited are _usually_ empty.
(note: the specification says that the `state` property is _always_ empty for limited syncs, which is incorrect.)
however, if an event in the timeline (`timeline.pdus`) merges a split in the room's DAG (i.e. has multiple `prev_events`),
the state at the _end_ of the timeline may include state events which were merged in and don't exist in the state
at the _start_ of the timeline. because this is uncommon, we check here to see if any events in the timeline
merged a split in the DAG.
// Fetch lazy-loaded membership events if lazy-loading is enabled
if let Some(lazily_loaded_members) = lazily_loaded_members
&& !lazily_loaded_members.is_empty()
{
trace!("including lazy membership events for members: {:?}", lazily_loaded_members);
see: https://github.com/element-hq/synapse/issues/16941
*/
let timeline_is_linear = timeline.pdus.is_empty() || {
let last_pdu_of_last_sync = services
services
.rooms
.timeline
.pdus_rev(room_id, Some(last_sync_end_count.saturating_add(1)))
.boxed()
.next()
.await
.transpose()
.expect("last sync should have had some PDUs")
.map(at!(1));
// make sure the prev_events of each pdu in the timeline refer only to the
// previous pdu
timeline
.pdus
.iter()
.try_fold(last_pdu_of_last_sync.map(|pdu| pdu.event_id), |prev_event_id, (_, pdu)| {
if let Ok(pdu_prev_event_id) = pdu.prev_events.iter().exactly_one() {
if prev_event_id
.as_ref()
.is_none_or(is_equal_to!(pdu_prev_event_id))
{
return ControlFlow::Continue(Some(pdu_prev_event_id.to_owned()));
}
}
trace!(
"pdu {:?} has split prev_events (expected {:?}): {:?}",
pdu.event_id, prev_event_id, pdu.prev_events
);
ControlFlow::Break(())
})
.is_continue()
};
if timeline_is_linear && !timeline.limited {
// if there are no splits in the DAG and the timeline isn't limited, then
// `state` will always be empty unless lazy loading is enabled.
if let Some(lazily_loaded_members) = lazily_loaded_members {
if !timeline.pdus.is_empty() {
// lazy loading is enabled, so we return the membership events which were
// requested by the caller.
let lazy_membership_events: Vec<_> = lazily_loaded_members
.short
.multi_get_eventid_from_short::<'_, OwnedEventId, _>(
lazily_loaded_members
.iter()
.stream()
.broad_filter_map(|user_id| async move {
@@ -178,71 +131,24 @@ pub(super) async fn build_state_incremental<'a>(
services
.rooms
.state_accessor
.state_get(
.state_get_shortid(
timeline_start_shortstatehash,
&StateEventType::RoomMember,
user_id.as_str(),
)
.ok()
.await
})
.collect()
.await;
if !lazy_membership_events.is_empty() {
trace!(
"syncing lazy membership events for members: {:?}",
lazy_membership_events
.iter()
.map(|pdu| pdu.state_key().unwrap())
.collect::<Vec<_>>()
);
}
return Ok(lazy_membership_events);
}
}
// lazy loading is disabled, `state` is empty.
return Ok(vec![]);
}),
)
.ignore_err()
.ready_for_each(|event_id| {
state_event_ids.insert(event_id);
})
.await;
}
/*
at this point, either the timeline is `limited` or the DAG has a split in it. this necessitates
computing the incremental state (which may be empty).
NOTE: this code path does not use the `lazy_membership_events` parameter. any changes to membership will be included
in the incremental state. therefore, the incremental state may include "redundant" membership events,
which we do not filter out because A. the spec forbids lazy-load filtering if the timeline is `limited`,
and B. DAG splits which require sending extra membership state events are (probably) uncommon enough that
the performance penalty is acceptable.
*/
trace!(%timeline_is_linear, %timeline.limited, "computing state for incremental sync");
// fetch the shorteventids of state events in the timeline
let state_events_in_timeline: BTreeSet<ShortEventId> = services
.rooms
.short
.multi_get_or_create_shorteventid(timeline.pdus.iter().filter_map(|(_, pdu)| {
if pdu.state_key().is_some() {
Some(pdu.event_id.as_ref())
} else {
None
}
}))
.collect()
.await;
trace!("{} state events in timeline", state_events_in_timeline.len());
/*
fetch the state events which were added since the last sync.
specifically we fetch the difference between the state at the last sync and the state at the _end_
of the timeline, and then we filter out state events in the timeline itself using the shorteventids we fetched.
this is necessary to account for splits in the DAG, as explained above.
*/
let state_diff = services
// Fetch the state events added since the last sync.
services
.rooms
.short
.multi_get_eventid_from_short::<'_, OwnedEventId, _>(
@@ -252,18 +158,29 @@ pub(super) async fn build_state_incremental<'a>(
.state_added((last_sync_end_shortstatehash, timeline_end_shortstatehash))
.await?
.stream()
.ready_filter_map(|(_, shorteventid)| {
if state_events_in_timeline.contains(&shorteventid) {
None
} else {
Some(shorteventid)
}
}),
.map(at!(1)),
)
.ignore_err();
.ignore_err()
.ready_for_each(|event_id| {
state_event_ids.insert(event_id);
})
.await;
// finally, fetch the PDU contents and collect them into a vec
let state_diff_pdus = state_diff
if !use_state_after {
// If state_after isn't enabled, filter out state events which also exist
// in the timeline. If splits exist in the DAG, this may not be exactly the same
// thing as the state diff ending at the start of the timeline, but Synapse
// also does this and it's technically more useful behavior anyway.
// See: https://github.com/element-hq/synapse/issues/16941
for (_, pdu) in &timeline.pdus {
state_event_ids.remove(pdu.event_id());
}
}
// Finally, fetch the PDU contents and collect them into a vec
let state_diff_pdus = state_event_ids
.stream()
.broad_filter_map(|event_id| async move {
services
.rooms
+26 -10
View File
@@ -15,7 +15,7 @@ use conduwuit::{
BoolExt, FutureBoolExt, IterStream, ReadyExt, TryFutureExtExt,
future::ReadyEqExt,
math::{ruma_from_usize, usize_from_ruma},
stream::WidebandExt,
stream::{TryIgnore, WidebandExt},
},
warn,
};
@@ -41,6 +41,7 @@ use ruma::{
uint,
};
use service::account_data::AnyRawAccountDataEvent;
use tokio::pin;
use super::share_encrypted_room;
use crate::{
@@ -69,8 +70,8 @@ pub(crate) async fn sync_events_v5_route(
ClientIp(client_ip): ClientIp,
body: Ruma<sync_events::v5::Request>,
) -> Result<sync_events::v5::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.expect_sender_device()?;
let ref sender_user = body.sender_user().to_owned();
let ref sender_device = body.sender_device().to_owned();
services
.users
@@ -92,7 +93,7 @@ pub(crate) async fn sync_events_v5_route(
.and_then(|string| string.parse().ok())
.unwrap_or(0);
let snake_key = into_snake_key(sender_user, sender_device.as_str(), conn_id);
let snake_key = into_snake_key(sender_user.as_ref(), sender_device.as_str(), conn_id);
if globalsince != 0 && !services.sync.snake_connection_cached(&snake_key) {
return Err!(Request(UnknownPos(
@@ -857,12 +858,27 @@ where
continue;
};
let since_shortstatehash = services
.rooms
.user
.get_token_shortstatehash(room_id, globalsince)
.await
.ok();
let since_shortstatehash = async {
pin! {
let pdus_rev = services
.rooms
.timeline
.pdus_rev(room_id, Some(PduCount::Normal(globalsince.saturating_sub(1))))
.ignore_err();
}
let (_, pdu_at_last_sync_end) = pdus_rev.next().await?;
Some(
services
.rooms
.state_accessor
.pdu_shortstatehash(&pdu_at_last_sync_end.event_id)
.await
.expect("pdu should have a shortstatehash"),
)
}
.await;
let encrypted_room = services
.rooms
+3 -3
View File
@@ -21,7 +21,7 @@ pub(crate) async fn update_tag_route(
State(services): State<crate::State>,
body: Ruma<create_tag::v3::Request>,
) -> Result<create_tag::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut tags_event = services
.account_data
@@ -56,7 +56,7 @@ pub(crate) async fn delete_tag_route(
State(services): State<crate::State>,
body: Ruma<delete_tag::v3::Request>,
) -> Result<delete_tag::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let mut tags_event = services
.account_data
@@ -88,7 +88,7 @@ pub(crate) async fn get_tags_route(
State(services): State<crate::State>,
body: Ruma<get_tags::v3::Request>,
) -> Result<get_tags::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let tags_event = services
.account_data
+3 -3
View File
@@ -34,14 +34,14 @@ pub(crate) async fn get_threads_route(
let threads: Vec<(PduCount, PduEvent)> = services
.rooms
.threads
.threads_until(body.identity.sender_user(), &body.room_id, from, &body.include)
.threads_until(body.sender_user(), &body.room_id, from, &body.include)
.await?
.take(limit)
.filter_map(|(count, pdu)| async move {
services
.rooms
.state_accessor
.user_can_see_event(body.identity.sender_user(), &body.room_id, &pdu.event_id)
.user_can_see_event(body.sender_user(), &body.room_id, &pdu.event_id)
.await
.then_some((count, pdu))
})
@@ -49,7 +49,7 @@ pub(crate) async fn get_threads_route(
if let Err(e) = services
.rooms
.pdu_metadata
.add_bundled_aggregations_to_pdu(body.identity.sender_user(), &mut pdu)
.add_bundled_aggregations_to_pdu(body.sender_user(), &mut pdu)
.await
{
debug_warn!("Failed to add bundled aggregations to thread: {e}");
+2 -2
View File
@@ -22,8 +22,8 @@ pub(crate) async fn send_event_to_device_route(
State(services): State<crate::State>,
body: Ruma<send_event_to_device::v3::Request>,
) -> Result<send_event_to_device::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_device = body.identity.sender_device();
let sender_user = body.sender_user();
let sender_device = body.sender_device.as_deref();
// Check if this is a new transaction id
if services
+3 -3
View File
@@ -14,13 +14,13 @@ pub(crate) async fn create_typing_event_route(
body: Ruma<create_typing_event::v3::Request>,
) -> Result<create_typing_event::v3::Response> {
use create_typing_event::v3::Typing;
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
services
.users
.update_device_last_seen(sender_user, body.identity.sender_device(), ip)
.update_device_last_seen(sender_user, body.sender_device.as_deref(), ip)
.await;
if sender_user != body.user_id && !body.identity.is_appservice() {
if sender_user != body.user_id && body.appservice_info.is_none() {
return Err!(Request(Forbidden("You cannot update typing status of other users.")));
}
+1 -1
View File
@@ -26,7 +26,7 @@ pub(crate) async fn search_users_route(
State(services): State<crate::State>,
body: Ruma<search_users::v3::Request>,
) -> Result<search_users::v3::Response> {
let sender_user = body.identity.sender_user();
let sender_user = body.sender_user();
let limit = usize::try_from(body.limit)
.map_or(LIMIT_DEFAULT, usize::from)
.min(LIMIT_MAX);
+13 -3
View File
@@ -2,13 +2,15 @@ use std::time::{Duration, SystemTime};
use axum::extract::State;
use base64::{Engine as _, engine::general_purpose};
use conduwuit::{Err, Result};
use conduwuit::{Err, Result, utils};
use hmac::{Hmac, KeyInit, Mac};
use ruma::{SecondsSinceUnixEpoch, api::client::voip::get_turn_server_info};
use ruma::{SecondsSinceUnixEpoch, UserId, api::client::voip::get_turn_server_info};
use sha1::Sha1;
use crate::Ruma;
const RANDOM_USER_ID_LENGTH: usize = 10;
type HmacSha1 = Hmac<Sha1>;
/// # `GET /_matrix/client/r0/voip/turnServer`
@@ -33,7 +35,15 @@ pub(crate) async fn turn_server_route(
)
.expect("time is valid");
let username: String = format!("{}:{}", expiry.get(), body.identity.sender_user());
let user = body.sender_user.unwrap_or_else(|| {
UserId::parse_with_server_name(
utils::random_string(RANDOM_USER_ID_LENGTH).to_lowercase(),
&services.server.name,
)
.unwrap()
});
let username: String = format!("{}:{}", expiry.get(), user);
let mut mac = HmacSha1::new_from_slice(turn_secret.as_bytes())
.expect("HMAC can take key of any size");
+1 -1
View File
@@ -15,7 +15,7 @@ pub(super) use conduwuit_service::state::State;
use http::{Uri, uri};
use self::handler::RouterExt;
pub(super) use self::{args::Args as Ruma, auth::ClientIdentity, response::RumaResponse};
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
use crate::{admin, client, server};
pub fn build(router: Router<State>, state: State) -> Router<State> {
+68 -13
View File
@@ -6,14 +6,17 @@ use axum::{
extract::{FromRequest, Path, Query},
};
use conduwuit::{Error, Result, err};
use ruma::{CanonicalJsonObject, api::IncomingRequest};
use ruma::{
CanonicalJsonObject, DeviceId, OwnedDeviceId, OwnedServerName, OwnedUserId, ServerName,
UserId, api::IncomingRequest,
};
use serde::Deserialize;
use crate::{State, router::auth::CheckAuth};
use crate::{State, router::auth::CheckAuth, service::appservice::RegistrationInfo};
/// Query parameters needed to authenticate requests
#[derive(Deserialize)]
pub(crate) struct AuthQueryParams {
pub(super) struct AuthQueryParams {
pub(super) user_id: Option<String>,
/// Device ID for appservice device masquerading (MSC3202/MSC4190).
/// Can be provided as `device_id` or `org.matrix.msc3202.device_id`.
@@ -22,22 +25,67 @@ pub(crate) struct AuthQueryParams {
}
/// Extractor for Ruma request structs
pub(crate) struct Args<R: IncomingRequest<Authentication: CheckAuth> + Send + Sync + 'static> {
pub(crate) struct Args<T> {
/// Request struct body
pub(crate) body: R,
pub(crate) body: T,
/// Parsed JSON body. None when body is not JSON.
/// Federation server authentication: X-Matrix origin
/// None when not a federation server.
pub(crate) origin: Option<OwnedServerName>,
/// Local user authentication: user_id.
/// None when not an authenticated local user.
pub(crate) sender_user: Option<OwnedUserId>,
/// Local user authentication: device_id.
/// None when not an authenticated local user or no device.
pub(crate) sender_device: Option<OwnedDeviceId>,
/// Appservice authentication; registration info.
/// None when not an appservice.
pub(crate) appservice_info: Option<RegistrationInfo>,
/// Parsed JSON content.
/// None when body is not a valid string
pub(crate) json_body: Option<CanonicalJsonObject>,
/// Identity of the requesting entity
pub(crate) identity: <R::Authentication as CheckAuth>::Identity,
}
impl<R> Deref for Args<R>
impl<T> Args<T>
where
R: IncomingRequest<Authentication: CheckAuth> + Send + Sync + 'static,
T: IncomingRequest + Send + Sync + 'static,
{
type Target = R;
#[inline]
pub(crate) fn sender(&self) -> (&UserId, &DeviceId) {
(self.sender_user(), self.sender_device())
}
#[inline]
pub(crate) fn sender_user(&self) -> &UserId {
self.sender_user
.as_deref()
.expect("user must be authenticated for this handler")
}
#[inline]
pub(crate) fn sender_device(&self) -> &DeviceId {
self.sender_device
.as_deref()
.expect("user must be authenticated and device identified")
}
#[inline]
pub(crate) fn origin(&self) -> &ServerName {
self.origin
.as_deref()
.expect("server must be authenticated for this handler")
}
}
impl<T> Deref for Args<T>
where
T: IncomingRequest + Send + Sync + 'static,
{
type Target = T;
fn deref(&self) -> &Self::Target { &self.body }
}
@@ -97,6 +145,13 @@ where
let body = R::try_from_http_request(request, &path)
.map_err(|e| err!(Request(BadJson(debug_warn!("{e}")))))?;
Ok(Self { body, json_body, identity: auth })
Ok(Self {
body,
origin: auth.origin,
sender_user: auth.sender_user,
sender_device: auth.sender_device,
appservice_info: auth.appservice_info,
json_body,
})
}
}
+112 -154
View File
@@ -3,7 +3,7 @@ use std::any::{Any, TypeId};
use conduwuit::{Err, Error, Result, err};
use http::StatusCode;
use ruma::{
DeviceId, OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
OwnedDeviceId, OwnedServerName, OwnedUserId, UserId,
api::{
IncomingRequest,
auth_scheme::{
@@ -24,57 +24,20 @@ use service::{
use crate::{router::args::AuthQueryParams, service::appservice::RegistrationInfo};
pub(crate) enum ClientIdentity {
User {
sender_user: OwnedUserId,
sender_device: OwnedDeviceId,
},
Appservice {
sender_user: OwnedUserId,
sender_device: Option<OwnedDeviceId>,
appservice_info: Box<RegistrationInfo>,
},
#[derive(Default)]
pub(super) struct Auth {
pub(super) origin: Option<OwnedServerName>,
pub(super) sender_user: Option<OwnedUserId>,
pub(super) sender_device: Option<OwnedDeviceId>,
pub(super) appservice_info: Option<RegistrationInfo>,
}
impl ClientIdentity {
pub(crate) fn sender_user(&self) -> &UserId {
match self {
| Self::User { sender_user, .. } | Self::Appservice { sender_user, .. } =>
sender_user,
}
}
pub(crate) fn sender_device(&self) -> Option<&DeviceId> {
match self {
| Self::User { sender_device, .. } => Some(sender_device),
| Self::Appservice { sender_device, .. } => sender_device.as_deref(),
}
}
pub(crate) fn expect_sender_device(&self) -> Result<&DeviceId> {
self.sender_device().ok_or_else(|| {
err!(Request(Forbidden("Appservices must masquerade to use this endpoint.")))
})
}
pub(crate) fn appservice_info(&self) -> Option<&RegistrationInfo> {
match self {
| Self::User { .. } => None,
| Self::Appservice { appservice_info, .. } => Some(appservice_info),
}
}
pub(crate) fn is_appservice(&self) -> bool { matches!(self, Self::Appservice { .. }) }
}
pub(crate) trait CheckAuth: AuthScheme {
type Identity: Send;
pub(super) trait CheckAuth: AuthScheme {
fn authenticate<R: IncomingRequest + Any, B: AsRef<[u8]> + Sync>(
services: &Services,
incoming_request: &hyper::Request<B>,
query: AuthQueryParams,
) -> impl Future<Output = Result<Self::Identity>> + Send {
) -> impl Future<Output = Result<Auth>> + Send {
async move {
let route = TypeId::of::<R>();
@@ -95,19 +58,17 @@ pub(crate) trait CheckAuth: AuthScheme {
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> impl Future<Output = Result<Self::Identity>> + Send;
) -> impl Future<Output = Result<Auth>> + Send;
}
impl CheckAuth for ServerSignatures {
type Identity = OwnedServerName;
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Self::Identity> {
) -> Result<Auth> {
let destination = services.globals.server_name();
if output
.destination
@@ -139,7 +100,10 @@ impl CheckAuth for ServerSignatures {
)));
}
Ok(output.origin)
Ok(Auth {
origin: Some(output.origin.clone()),
..Default::default()
})
},
| Err(err) =>
Err!(Request(Unauthorized(warn!("Failed to verify X-Matrix header: {err}")))),
@@ -148,181 +112,175 @@ impl CheckAuth for ServerSignatures {
}
impl CheckAuth for AccessToken {
type Identity = ClientIdentity;
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
_request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
if let Some((sender_user, sender_device, status)) =
services.users.find_from_token(&output).await
{
// If the token is expired we return a soft logout
if matches!(status, AccessTokenStatus::Expired) {
) -> Result<Auth> {
let (sender_user, sender_device, appservice_info) = {
if let Some((sender_user, sender_device, status)) =
services.users.find_from_token(&output).await
{
// If the token is expired we return a soft logout
if matches!(status, AccessTokenStatus::Expired) {
return Err(Error::Request(
ErrorKind::UnknownToken(
assign!(UnknownTokenErrorData::new(), { soft_logout: true }),
),
"This token has expired".into(),
StatusCode::UNAUTHORIZED,
));
}
// Locked users can only use /logout and /logout/all
if services
.users
.is_locked(&sender_user)
.await
.is_ok_and(std::convert::identity)
{
if !(route == TypeId::of::<client::session::logout::v3::Request>()
|| route == TypeId::of::<client::session::logout_all::v3::Request>())
{
return Err!(Request(UserLocked("Your account is locked.")));
}
}
(Some(sender_user), Some(sender_device), None)
} else if let Ok(appservice_info) = services.appservice.find_from_token(&output).await
{
let Ok(sender_user) = query.user_id.clone().map_or_else(
|| {
UserId::parse_with_server_name(
appservice_info.registration.sender_localpart.as_str(),
services.globals.server_name(),
)
},
UserId::parse,
) else {
return Err!(Request(InvalidUsername("Username is invalid.")));
};
if !appservice_info.is_user_match(&sender_user) {
return Err!(Request(Exclusive("User is not in namespace.")));
}
// MSC3202/MSC4190: Handle device_id masquerading for appservices.
// The device_id can be provided via `device_id` or
// `org.matrix.msc3202.device_id` query parameter.
let sender_device =
if let Some(device_id) = query.device_id.as_deref().map(Into::into) {
// Verify the device exists for this user
if services
.users
.get_device_metadata(&sender_user, device_id)
.await
.is_err()
{
return Err!(Request(Forbidden(
"Device does not exist for user or appservice cannot masquerade \
as this device."
)));
}
Some(device_id.to_owned())
} else {
None
};
(Some(sender_user), sender_device, Some(appservice_info))
} else {
return Err(Error::Request(
ErrorKind::UnknownToken(
assign!(UnknownTokenErrorData::new(), { soft_logout: true }),
),
"This token has expired".into(),
ErrorKind::UnknownToken(UnknownTokenErrorData::new()),
"Invalid token".into(),
StatusCode::UNAUTHORIZED,
));
}
};
// Locked users can only use /logout and /logout/all
if services
.users
.is_locked(&sender_user)
.await
.is_ok_and(std::convert::identity)
{
if !(route == TypeId::of::<client::session::logout::v3::Request>()
|| route == TypeId::of::<client::session::logout_all::v3::Request>())
{
return Err!(Request(UserLocked("Your account is locked.")));
}
}
Ok(ClientIdentity::User { sender_user, sender_device })
} else if let Ok(appservice_info) = services.appservice.find_from_token(&output).await {
let Ok(sender_user) = query.user_id.clone().map_or_else(
|| {
UserId::parse_with_server_name(
appservice_info.registration.sender_localpart.as_str(),
services.globals.server_name(),
)
},
UserId::parse,
) else {
return Err!(Request(InvalidUsername("Username is invalid.")));
};
if !appservice_info.is_user_match(&sender_user) {
return Err!(Request(Exclusive("User is not in namespace.")));
}
// MSC3202/MSC4190: Handle device_id masquerading for appservices.
// The device_id can be provided via `device_id` or
// `org.matrix.msc3202.device_id` query parameter.
let sender_device =
if let Some(device_id) = query.device_id.as_deref().map(Into::into) {
// Verify the device exists for this user
if services
.users
.get_device_metadata(&sender_user, device_id)
.await
.is_err()
{
return Err!(Request(Forbidden(
"Device does not exist for user or appservice cannot masquerade as \
this device."
)));
}
Some(device_id.to_owned())
} else {
None
};
Ok(ClientIdentity::Appservice {
sender_user,
sender_device,
appservice_info: Box::new(appservice_info),
})
} else {
Err(Error::Request(
ErrorKind::UnknownToken(UnknownTokenErrorData::new()),
"Invalid token".into(),
StatusCode::UNAUTHORIZED,
))
}
Ok(Auth {
sender_user,
sender_device,
appservice_info,
..Default::default()
})
}
}
impl CheckAuth for AccessTokenOptional {
type Identity = Option<ClientIdentity>;
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
) -> Result<Auth> {
match output {
| Some(token) =>
<AccessToken as CheckAuth>::verify(services, token, request, query, route)
.await
.map(Some),
| None => Ok(None),
<AccessToken as CheckAuth>::verify(services, token, request, query, route).await,
| None => Ok(Auth::default()),
}
}
}
impl CheckAuth for AppserviceToken {
type Identity = RegistrationInfo;
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
_request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Self::Identity> {
) -> Result<Auth> {
let Ok(appservice_info) = services.appservice.find_from_token(&output).await else {
return Err!(Request(Unauthorized("Invalid appservice token.")));
};
Ok(appservice_info)
Ok(Auth {
appservice_info: Some(appservice_info),
..Default::default()
})
}
}
impl CheckAuth for AppserviceTokenOptional {
type Identity = Option<RegistrationInfo>;
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
) -> Result<Auth> {
match output {
| Some(token) =>
<AppserviceToken as CheckAuth>::verify(services, token, request, query, route)
.await
.map(Some),
| None => Ok(None),
.await,
| None => Ok(Auth::default()),
}
}
}
impl CheckAuth for NoAuthentication {
type Identity = ();
async fn verify<B: AsRef<[u8]> + Sync>(
_services: &Services,
_output: Self::Output,
_request: &hyper::Request<B>,
_query: AuthQueryParams,
_route: TypeId,
) -> Result<Self::Identity> {
Ok(())
) -> Result<Auth> {
Ok(Auth::default())
}
}
impl CheckAuth for NoAccessToken {
type Identity = Option<ClientIdentity>;
async fn verify<B: AsRef<[u8]> + Sync>(
services: &Services,
_output: Self::Output,
request: &hyper::Request<B>,
query: AuthQueryParams,
route: TypeId,
) -> Result<Self::Identity> {
) -> Result<Auth> {
// We handle these the same as AccessTokenOptional
let token = AccessTokenOptional::extract_authentication(request).map_err(|err| {
err!(Request(Unauthorized(warn!("Failed to extract authorization: {}", err))))
+3 -3
View File
@@ -28,7 +28,7 @@ pub(crate) async fn get_backfill_route(
) -> Result<get_backfill::v1::Response> {
AccessCheck {
services: &services,
origin: &body.identity,
origin: body.origin(),
room_id: &body.room_id,
event_id: None,
}
@@ -41,7 +41,7 @@ pub(crate) async fn get_backfill_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve backfill for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -76,7 +76,7 @@ pub(crate) async fn get_backfill_route(
Ok(services
.rooms
.state_accessor
.server_can_see_event(&body.identity, &pdu.room_id_or_hash(), &pdu.event_id)
.server_can_see_event(body.origin(), &pdu.room_id_or_hash(), &pdu.event_id)
.await
.then_some(pdu))
})
+2 -2
View File
@@ -40,7 +40,7 @@ pub(crate) async fn get_event_route(
AccessCheck {
services: &services,
origin: &body.identity,
origin: body.origin(),
room_id,
event_id: Some(&body.event_id),
}
@@ -54,7 +54,7 @@ pub(crate) async fn get_event_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve state for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
+2 -2
View File
@@ -19,7 +19,7 @@ pub(crate) async fn get_event_authorization_route(
) -> Result<get_event_authorization::v1::Response> {
AccessCheck {
services: &services,
origin: &body.identity,
origin: body.origin(),
room_id: &body.room_id,
event_id: None,
}
@@ -42,7 +42,7 @@ pub(crate) async fn get_event_authorization_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve state for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
+4 -4
View File
@@ -22,7 +22,7 @@ pub(crate) async fn get_missing_events_route(
) -> Result<get_missing_events::v1::Response> {
AccessCheck {
services: &services,
origin: &body.identity,
origin: body.origin(),
room_id: &body.room_id,
event_id: None,
}
@@ -36,7 +36,7 @@ pub(crate) async fn get_missing_events_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve state for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -91,10 +91,10 @@ pub(crate) async fn get_missing_events_route(
if !services
.rooms
.state_accessor
.server_can_see_event(&body.identity, &body.room_id, pdu.event_id())
.server_can_see_event(body.origin(), &body.room_id, pdu.event_id())
.await
{
debug!(%next_event_id, origin = %body.identity, "redacting event origin cannot see");
debug!(%next_event_id, origin = %body.origin(), "redacting event origin cannot see");
pdu.redact(&room_version, json!({}))?;
}
+2 -2
View File
@@ -20,7 +20,7 @@ pub(crate) async fn get_hierarchy_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve state for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -29,7 +29,7 @@ pub(crate) async fn get_hierarchy_route(
let response = services
.rooms
.summary
.get_local_room_summary_for_server(&body.identity, &body.room_id, body.suggested_only)
.get_local_room_summary_for_server(body.origin(), &body.room_id, body.suggested_only)
.await;
if let Accessibility::Accessible(response) = response {
+5 -4
View File
@@ -32,7 +32,7 @@ pub(crate) async fn create_invite_route(
services
.rooms
.event_handler
.acl_check(&body.identity, &body.room_id)
.acl_check(body.origin(), &body.room_id)
.await?;
if !services.server.supported_room_version(&body.room_version) {
@@ -54,11 +54,12 @@ pub(crate) async fn create_invite_route(
if services
.moderation
.is_remote_server_forbidden(&body.identity)
.is_remote_server_forbidden(body.origin())
{
warn!(
"Received federated/remote invite from banned server {} for room ID {}. Rejecting.",
body.identity, body.room_id
body.origin(),
body.room_id
);
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
@@ -104,7 +105,7 @@ pub(crate) async fn create_invite_route(
.and_then(Result::ok)
.ok_or_else(|| err!(Request(InvalidParam("Invalid sender property"))))?;
if sender_user.server_name() != body.identity {
if sender_user.server_name() != body.origin() {
return Err!(Request(Forbidden("Sender's server does not match the origin server.",)));
}
+8 -6
View File
@@ -30,7 +30,7 @@ use crate::Ruma;
/// # `GET /_matrix/federation/v1/make_join/{roomId}/{userId}`
///
/// Creates a join template.
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.identity), level = "info")]
#[tracing::instrument(skip_all, fields(room_id = %body.room_id, user_id = %body.user_id, origin = %body.origin()), level = "info")]
pub(crate) async fn create_join_event_template_route(
State(services): State<crate::State>,
body: Ruma<prepare_join_event::v1::Request>,
@@ -45,14 +45,14 @@ pub(crate) async fn create_join_event_template_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
room_id = %body.room_id,
"Refusing to serve make_join for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
}
if body.user_id.server_name() != body.identity {
if body.user_id.server_name() != body.origin() {
return Err!(Request(BadJson("Not allowed to join on behalf of another server/user.")));
}
@@ -60,17 +60,19 @@ pub(crate) async fn create_join_event_template_route(
services
.rooms
.event_handler
.acl_check(&body.identity, &body.room_id)
.acl_check(body.origin(), &body.room_id)
.await?;
if services
.moderation
.is_remote_server_forbidden(&body.identity)
.is_remote_server_forbidden(body.origin())
{
warn!(
"Server {} for remote user {} tried joining room ID {} which has a server name that \
is globally forbidden. Rejecting.",
body.identity, &body.user_id, &body.room_id,
body.origin(),
&body.user_id,
&body.room_id,
);
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
+7 -5
View File
@@ -28,14 +28,14 @@ pub(crate) async fn create_knock_event_template_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
room_id = %body.room_id,
"Refusing to serve make_knock for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
}
if body.user_id.server_name() != body.identity {
if body.user_id.server_name() != body.origin() {
return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
}
@@ -43,17 +43,19 @@ pub(crate) async fn create_knock_event_template_route(
services
.rooms
.event_handler
.acl_check(&body.identity, &body.room_id)
.acl_check(body.origin(), &body.room_id)
.await?;
if services
.moderation
.is_remote_server_forbidden(&body.identity)
.is_remote_server_forbidden(body.origin())
{
warn!(
"Server {} for remote user {} tried knocking room ID {} which has a server name \
that is globally forbidden. Rejecting.",
body.identity, &body.user_id, &body.room_id,
body.origin(),
&body.user_id,
&body.room_id,
);
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
+3 -3
View File
@@ -26,13 +26,13 @@ pub(crate) async fn create_leave_event_template_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve make_leave for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
}
if body.user_id.server_name() != body.identity {
if body.user_id.server_name() != body.origin() {
return Err!(Request(Forbidden(
"Not allowed to leave on behalf of another server/user."
)));
@@ -42,7 +42,7 @@ pub(crate) async fn create_leave_event_template_route(
services
.rooms
.event_handler
.acl_check(&body.identity, &body.room_id)
.acl_check(body.origin(), &body.room_id)
.await?;
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
+4 -4
View File
@@ -62,7 +62,7 @@ pub(crate) async fn send_transaction_message_route(
ClientIp(client): ClientIp,
body: Ruma<send_transaction_message::v1::Request>,
) -> Result<send_transaction_message::v1::Response> {
if body.identity != body.body.origin {
if body.origin() != body.body.origin {
return Err!(Request(Forbidden(
"Not allowed to send transactions on behalf of other servers"
)));
@@ -80,7 +80,7 @@ pub(crate) async fn send_transaction_message_route(
)));
}
let txn_key = (body.identity.clone(), body.transaction_id.clone());
let txn_key = (body.origin().to_owned(), body.transaction_id.clone());
// Atomically check cache, join active, or start new transaction
match services
@@ -136,7 +136,7 @@ async fn wait_for_result(
skip_all,
fields(
id = ?body.transaction_id.as_str(),
origin = ?body.identity
origin = ?body.origin()
)
)]
async fn process_inbound_transaction(
@@ -164,7 +164,7 @@ async fn process_inbound_transaction(
.stream();
debug!(pdus = body.pdus.len(), edus = body.edus.len(), "Processing transaction",);
let results = match handle(&services, &client, &body.identity, pdus, edus).await {
let results = match handle(&services, &client, body.origin(), pdus, edus).await {
| Ok(results) => results,
| Err(err) => {
fail_federation_txn(services, &txn_key, &sender, err);
+5 -4
View File
@@ -304,7 +304,7 @@ pub(crate) async fn create_join_event_v2_route(
) -> Result<create_join_event::v2::Response> {
if services
.moderation
.is_remote_server_forbidden(&body.identity)
.is_remote_server_forbidden(body.origin())
{
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
@@ -314,7 +314,8 @@ pub(crate) async fn create_join_event_v2_route(
warn!(
"Server {} tried joining room ID {} through us which has a server name that is \
globally forbidden. Rejecting.",
body.identity, &body.room_id,
body.origin(),
&body.room_id,
);
return Err!(Request(Forbidden(warn!(
"Room ID server name {server} is banned on this homeserver."
@@ -324,12 +325,12 @@ pub(crate) async fn create_join_event_v2_route(
let now = Instant::now();
let room_state =
create_join_event(&services, &body.identity, &body.room_id, &body.pdu, body.omit_members)
create_join_event(&services, body.origin(), &body.room_id, &body.pdu, body.omit_members)
.boxed()
.await?;
info!(
"Finished sending a join for {} in {} in {:?}",
body.identity,
body.origin(),
&body.room_id,
now.elapsed()
);
+8 -6
View File
@@ -26,12 +26,13 @@ pub(crate) async fn create_knock_event_v1_route(
) -> Result<create_knock_event::v1::Response> {
if services
.moderation
.is_remote_server_forbidden(&body.identity)
.is_remote_server_forbidden(body.origin())
{
warn!(
"Server {} tried knocking room ID {} who has a server name that is globally \
forbidden. Rejecting.",
body.identity, &body.room_id,
body.origin(),
&body.room_id,
);
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
@@ -41,7 +42,8 @@ pub(crate) async fn create_knock_event_v1_route(
warn!(
"Server {} tried knocking room ID {} which has a server name that is globally \
forbidden. Rejecting.",
body.identity, &body.room_id,
body.origin(),
&body.room_id,
);
return Err!(Request(Forbidden("Server is banned on this homeserver.")));
}
@@ -58,7 +60,7 @@ pub(crate) async fn create_knock_event_v1_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve send_knock for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
@@ -68,7 +70,7 @@ pub(crate) async fn create_knock_event_v1_route(
services
.rooms
.event_handler
.acl_check(&body.identity, &body.room_id)
.acl_check(body.origin(), &body.room_id)
.await?;
let room_version = services.rooms.state.get_room_version(&body.room_id).await?;
@@ -131,7 +133,7 @@ pub(crate) async fn create_knock_event_v1_route(
.await?;
// check if origin server is trying to send for another server
if sender.server_name() != body.identity {
if sender.server_name() != body.origin() {
return Err!(Request(BadJson("Not allowed to knock on behalf of another server/user.")));
}
+1 -1
View File
@@ -21,7 +21,7 @@ pub(crate) async fn create_leave_event_v2_route(
State(services): State<crate::State>,
body: Ruma<create_leave_event::v2::Request>,
) -> Result<create_leave_event::v2::Response> {
create_leave_event(&services, &body.identity, &body.room_id, &body.pdu).await?;
create_leave_event(&services, body.origin(), &body.room_id, &body.pdu).await?;
Ok(create_leave_event::v2::Response::new())
}
+2 -2
View File
@@ -17,7 +17,7 @@ pub(crate) async fn get_room_state_route(
) -> Result<get_room_state::v1::Response> {
AccessCheck {
services: &services,
origin: &body.identity,
origin: body.origin(),
room_id: &body.room_id,
event_id: None,
}
@@ -40,7 +40,7 @@ pub(crate) async fn get_room_state_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve state for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
+2 -2
View File
@@ -18,7 +18,7 @@ pub(crate) async fn get_room_state_ids_route(
) -> Result<get_room_state_ids::v1::Response> {
AccessCheck {
services: &services,
origin: &body.identity,
origin: body.origin(),
room_id: &body.room_id,
event_id: None,
}
@@ -41,7 +41,7 @@ pub(crate) async fn get_room_state_ids_route(
.await
{
info!(
origin = body.identity.as_str(),
origin = body.origin().as_str(),
"Refusing to serve state for room we aren't participating in"
);
return Err!(Request(NotFound("This server is not participating in that room.")));
+3 -3
View File
@@ -60,13 +60,13 @@ pub(crate) async fn get_devices_route(
let master_key = services
.users
.get_master_key(None, &body.user_id, &|u| u.server_name() == body.identity)
.get_master_key(None, &body.user_id, &|u| u.server_name() == body.origin())
.await
.ok();
let self_signing_key = services
.users
.get_self_signing_key(None, &body.user_id, &|u| u.server_name() == body.identity)
.get_self_signing_key(None, &body.user_id, &|u| u.server_name() == body.origin())
.await
.ok();
@@ -94,7 +94,7 @@ pub(crate) async fn get_keys_route(
&services,
None,
&body.device_keys,
|u| u.server_name() == body.identity,
|u| Some(u.server_name()) == body.origin.as_deref(),
services.globals.allow_device_name_federation(),
Duration::from_secs(0),
)
+37 -24
View File
@@ -2077,12 +2077,10 @@ pub struct Config {
pub stream_amplification: usize,
/// Number of sender task workers; determines sender parallelism. Default is
/// '0' which means the value is determined internally, likely matching the
/// number of tokio worker-threads or number of cores, etc. Override by
/// setting a non-zero value.
/// core count. Override by setting a different value.
///
/// default: 0
#[serde(default)]
/// default: core count
#[serde(default = "default_sender_workers")]
pub sender_workers: usize,
/// Enables listener sockets; can be set to false to disable listening. This
@@ -2522,45 +2520,47 @@ fn default_database_backups_to_keep() -> i16 { 1 }
fn default_db_write_buffer_capacity_mb() -> f64 { 48.0 + parallelism_scaled_f64(4.0) }
fn default_db_cache_capacity_mb() -> f64 { 128.0 + parallelism_scaled_f64(64.0) }
fn default_db_cache_capacity_mb() -> f64 { 512.0 + parallelism_scaled_f64(512.0) }
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(10_000).saturating_add(100_000) }
fn default_pdu_cache_capacity() -> u32 { parallelism_scaled_u32(50_000).saturating_add(100_000) }
fn default_cache_capacity_modifier() -> f64 { 1.0 }
fn default_auth_chain_cache_capacity() -> u32 {
parallelism_scaled_u32(10_000).saturating_add(100_000)
}
fn default_shorteventid_cache_capacity() -> u32 {
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_shorteventid_cache_capacity() -> u32 {
parallelism_scaled_u32(100_000).saturating_add(100_000)
}
fn default_eventidshort_cache_capacity() -> u32 {
parallelism_scaled_u32(25_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_eventid_pdu_cache_capacity() -> u32 {
parallelism_scaled_u32(25_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_shortstatekey_cache_capacity() -> u32 {
parallelism_scaled_u32(10_000).saturating_add(100_000)
parallelism_scaled_u32(100_000).saturating_add(100_000)
}
fn default_statekeyshort_cache_capacity() -> u32 {
parallelism_scaled_u32(10_000).saturating_add(100_000)
parallelism_scaled_u32(50_000).saturating_add(100_000)
}
fn default_servernameevent_data_cache_capacity() -> u32 {
parallelism_scaled_u32(100_000).saturating_add(500_000)
parallelism_scaled_u32(100_000).saturating_add(100_000)
}
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(100) }
fn default_stateinfo_cache_capacity() -> u32 { parallelism_scaled_u32(500).clamp(100, 12000) }
fn default_roomid_spacehierarchy_cache_capacity() -> u32 { parallelism_scaled_u32(1000) }
fn default_roomid_spacehierarchy_cache_capacity() -> u32 {
parallelism_scaled_u32(500).clamp(100, 12000)
}
fn default_dns_cache_entries() -> u32 { 32768 }
fn default_dns_cache_entries() -> u32 { 327_680 }
fn default_dns_min_ttl() -> u64 { 60 * 180 }
@@ -2768,15 +2768,26 @@ fn default_admin_log_capture() -> String {
fn default_admin_room_tag() -> String { "m.server_notice".to_owned() }
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_precision_loss)]
fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
pub fn parallelism_scaled_f64(val: f64) -> f64 { val * (sys::available_parallelism() as f64) }
fn parallelism_scaled_u32(val: u32) -> u32 {
let val = val.try_into().expect("failed to cast u32 to usize");
parallelism_scaled(val).try_into().unwrap_or(u32::MAX)
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
pub fn parallelism_scaled_u32(val: u32) -> u32 {
val.saturating_mul(sys::available_parallelism() as u32)
}
fn parallelism_scaled(val: usize) -> usize { val.saturating_mul(sys::available_parallelism()) }
#[must_use]
#[allow(clippy::as_conversions, clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub fn parallelism_scaled_i32(val: i32) -> i32 {
val.saturating_mul(sys::available_parallelism() as i32)
}
#[must_use]
pub fn parallelism_scaled(val: usize) -> usize {
val.saturating_mul(sys::available_parallelism())
}
fn default_trusted_server_batch_size() -> usize { 256 }
@@ -2796,6 +2807,8 @@ fn default_stream_width_scale() -> f32 { 1.0 }
fn default_stream_amplification() -> usize { 1024 }
fn default_sender_workers() -> usize { parallelism_scaled(1) }
fn default_client_receive_timeout() -> u64 { 75 }
fn default_client_request_timeout() -> u64 { 180 }
+3
View File
@@ -5,6 +5,7 @@ pub type DigestOut = [u8; 256 / 8];
/// Sha256 hash (input gather joined by 0xFF bytes)
#[must_use]
#[tracing::instrument(skip(inputs), level = "trace")]
#[allow(clippy::unnecessary_fallible_conversions)]
pub fn delimited<'a, T, I>(mut inputs: I) -> DigestOut
where
I: Iterator<Item = T> + 'a,
@@ -25,6 +26,7 @@ where
/// Sha256 hash (input gather)
#[must_use]
#[tracing::instrument(skip(inputs), level = "trace")]
#[allow(clippy::unnecessary_fallible_conversions)]
pub fn concat<'a, T, I>(inputs: I) -> DigestOut
where
I: Iterator<Item = T> + 'a,
@@ -43,6 +45,7 @@ where
#[inline]
#[must_use]
#[tracing::instrument(skip(input), level = "trace")]
#[allow(clippy::unnecessary_fallible_conversions)]
pub fn hash<T>(input: T) -> DigestOut
where
T: AsRef<[u8]>,
+1 -1
View File
@@ -29,7 +29,7 @@ fn descriptor_cf_options(
set_table_options(&mut opts, &desc, cache)?;
opts.set_min_write_buffer_number(1);
opts.set_max_write_buffer_number(2);
opts.set_max_write_buffer_number(3);
opts.set_write_buffer_size(desc.write_size);
opts.set_target_file_size_base(desc.file_size);
+1 -6
View File
@@ -201,12 +201,7 @@ pub(super) static MAPS: &[Descriptor] = &[
},
Descriptor {
name: "roomsynctoken_shortstatehash",
file_shape: 3,
val_size_hint: Some(8),
block_size: 512,
compression_level: 3,
bottommost_level: Some(6),
..descriptor::SEQUENTIAL
..descriptor::DROPPED
},
Descriptor {
name: "roomuserdataid_accountdata",
+4 -4
View File
@@ -8,7 +8,7 @@ use axum::{
extract::State,
response::{IntoResponse, Response},
};
use conduwuit::{Result, debug, debug_error, debug_warn, err, error, trace};
use conduwuit::{Result, debug_warn, err, error, info, trace};
use conduwuit_service::Services;
use futures::FutureExt;
use http::{Method, StatusCode, Uri};
@@ -102,11 +102,11 @@ fn handle_result(method: &Method, uri: &Uri, result: Response) -> Result<Respons
let reason = status.canonical_reason().unwrap_or("Unknown Reason");
if status.is_server_error() {
error!(%method, %uri, "{code} {reason}");
info!(%method, %uri, "{code} {reason}");
} else if status.is_client_error() {
debug_error!(%method, %uri, "{code} {reason}");
info!(%method, %uri, "{code} {reason}");
} else if status.is_redirection() {
debug!(%method, %uri, "{code} {reason}");
trace!(%method, %uri, "{code} {reason}");
} else {
trace!(%method, %uri, "{code} {reason}");
}
+1 -1
View File
@@ -100,7 +100,7 @@ impl Service {
/// Pings the presence of the given user in the given room, setting the
/// specified state.
pub async fn ping_presence(&self, user_id: &UserId, new_state: &PresenceState) -> Result<()> {
const REFRESH_TIMEOUT: u64 = 60 * 1000;
const REFRESH_TIMEOUT: u64 = 60 * 1000 * 4;
let last_presence = self.db.get_presence(user_id).await;
let state_changed = match last_presence {
+1 -1
View File
@@ -294,7 +294,7 @@ impl Service {
pub async fn appservice_checks(
&self,
room_alias: &RoomAliasId,
appservice_info: Option<&RegistrationInfo>,
appservice_info: &Option<RegistrationInfo>,
) -> Result<()> {
if !self
.services
@@ -80,7 +80,7 @@ where
{
// Exponential backoff
const MIN_DURATION: u64 = 60 * 2;
const MAX_DURATION: u64 = 60 * 60 * 8;
const MAX_DURATION: u64 = 60 * 60;
if continue_exponential_backoff_secs(
MIN_DURATION,
MAX_DURATION,
@@ -46,7 +46,7 @@ where
{
// Exponential backoff
const MIN_DURATION: u64 = 5 * 60;
const MAX_DURATION: u64 = 60 * 60 * 24;
const MAX_DURATION: u64 = 60 * 60;
if continue_exponential_backoff_secs(MIN_DURATION, MAX_DURATION, time.elapsed(), *tries) {
debug!(
?tries,
+2 -46
View File
@@ -1,10 +1,10 @@
use std::sync::Arc;
use conduwuit::{Result, implement};
use database::{Database, Deserialized, Map};
use database::{Deserialized, Map};
use ruma::{RoomId, UserId};
use crate::{Dep, globals, rooms, rooms::short::ShortStateHash};
use crate::{Dep, globals};
pub struct Service {
db: Data,
@@ -12,32 +12,25 @@ pub struct Service {
}
struct Data {
db: Arc<Database>,
userroomid_notificationcount: Arc<Map>,
userroomid_highlightcount: Arc<Map>,
roomuserid_lastnotificationread: Arc<Map>,
roomsynctoken_shortstatehash: Arc<Map>,
}
struct Services {
globals: Dep<globals::Service>,
short: Dep<rooms::short::Service>,
}
impl crate::Service for Service {
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
db: Data {
db: args.db.clone(),
userroomid_notificationcount: args.db["userroomid_notificationcount"].clone(),
userroomid_highlightcount: args.db["userroomid_highlightcount"].clone(),
roomuserid_lastnotificationread: args.db["userroomid_highlightcount"].clone(),
roomsynctoken_shortstatehash: args.db["roomsynctoken_shortstatehash"].clone(),
},
services: Services {
globals: args.depend::<globals::Service>("globals"),
short: args.depend::<rooms::short::Service>("rooms::short"),
},
}))
}
@@ -90,40 +83,3 @@ pub async fn last_notification_read(&self, user_id: &UserId, room_id: &RoomId) -
.deserialized()
.unwrap_or(0)
}
#[implement(Service)]
pub async fn associate_token_shortstatehash(
&self,
room_id: &RoomId,
token: u64,
shortstatehash: ShortStateHash,
) {
let shortroomid = self
.services
.short
.get_shortroomid(room_id)
.await
.expect("room exists");
let _cork = self.db.db.cork();
let key: &[u64] = &[shortroomid, token];
self.db
.roomsynctoken_shortstatehash
.put(key, shortstatehash);
}
#[implement(Service)]
pub async fn get_token_shortstatehash(
&self,
room_id: &RoomId,
token: u64,
) -> Result<ShortStateHash> {
let shortroomid = self.services.short.get_shortroomid(room_id).await?;
let key: &[u64] = &[shortroomid, token];
self.db
.roomsynctoken_shortstatehash
.qry(key)
.await
.deserialized()
}
+10 -14
View File
@@ -92,20 +92,20 @@ impl UiaaSessionMetadata {
/// Information about the user which is initiating this UIAA session.
pub struct UiaaInitiator<'a> {
user_id: &'a UserId,
device_id: Option<&'a DeviceId>,
device_id: &'a DeviceId,
oauth_ticket: Option<OAuthTicket>,
}
impl<'a> UiaaInitiator<'a> {
#[must_use]
pub fn new(user_id: &'a UserId, device_id: Option<&'a DeviceId>) -> Self {
pub fn new(user_id: &'a UserId, device_id: &'a DeviceId) -> Self {
Self { user_id, device_id, oauth_ticket: None }
}
#[must_use]
pub fn with_oauth_ticket(
user_id: &'a UserId,
device_id: Option<&'a DeviceId>,
device_id: &'a DeviceId,
oauth_ticket: OAuthTicket,
) -> Self {
Self {
@@ -220,7 +220,7 @@ impl Service {
&self,
auth: &Option<AuthData>,
user_id: &UserId,
device_id: Option<&DeviceId>,
device_id: &DeviceId,
oauth_ticket: Option<OAuthTicket>,
) -> Result<Identity> {
self.authenticate(
@@ -252,16 +252,12 @@ impl Service {
let mut info = assign!(UiaaInfo::new(flows), { params: Some(params), session: Some(session_id.clone()) });
let session_metadata = if let Some(initiator) = initiator {
let is_oauth = if let Some(device_id) = initiator.device_id {
self.services
.oauth
.get_session_info_for_device(initiator.user_id, device_id)
.await
.is_some()
} else {
// Appservices never have oauth sessions
false
};
let is_oauth = self
.services
.oauth
.get_session_info_for_device(initiator.user_id, initiator.device_id)
.await
.is_some();
if is_oauth {
if let Some(oauth_ticket) = initiator.oauth_ticket {