Compare commits

..

12 Commits

Author SHA1 Message Date
Ginger
087d8b1016 fix: Remove sliding sync proxy from .well-known/client response 2026-04-06 10:36:30 -04:00
Renovate Bot
6155dd2726 chore(deps): update node-patch-updates to v2.0.8 2026-04-06 13:04:13 +00:00
timedout
688cd8f46a fix: Forbid creating events sent by remote users 2026-04-05 22:34:11 +01:00
timedout
3ab1f102dd fix: Switch lettre to ring backend 2026-04-05 21:07:45 +00:00
timedout
480a32e4d4 chore: Add newsfrag 2026-04-05 21:04:27 +01:00
timedout
fadd559837 feat: Add admin commands to delete pushers 2026-04-05 20:58:11 +01:00
timedout
79c63c17fc feat: Delete pushers when a user logs out 2026-04-05 20:48:03 +01:00
timedout
cdc772ba10 feat: Delete all pushers for a user during deactivation 2026-04-05 20:42:08 +01:00
timedout
5f1b80a47c chore: Add newsfrag 2026-04-05 20:15:52 +01:00
timedout
0f8b56f521 feat: Add admin command to reset user push rules 2026-04-05 20:12:21 +01:00
éźera
67d8d72506 fix: return 404 when joining non-existent room
Fixes #1443.
2026-04-05 11:40:53 -05:00
Renovate Bot
fcfa7b8bef chore(deps): update pre-commit hook crate-ci/typos to v1.45.0 2026-04-02 05:02:08 +00:00
16 changed files with 160 additions and 13 deletions

View File

@@ -24,7 +24,7 @@ repos:
- id: check-added-large-files
- repo: https://github.com/crate-ci/typos
rev: v1.44.0
rev: v1.45.0
hooks:
- id: typos
- id: typos

View File

@@ -559,7 +559,7 @@ version = "0.15.0"
[workspace.dependencies.lettre]
version = "0.11.19"
default-features = false
features = ["smtp-transport", "pool", "hostname", "builder", "rustls", "aws-lc-rs", "rustls-native-certs", "tokio1", "tokio1-rustls", "tracing", "serde"]
features = ["smtp-transport", "pool", "hostname", "builder", "rustls", "rustls-native-certs", "tokio1", "ring", "tokio1-rustls", "tracing", "serde"]
[workspace.dependencies.governor]
version = "0.10.4"

1
changelog.d/1579.bugfix Normal file
View File

@@ -0,0 +1 @@
Fixed error 500 when joining non-existent rooms. Contributed by @ezera.

1
changelog.d/1613.feature Normal file
View File

@@ -0,0 +1 @@
Added `!admin users reset-push-rules` command to reset the notification settings of users. Contributed by @nex.

1
changelog.d/1614.feature Normal file
View File

@@ -0,0 +1 @@
Notification pushers are now automatically removed when their associated device is. Admin commands now exist for manual cleanup too. Contributed by @nex.

View File

@@ -1,6 +1,10 @@
use clap::Subcommand;
use conduwuit::Result;
use ruma::OwnedUserId;
use conduwuit::{
Result,
utils::{IterStream, stream::BroadbandExt},
};
use futures::StreamExt;
use ruma::{OwnedDeviceId, OwnedUserId};
use crate::Context;
@@ -11,6 +15,23 @@ pub enum PusherCommand {
/// Full user ID
user_id: OwnedUserId,
},
/// Deletes a specific pusher by ID
DeletePusher {
user_id: OwnedUserId,
pusher_id: String,
},
/// Deletes all pushers for a user
DeleteAllUser {
user_id: OwnedUserId,
},
/// Deletes all pushers associated with a device ID
DeleteAllDevice {
user_id: OwnedUserId,
device_id: OwnedDeviceId,
},
}
pub(super) async fn process(subcommand: PusherCommand, context: &Context<'_>) -> Result {
@@ -24,6 +45,51 @@ pub(super) async fn process(subcommand: PusherCommand, context: &Context<'_>) ->
write!(context, "Query completed in {query_time:?}:\n\n```rs\n{results:#?}\n```")
},
| PusherCommand::DeletePusher { user_id, pusher_id } => {
services.pusher.delete_pusher(&user_id, &pusher_id).await;
write!(context, "Deleted pusher {pusher_id} for {user_id}.")
},
| PusherCommand::DeleteAllUser { user_id } => {
let pushers = services
.pusher
.get_pushkeys(&user_id)
.collect::<Vec<_>>()
.await;
let pusher_count = pushers.len();
pushers
.stream()
.for_each(async |pushkey| {
services.pusher.delete_pusher(&user_id, pushkey).await;
})
.await;
write!(context, "Deleted {pusher_count} pushers for {user_id}.")
},
| PusherCommand::DeleteAllDevice { user_id, device_id } => {
let pushers = services
.pusher
.get_pushkeys(&user_id)
.map(ToOwned::to_owned)
.broad_filter_map(async |pushkey| {
services
.pusher
.get_pusher_device(&pushkey)
.await
.ok()
.as_ref()
.is_some_and(|pusher_device| pusher_device == &device_id)
.then_some(pushkey)
})
.collect::<Vec<_>>()
.await;
let pusher_count = pushers.len();
pushers
.stream()
.for_each(async |pushkey| {
services.pusher.delete_pusher(&user_id, &pushkey).await;
})
.await;
write!(context, "Deleted {pusher_count} pushers for {device_id}.")
},
}
.await
}

View File

@@ -3,7 +3,10 @@
fmt::Write as _,
};
use api::client::{full_user_deactivate, join_room_by_id_helper, leave_room, remote_leave_room};
use api::client::{
full_user_deactivate, join_room_by_id_helper, leave_room, recreate_push_rules_and_return,
remote_leave_room,
};
use conduwuit::{
Err, Result, debug_warn, error, info,
matrix::{Event, pdu::PduBuilder},
@@ -1198,3 +1201,14 @@ pub(super) async fn change_email(&self, user_id: String, email: Option<String>)
},
}
}
#[admin_command]
pub(super) async fn reset_push_rules(&self, user_id: String) -> Result {
let user_id = parse_local_user_id(self.services, &user_id)?;
if !self.services.users.is_active(&user_id).await {
return Err!("User is not active.");
}
recreate_push_rules_and_return(self.services, &user_id).await?;
self.write_str("Reset user's push rules to the server default.")
.await
}

View File

@@ -257,4 +257,10 @@ pub enum UserCommand {
#[arg(long)]
yes_i_want_to_do_this: bool,
},
/// Resets the push-rules (notification settings) of the target user to the
/// server defaults.
ResetPushRules {
user_id: String,
},
}

View File

@@ -362,6 +362,14 @@ pub async fn full_user_deactivate(
})
.await;
services
.pusher
.get_pushkeys(user_id)
.for_each(async |pushkey| {
services.pusher.delete_pusher(user_id, pushkey).await;
})
.await;
// TODO: Rescind all user invites
let mut pdu_queue: Vec<(PduBuilder, &OwnedRoomId)> = Vec::new();

View File

@@ -785,6 +785,15 @@ async fn join_room_by_id_helper_local(
};
if servers.is_empty() || servers.len() == 1 && services.globals.server_is_ours(&servers[0]) {
if !services.rooms.metadata.exists(room_id).await {
return Err!(Request(
Unknown(
"Room was not found locally and no servers were found to help us discover it"
),
NOT_FOUND
));
}
return Err(error);
}

View File

@@ -64,6 +64,7 @@
pub(super) use presence::*;
pub(super) use profile::*;
pub use profile::{update_all_rooms, update_avatar_url, update_displayname};
pub use push::recreate_push_rules_and_return;
pub(super) use push::*;
pub(super) use read_marker::*;
pub(super) use redact::*;

View File

@@ -489,7 +489,7 @@ pub(crate) async fn set_pushers_route(
/// user somehow has bad push rules, these must always exist per spec.
/// so recreate it and return server default silently
async fn recreate_push_rules_and_return(
pub async fn recreate_push_rules_and_return(
services: &Services,
sender_user: &ruma::UserId,
) -> Result<get_pushrules_all::v3::Response> {

View File

@@ -4,7 +4,7 @@
use axum_client_ip::InsecureClientIp;
use conduwuit::{
Err, Error, Result, debug, err, info,
utils::{self, ReadyExt, hash},
utils::{self, ReadyExt, hash, stream::BroadbandExt},
warn,
};
use conduwuit_core::{debug_error, debug_warn};
@@ -414,9 +414,28 @@ pub(crate) async fn logout_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout::v3::Request>,
) -> Result<logout::v3::Response> {
let (sender_user, sender_device) = body.sender();
services
.users
.remove_device(body.sender_user(), body.sender_device())
.remove_device(sender_user, sender_device)
.await;
services
.pusher
.get_pushkeys(sender_user)
.map(ToOwned::to_owned)
.broad_filter_map(async |pushkey| {
services
.pusher
.get_pusher_device(&pushkey)
.await
.ok()
.as_ref()
.is_some_and(|pusher_device| pusher_device == sender_device)
.then_some(pushkey)
})
.for_each(async |pushkey| {
services.pusher.delete_pusher(sender_user, &pushkey).await;
})
.await;
Ok(logout::v3::Response::new())
@@ -441,10 +460,18 @@ pub(crate) async fn logout_all_route(
InsecureClientIp(client): InsecureClientIp,
body: Ruma<logout_all::v3::Request>,
) -> Result<logout_all::v3::Response> {
let sender_user = body.sender_user();
services
.users
.all_device_ids(body.sender_user())
.for_each(|device_id| services.users.remove_device(body.sender_user(), device_id))
.all_device_ids(sender_user)
.for_each(|device_id| services.users.remove_device(sender_user, device_id))
.await;
services
.pusher
.get_pushkeys(sender_user)
.for_each(async |pushkey| {
services.pusher.delete_pusher(sender_user, pushkey).await;
})
.await;
Ok(logout_all::v3::Response::new())

View File

@@ -2,7 +2,7 @@
use conduwuit::{Error, Result};
use ruma::api::client::{
discovery::{
discover_homeserver::{self, HomeserverInfo, SlidingSyncProxyInfo},
discover_homeserver::{self, HomeserverInfo},
discover_support::{self, Contact},
},
error::ErrorKind,
@@ -23,9 +23,9 @@ pub(crate) async fn well_known_client(
};
Ok(discover_homeserver::Response {
homeserver: HomeserverInfo { base_url: client_url.clone() },
homeserver: HomeserverInfo { base_url: client_url },
identity_server: None,
sliding_sync_proxy: Some(SlidingSyncProxyInfo { url: client_url }),
sliding_sync_proxy: None,
tile_server: None,
rtc_foci: services
.config

View File

@@ -77,6 +77,14 @@ macro_rules! err {
)
};
(Request($variant:ident($($args:tt)+), $status_code:ident)) => {
$crate::error::Error::Request(
$crate::ruma::api::client::error::ErrorKind::$variant,
$crate::format_maybe!($($args)+),
$crate::http::StatusCode::$status_code,
)
};
(Config($item:literal, $($args:tt)+)) => {{
let mut buf = String::new();
$crate::error::Error::Config($item, $crate::err_log!(buf, error, config = %$item, $($args)+))

View File

@@ -81,6 +81,11 @@ fn from_evt(
))
}
}
if !self.services.globals.user_is_local(sender) {
return Err!(Request(Forbidden("Sender must be a local user")));
}
let PduBuilder {
event_type,
content,