From 0ba77674c72324b6f533acfc8d2cbf328b9448a0 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sun, 25 May 2025 00:36:28 +0100 Subject: [PATCH 01/18] docs: Security policy --- SECURITY.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/SUMMARY.md | 1 + docs/security.md | 1 + 3 files changed, 61 insertions(+) create mode 100644 SECURITY.md create mode 100644 docs/security.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..c5355491 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,59 @@ +# Security Policy for Continuwuity + +This document outlines the security policy for Continuwuity. Our goal is to maintain a secure platform for all users, and we take security matters seriously. + +## Supported Versions + +We provide security updates for the following versions of Continuwuity: + +| Version | Supported | +| -------------- |:----------------:| +| Latest release | ✅ | +| Main branch | ✅ | +| Older releases | ❌ | + +## Reporting a Vulnerability + +### Responsible Disclosure + +We appreciate the efforts of security researchers and the community in identifying and reporting vulnerabilities. To ensure that potential vulnerabilities are addressed properly, please follow these guidelines: + +1. **Email the security team** directly at [security@continuwuity.org](mailto:security@continuwuity.org) +2. Contact members of the team over E2EE private message. + - [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link) + - [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk) +3. **Do not disclose the vulnerability publicly** until it has been addressed +4. **Provide detailed information** about the vulnerability, including: + - A clear description of the issue + - Steps to reproduce + - Potential impact + - Any possible mitigations + - Version(s) affected, including specific commits if possible + +### What to Expect + +When you report a security vulnerability: + +1. **Acknowledgment**: We will acknowledge receipt of your report. +2. **Assessment**: We will assess the vulnerability and determine its impact on our users +3. **Updates**: We will provide updates on our progress in addressing the vulnerability, and may request you help test mitigations +4. **Resolution**: Once resolved, we will notify you and discuss coordinated disclosure +5. **Credit**: We will recognize your contribution (unless you prefer to remain anonymous) + +## Security Update Process + +When security vulnerabilities are identified: + +1. We will develop and test fixes in a private branch +2. Security updates will be released as soon as possible +3. Release notes will include information about the vulnerabilities, avoiding details that could facilitate exploitation where possible +4. Critical security updates may be backported to the previous stable release + +## Additional Resources + +- [Matrix Security Disclosure Policy](https://matrix.org/security-disclosure-policy/) +- [Continuwuity Documentation](https://continuwuity.org/introduction) + +--- + +This security policy was last updated on May 25, 2025. diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 473c9e74..af729003 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -20,3 +20,4 @@ - [Testing](development/testing.md) - [Hot Reloading ("Live" Development)](development/hot_reload.md) - [Community (and Guidelines)](community.md) +- [Security](security.md) diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 00000000..b4474cf5 --- /dev/null +++ b/docs/security.md @@ -0,0 +1 @@ +{{#include ../SECURITY.md}} From e8d823a65340a891693339dd5a5dd53e6732fb61 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Mon, 26 May 2025 15:01:58 +0100 Subject: [PATCH 02/18] docs: Apply feedback on security policy --- SECURITY.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index c5355491..a9aa183e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -12,16 +12,18 @@ We provide security updates for the following versions of Continuwuity: | Main branch | ✅ | | Older releases | ❌ | +We may backport fixes to the previous release at our discretion, but we don't guarantee this. + ## Reporting a Vulnerability ### Responsible Disclosure We appreciate the efforts of security researchers and the community in identifying and reporting vulnerabilities. To ensure that potential vulnerabilities are addressed properly, please follow these guidelines: -1. **Email the security team** directly at [security@continuwuity.org](mailto:security@continuwuity.org) -2. Contact members of the team over E2EE private message. +1. Contact members of the team over E2EE private message. - [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link) - [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk) +2. **Email the security team** directly at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details. 3. **Do not disclose the vulnerability publicly** until it has been addressed 4. **Provide detailed information** about the vulnerability, including: - A clear description of the issue @@ -30,6 +32,8 @@ We appreciate the efforts of security researchers and the community in identifyi - Any possible mitigations - Version(s) affected, including specific commits if possible +If you have any doubts about a potential security vulnerability, contact us via private channels first! We'd prefer that you bother us, instead of having a vulnerability disclosed without a fix. + ### What to Expect When you report a security vulnerability: From 2b268fdaf3c9a8ff96c7e6d438a754e7d4cdec3d Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 21 May 2025 14:16:58 +0100 Subject: [PATCH 03/18] fix: Allow joining via invite for knock_restricted rooms --- src/core/matrix/state_res/event_auth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/matrix/state_res/event_auth.rs b/src/core/matrix/state_res/event_auth.rs index 715e5156..759ab5cb 100644 --- a/src/core/matrix/state_res/event_auth.rs +++ b/src/core/matrix/state_res/event_auth.rs @@ -638,7 +638,7 @@ fn valid_membership_change( warn!(?target_user_membership_event_id, "Banned user can't join"); false } else if (join_rules == JoinRule::Invite - || room_version.allow_knocking && join_rules == JoinRule::Knock) + || room_version.allow_knocking && (join_rules == JoinRule::Knock || matches!(join_rules, JoinRule::KnockRestricted(_)))) // If the join_rule is invite then allow if membership state is invite or join && (target_user_current_membership == MembershipState::Join || target_user_current_membership == MembershipState::Invite) From 640714922bb058e6b3251ff17eff84bd07b90c5f Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 21 May 2025 14:17:47 +0100 Subject: [PATCH 04/18] feat: For knock_restricted rooms, automatically join rooms we meet restrictions for rather than knocking --- src/api/client/membership.rs | 109 +++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/api/client/membership.rs b/src/api/client/membership.rs index 2847d668..e587d806 100644 --- a/src/api/client/membership.rs +++ b/src/api/client/membership.rs @@ -2162,6 +2162,109 @@ async fn knock_room_by_id_helper( } } + // For knock_restricted rooms, check if the user meets the restricted conditions + // If they do, attempt to join instead of knock + // This is not mentioned in the spec, but should be allowable (we're allowed to + // auto-join invites to knocked rooms) + let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await; + if let JoinRule::KnockRestricted(restricted) = &join_rule { + let restriction_rooms: Vec<_> = restricted + .allow + .iter() + .filter_map(|a| match a { + | AllowRule::RoomMembership(r) => Some(&r.room_id), + | _ => None, + }) + .collect(); + + // Check if the user is in any of the allowed rooms + let mut user_meets_restrictions = false; + for restriction_room_id in &restriction_rooms { + if services + .rooms + .state_cache + .is_joined(sender_user, restriction_room_id) + .await + { + user_meets_restrictions = true; + break; + } + } + + // If the user meets the restrictions, try joining instead + if user_meets_restrictions { + debug_info!( + "{sender_user} meets the restricted criteria in knock_restricted room \ + {room_id}, attempting to join instead of knock" + ); + // For this case, we need to drop the state lock and get a new one in + // join_room_by_id_helper We need to release the lock here and let + // join_room_by_id_helper acquire it again + drop(state_lock); + match join_room_by_id_helper( + services, + sender_user, + room_id, + reason.clone(), + servers, + None, + &None, + ) + .await + { + | Ok(_) => return Ok(knock_room::v3::Response::new(room_id.to_owned())), + | Err(e) => { + debug_warn!( + "Failed to convert knock to join for {sender_user} in {room_id}: {e:?}" + ); + // Get a new state lock for the remaining knock logic + let new_state_lock = services.rooms.state.mutex.lock(room_id).await; + + let server_in_room = services + .rooms + .state_cache + .server_in_room(services.globals.server_name(), room_id) + .await; + + let local_knock = server_in_room + || servers.is_empty() + || (servers.len() == 1 && services.globals.server_is_ours(&servers[0])); + + if local_knock { + knock_room_helper_local( + services, + sender_user, + room_id, + reason, + servers, + new_state_lock, + ) + .boxed() + .await?; + } else { + knock_room_helper_remote( + services, + sender_user, + room_id, + reason, + servers, + new_state_lock, + ) + .boxed() + .await?; + } + + return Ok(knock_room::v3::Response::new(room_id.to_owned())); + }, + } + } + } else if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) { + debug_warn!( + "{sender_user} attempted to knock on room {room_id} but its join rule is \ + {join_rule:?}, not knock or knock_restricted" + ); + } + let server_in_room = services .rooms .state_cache @@ -2209,6 +2312,12 @@ async fn knock_room_helper_local( return Err!(Request(Forbidden("This room does not support knocking."))); } + // Verify that this room has a valid knock or knock_restricted join rule + let join_rule = services.rooms.state_accessor.get_join_rules(room_id).await; + if !matches!(join_rule, JoinRule::Knock | JoinRule::KnockRestricted(_)) { + return Err!(Request(Forbidden("This room's join rule does not allow knocking."))); + } + let content = RoomMemberEventContent { displayname: services.users.displayname(sender_user).await.ok(), avatar_url: services.users.avatar_url(sender_user).await.ok(), From 94ae82414947342ac967bd05c8933d54d9ffc223 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 14:01:16 +0100 Subject: [PATCH 05/18] ci: Don't install rustup if it's already there --- .forgejo/actions/rust-toolchain/action.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.forgejo/actions/rust-toolchain/action.yml b/.forgejo/actions/rust-toolchain/action.yml index 71fb96f5..ae5cfcee 100644 --- a/.forgejo/actions/rust-toolchain/action.yml +++ b/.forgejo/actions/rust-toolchain/action.yml @@ -19,11 +19,20 @@ outputs: rustc_version: description: The rustc version installed value: ${{ steps.rustc-version.outputs.version }} + rustup_version: + description: The rustup version installed + value: ${{ steps.rustup-version.outputs.version }} runs: using: composite steps: + - name: Check if rustup is already installed + shell: bash + id: rustup-version + run: | + echo "version=$(rustup --version)" >> $GITHUB_OUTPUT - name: Cache rustup toolchains + if: steps.rustup-version.outputs.version == '' uses: actions/cache@v3 with: path: | @@ -33,6 +42,7 @@ runs: # Requires repo to be cloned if toolchain is not specified key: ${{ runner.os }}-rustup-${{ inputs.toolchain || hashFiles('**/rust-toolchain.toml') }} - name: Install Rust toolchain + if: steps.rustup-version.outputs.version == '' shell: bash run: | if ! command -v rustup &> /dev/null ; then From b9d60c64e5e3c05a5263955649bc1a0f60b1d172 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 14:07:22 +0100 Subject: [PATCH 06/18] ci: Don't specify container for image builder --- .forgejo/workflows/release-image.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index ec466c58..ec05fb45 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -57,7 +57,6 @@ jobs: build-image: runs-on: dind - container: ghcr.io/catthehacker/ubuntu:act-latest needs: define-variables permissions: contents: read @@ -211,7 +210,6 @@ jobs: merge: runs-on: dind - container: ghcr.io/catthehacker/ubuntu:act-latest needs: [define-variables, build-image] steps: - name: Download digests From ea5dc8e09d11065564f4483a622f82bb12450783 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 23 May 2025 21:10:48 +0100 Subject: [PATCH 07/18] fix: Use correct brand in clap version string --- src/core/mod.rs | 5 ++++- src/main/clap.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/core/mod.rs b/src/core/mod.rs index b91cdf0b..aaacd4d8 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -21,7 +21,10 @@ pub use ::toml; pub use ::tracing; pub use config::Config; pub use error::Error; -pub use info::{rustc_flags_capture, version, version::version}; +pub use info::{ + rustc_flags_capture, version, + version::{name, version}, +}; pub use matrix::{Event, EventTypeExt, PduCount, PduEvent, PduId, RoomVersion, pdu, state_res}; pub use server::Server; pub use utils::{ctor, dtor, implement, result, result::Result}; diff --git a/src/main/clap.rs b/src/main/clap.rs index 9b63af19..a3b2b19a 100644 --- a/src/main/clap.rs +++ b/src/main/clap.rs @@ -15,7 +15,7 @@ use conduwuit_core::{ #[clap( about, long_about = None, - name = "conduwuit", + name = conduwuit_core::name(), version = conduwuit_core::version(), )] pub(crate) struct Args { From b57be072c7fdab5c880fdebaeebe11f02648ac08 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 14:14:06 +0100 Subject: [PATCH 08/18] build: Don't rerun on git changes --- src/build_metadata/build.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/build_metadata/build.rs b/src/build_metadata/build.rs index bfdf20b1..bf84d508 100644 --- a/src/build_metadata/build.rs +++ b/src/build_metadata/build.rs @@ -79,12 +79,12 @@ fn main() { // --- Rerun Triggers --- // TODO: The git rerun triggers seem to always run - // Rerun if the git HEAD changes - println!("cargo:rerun-if-changed=.git/HEAD"); - // Rerun if the ref pointed to by HEAD changes (e.g., new commit on branch) - if let Some(ref_path) = run_git_command(&["symbolic-ref", "--quiet", "HEAD"]) { - println!("cargo:rerun-if-changed=.git/{ref_path}"); - } + // // Rerun if the git HEAD changes + // println!("cargo:rerun-if-changed=.git/HEAD"); + // // Rerun if the ref pointed to by HEAD changes (e.g., new commit on branch) + // if let Some(ref_path) = run_git_command(&["symbolic-ref", "--quiet", "HEAD"]) + // { println!("cargo:rerun-if-changed=.git/{ref_path}"); + // } println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH"); println!("cargo:rerun-if-env-changed=GIT_COMMIT_HASH_SHORT"); From 3c44dccd6555d38c19e46ca8b5ad8acf49d99f52 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Mon, 26 May 2025 19:16:50 +0100 Subject: [PATCH 09/18] ci: HACK, disable saving to actions cache --- .forgejo/workflows/release-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index ec05fb45..92a5b7c4 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -187,7 +187,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} annotations: ${{ steps.meta.outputs.annotations }} cache-from: type=gha - cache-to: type=gha,mode=max + # cache-to: type=gha,mode=max sbom: true outputs: type=image,"name=${{ needs.define-variables.outputs.images_list }}",push-by-digest=true,name-canonical=true,push=true env: From 1d45e0b68cd86a72393bf3cb3f866d5943056018 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 13 Jun 2025 13:39:50 +0100 Subject: [PATCH 10/18] feat: Add warning when admin users will be exposed as support contacts --- src/core/config/check.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/config/check.rs b/src/core/config/check.rs index ded9533d..3dc45e2f 100644 --- a/src/core/config/check.rs +++ b/src/core/config/check.rs @@ -219,6 +219,15 @@ pub fn check(config: &Config) -> Result { )); } + // Check if support contact information is configured + if config.well_known.support_email.is_none() && config.well_known.support_mxid.is_none() { + warn!( + "No support contact information (support_email or support_mxid) is configured in \ + the well_known section. Users in the admin room will be automatically listed as \ + support contacts in the /.well-known/matrix/support endpoint." + ); + } + if config .url_preview_domain_contains_allowlist .contains(&"*".to_owned()) From d7514178ab3aaf594bc2b55bb115842f5ba9f2ca Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 13 Jun 2025 14:29:14 +0100 Subject: [PATCH 11/18] ci: Fix extra bracket in commit shorthash --- .forgejo/workflows/release-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/release-image.yml b/.forgejo/workflows/release-image.yml index 92a5b7c4..55b303b2 100644 --- a/.forgejo/workflows/release-image.yml +++ b/.forgejo/workflows/release-image.yml @@ -180,7 +180,7 @@ jobs: file: "docker/Dockerfile" build-args: | GIT_COMMIT_HASH=${{ github.sha }}) - GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }}) + GIT_COMMIT_HASH_SHORT=${{ env.COMMIT_SHORT_SHA }} GIT_REMOTE_URL=${{github.event.repository.html_url }} GIT_REMOTE_COMMIT_URL=${{github.event.head_commit.url }} platforms: ${{ matrix.platform }} From 44e60d0ea60fec116eb1535239cc8007d73992f0 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Fri, 30 May 2025 23:50:29 +0100 Subject: [PATCH 12/18] docs: Tiny phrasing changes to the security policy --- SECURITY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index a9aa183e..2869ce58 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -20,10 +20,10 @@ We may backport fixes to the previous release at our discretion, but we don't gu We appreciate the efforts of security researchers and the community in identifying and reporting vulnerabilities. To ensure that potential vulnerabilities are addressed properly, please follow these guidelines: -1. Contact members of the team over E2EE private message. +1. **Contact members of the team directly** over E2EE private message. - [@jade:ellis.link](https://matrix.to/#/@jade:ellis.link) - [@nex:nexy7574.co.uk](https://matrix.to/#/@nex:nexy7574.co.uk) -2. **Email the security team** directly at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details. +2. **Email the security team** at [security@continuwuity.org](mailto:security@continuwuity.org). This is not E2EE, so don't include sensitive details. 3. **Do not disclose the vulnerability publicly** until it has been addressed 4. **Provide detailed information** about the vulnerability, including: - A clear description of the issue @@ -48,7 +48,7 @@ When you report a security vulnerability: When security vulnerabilities are identified: -1. We will develop and test fixes in a private branch +1. We will develop and test fixes in a private fork 2. Security updates will be released as soon as possible 3. Release notes will include information about the vulnerabilities, avoiding details that could facilitate exploitation where possible 4. Critical security updates may be backported to the previous stable release From 5d44653e3a9bc278f9dbe3084e2e34a9a0799e1f Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 14 Jun 2025 16:28:57 +0100 Subject: [PATCH 13/18] fix: Incorrect command descriptions --- src/admin/debug/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/admin/debug/mod.rs b/src/admin/debug/mod.rs index 9b86f18c..1fd4e263 100644 --- a/src/admin/debug/mod.rs +++ b/src/admin/debug/mod.rs @@ -125,13 +125,13 @@ pub(super) enum DebugCommand { reset: bool, }, - /// - Verify json signatures + /// - Sign JSON blob /// /// This command needs a JSON blob provided in a Markdown code block below /// the command. SignJson, - /// - Verify json signatures + /// - Verify JSON signatures /// /// This command needs a JSON blob provided in a Markdown code block below /// the command. From 0d8a8bf02e7ae6757df8f7203ac150612aa835ca Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 21 May 2025 22:24:33 +0100 Subject: [PATCH 14/18] feat: Add admin command to delete sync tokens from a room --- src/admin/room/commands.rs | 19 +++++++++++++++++- src/admin/room/mod.rs | 9 ++++++++- src/service/rooms/user/mod.rs | 37 +++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 81f36f15..5b08ff2a 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -1,6 +1,6 @@ use conduwuit::{Err, Result}; use futures::StreamExt; -use ruma::OwnedRoomId; +use ruma::{OwnedRoomId, OwnedRoomOrAliasId}; use crate::{PAGE_SIZE, admin_command, get_room_info}; @@ -66,3 +66,20 @@ pub(super) async fn exists(&self, room_id: OwnedRoomId) -> Result { self.write_str(&format!("{result}")).await } + +#[admin_command] +pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result { + // Resolve the room ID from the room or alias ID + let room_id = self.services.rooms.alias.resolve(&room).await?; + + // Delete all tokens for this room using the service method + let deleted_count = match self.services.rooms.user.delete_room_tokens(&room_id).await { + | Ok(count) => count, + | Err(_) => return Err!("Failed to delete sync tokens for room {}", room_id), + }; + + self.write_str(&format!( + "Successfully deleted {deleted_count} sync tokens for room {room_id}" + )) + .await +} diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 26d2c2d8..0eac2224 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -6,7 +6,7 @@ mod moderation; use clap::Subcommand; use conduwuit::Result; -use ruma::OwnedRoomId; +use ruma::{OwnedRoomId, OwnedRoomOrAliasId}; use self::{ alias::RoomAliasCommand, directory::RoomDirectoryCommand, info::RoomInfoCommand, @@ -56,4 +56,11 @@ pub(super) enum RoomCommand { Exists { room_id: OwnedRoomId, }, + + /// - Delete all sync tokens for a room + PurgeSyncTokens { + /// Room ID or alias to purge sync tokens for + #[arg(value_parser)] + room: OwnedRoomOrAliasId, + }, } diff --git a/src/service/rooms/user/mod.rs b/src/service/rooms/user/mod.rs index bd76f1f4..cc72ac97 100644 --- a/src/service/rooms/user/mod.rs +++ b/src/service/rooms/user/mod.rs @@ -127,3 +127,40 @@ pub async fn get_token_shortstatehash( .await .deserialized() } + +/// Delete all sync tokens associated with a room +/// +/// This helps clean up the database as these tokens are never otherwise removed +#[implement(Service)] +pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result { + use futures::TryStreamExt; + + let shortroomid = self.services.short.get_shortroomid(room_id).await?; + + // Create a prefix to search by - all entries for this room will start with its + // short ID + let prefix = &[shortroomid]; + + // Get all keys with this room prefix + let mut count = 0; + + // Collect all keys into a Vec first, then delete them + let keys = self + .db + .roomsynctoken_shortstatehash + .keys_prefix_raw(prefix) + .map_ok(|key| { + // Clone the key since we can't store references in the Vec + Vec::from(key) + }) + .try_collect::>() + .await?; + + // Delete each key individually + for key in &keys { + self.db.roomsynctoken_shortstatehash.del(key); + count += 1; + } + + Ok(count) +} From a173f7c091a43e8320f13c76f6da9783b9c009d2 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Wed, 21 May 2025 22:44:15 +0100 Subject: [PATCH 15/18] feat: Add command to purge sync tokens for empty rooms --- src/admin/room/commands.rs | 166 ++++++++++++++++++++++++++++++++++ src/admin/room/mod.rs | 23 +++++ src/service/rooms/user/mod.rs | 25 +++++ 3 files changed, 214 insertions(+) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 5b08ff2a..9075389f 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -83,3 +83,169 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result )) .await } + +#[admin_command] +pub(super) async fn purge_empty_room_tokens( + &self, + yes: bool, + target_disabled: bool, + target_banned: bool, + dry_run: bool, +) -> Result { + use conduwuit::{debug, info}; + + if !yes && !dry_run { + return Err!( + "Please confirm this operation with --yes as it may delete tokens from many rooms, \ + or use --dry-run to simulate" + ); + } + + let mode = if dry_run { "Simulating" } else { "Starting" }; + + let mut total_rooms_processed = 0; + let mut empty_rooms_processed = 0; + let mut total_tokens_deleted = 0; + let mut error_count = 0; + let mut skipped_rooms = 0; + + info!("{} purge of sync tokens for rooms with no local users", mode); + + // Get all rooms in the server + let all_rooms = self + .services + .rooms + .metadata + .iter_ids() + .collect::>() + .await; + + info!("Found {} rooms total on the server", all_rooms.len()); + + // Filter rooms based on options + let mut rooms = Vec::new(); + for room_id in all_rooms { + // Filter rooms based on targeting options + let is_disabled = self.services.rooms.metadata.is_disabled(room_id).await; + let is_banned = self.services.rooms.metadata.is_banned(room_id).await; + + // If targeting specific types of rooms, only include matching rooms + if (target_disabled || target_banned) + && !((target_disabled && is_disabled) || (target_banned && is_banned)) + { + debug!("Skipping room {} as it doesn't match targeting criteria", room_id); + skipped_rooms += 1; + continue; + } + + rooms.push(room_id); + } + + // Total number of rooms we'll be checking + let total_rooms = rooms.len(); + info!( + "Processing {} rooms after filtering (skipped {} rooms)", + total_rooms, skipped_rooms + ); + + // Process each room + for room_id in rooms { + total_rooms_processed += 1; + + // Count local users in this room + let local_users_count = self + .services + .rooms + .state_cache + .local_users_in_room(room_id) + .count() + .await; + + // Only process rooms with no local users + if local_users_count == 0 { + empty_rooms_processed += 1; + + // In dry run mode, just count what would be deleted, don't actually delete + debug!( + "Room {} has no local users, {}", + room_id, + if dry_run { + "would purge sync tokens" + } else { + "purging sync tokens" + } + ); + + if dry_run { + // For dry run mode, count tokens without deleting + match self.services.rooms.user.count_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Would delete {} sync tokens for room {}", count, room_id); + total_tokens_deleted += count; + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error counting sync tokens for room {}: {:?}", room_id, e); + error_count += 1; + }, + } + } else { + // Real deletion mode + match self.services.rooms.user.delete_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Deleted {} sync tokens for room {}", count, room_id); + total_tokens_deleted += count; + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error purging sync tokens for room {}: {:?}", room_id, e); + error_count += 1; + }, + } + } + } else { + debug!("Room {} has {} local users, skipping", room_id, local_users_count); + } + + // Log progress periodically + if total_rooms_processed % 100 == 0 || total_rooms_processed == total_rooms { + info!( + "Progress: {}/{} rooms processed, {} empty rooms found, {} tokens {}", + total_rooms_processed, + total_rooms, + empty_rooms_processed, + total_tokens_deleted, + if dry_run { "would be deleted" } else { "deleted" } + ); + } + } + + let action = if dry_run { "would be deleted" } else { "deleted" }; + info!( + "Finished {}: processed {} empty rooms out of {} total, {} tokens {}, errors: {}", + if dry_run { + "purge simulation" + } else { + "purging sync tokens" + }, + empty_rooms_processed, + total_rooms, + total_tokens_deleted, + action, + error_count + ); + + let mode_msg = if dry_run { "DRY RUN: " } else { "" }; + self.write_str(&format!( + "{}Successfully processed {empty_rooms_processed} empty rooms (out of {total_rooms} \ + total rooms), {total_tokens_deleted} tokens {}. Skipped {skipped_rooms} rooms based on \ + filters. Failed for {error_count} rooms.", + mode_msg, + if dry_run { "would be deleted" } else { "deleted" } + )) + .await +} diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 0eac2224..61114b90 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -63,4 +63,27 @@ pub(super) enum RoomCommand { #[arg(value_parser)] room: OwnedRoomOrAliasId, }, + + /// - Delete sync tokens for all rooms that have no local users + /// + /// By default, processes all empty rooms. You can use --target-disabled + /// and/or --target-banned to exclusively process rooms matching those + /// conditions. + PurgeEmptyRoomTokens { + /// Confirm you want to delete tokens from potentially many rooms + #[arg(long)] + yes: bool, + + /// Only purge rooms that have federation disabled + #[arg(long)] + target_disabled: bool, + + /// Only purge rooms that have been banned + #[arg(long)] + target_banned: bool, + + /// Perform a dry run without actually deleting any tokens + #[arg(long)] + dry_run: bool, + }, } diff --git a/src/service/rooms/user/mod.rs b/src/service/rooms/user/mod.rs index cc72ac97..58df427b 100644 --- a/src/service/rooms/user/mod.rs +++ b/src/service/rooms/user/mod.rs @@ -128,6 +128,31 @@ pub async fn get_token_shortstatehash( .deserialized() } +/// Count how many sync tokens exist for a room without deleting them +/// +/// This is useful for dry runs to see how many tokens would be deleted +#[implement(Service)] +pub async fn count_room_tokens(&self, room_id: &RoomId) -> Result { + use futures::TryStreamExt; + + let shortroomid = self.services.short.get_shortroomid(room_id).await?; + + // Create a prefix to search by - all entries for this room will start with its + // short ID + let prefix = &[shortroomid]; + + // Collect all keys into a Vec and count them + let keys = self + .db + .roomsynctoken_shortstatehash + .keys_prefix_raw(prefix) + .map_ok(|_| ()) // We only need to count, not store the keys + .try_collect::>() + .await?; + + Ok(keys.len()) +} + /// Delete all sync tokens associated with a room /// /// This helps clean up the database as these tokens are never otherwise removed From ae3858163fa13dc6f20e5cc124b3b273ebcecee7 Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 13:49:22 +0100 Subject: [PATCH 16/18] chore: Fix more complicated clippy warnings --- src/admin/room/commands.rs | 73 ++++++++++++++++++++++------------- src/admin/room/mod.rs | 15 +++---- src/service/rooms/user/mod.rs | 6 +-- 3 files changed, 53 insertions(+), 41 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 9075389f..5e25ec7a 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -73,9 +73,8 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result let room_id = self.services.rooms.alias.resolve(&room).await?; // Delete all tokens for this room using the service method - let deleted_count = match self.services.rooms.user.delete_room_tokens(&room_id).await { - | Ok(count) => count, - | Err(_) => return Err!("Failed to delete sync tokens for room {}", room_id), + let Ok(deleted_count) = self.services.rooms.user.delete_room_tokens(&room_id).await else { + return Err!("Failed to delete sync tokens for room {}", room_id); }; self.write_str(&format!( @@ -84,12 +83,23 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result .await } +/// Target options for room purging +#[derive(Default, Debug, clap::ValueEnum, Clone)] +pub(crate) enum RoomTargetOption { + #[default] + /// Target all rooms + All, + /// Target only disabled rooms + DisabledOnly, + /// Target only banned rooms + BannedOnly, +} + #[admin_command] pub(super) async fn purge_empty_room_tokens( &self, yes: bool, - target_disabled: bool, - target_banned: bool, + target_option: Option, dry_run: bool, ) -> Result { use conduwuit::{debug, info}; @@ -103,11 +113,13 @@ pub(super) async fn purge_empty_room_tokens( let mode = if dry_run { "Simulating" } else { "Starting" }; - let mut total_rooms_processed = 0; - let mut empty_rooms_processed = 0; - let mut total_tokens_deleted = 0; - let mut error_count = 0; - let mut skipped_rooms = 0; + // strictly, we should check if these reach the max value after the loop and + // warn the user that the count is too large + let mut total_rooms_processed: usize = 0; + let mut empty_rooms_processed: u32 = 0; + let mut total_tokens_deleted: usize = 0; + let mut error_count: u32 = 0; + let mut skipped_rooms: u32 = 0; info!("{} purge of sync tokens for rooms with no local users", mode); @@ -125,17 +137,24 @@ pub(super) async fn purge_empty_room_tokens( // Filter rooms based on options let mut rooms = Vec::new(); for room_id in all_rooms { - // Filter rooms based on targeting options - let is_disabled = self.services.rooms.metadata.is_disabled(room_id).await; - let is_banned = self.services.rooms.metadata.is_banned(room_id).await; - - // If targeting specific types of rooms, only include matching rooms - if (target_disabled || target_banned) - && !((target_disabled && is_disabled) || (target_banned && is_banned)) - { - debug!("Skipping room {} as it doesn't match targeting criteria", room_id); - skipped_rooms += 1; - continue; + if let Some(target) = &target_option { + match target { + | RoomTargetOption::DisabledOnly => { + if !self.services.rooms.metadata.is_disabled(room_id).await { + debug!("Skipping room {} as it's not disabled", room_id); + skipped_rooms = skipped_rooms.saturating_add(1); + continue; + } + }, + | RoomTargetOption::BannedOnly => { + if !self.services.rooms.metadata.is_banned(room_id).await { + debug!("Skipping room {} as it's not banned", room_id); + skipped_rooms = skipped_rooms.saturating_add(1); + continue; + } + }, + | RoomTargetOption::All => {}, + } } rooms.push(room_id); @@ -150,7 +169,7 @@ pub(super) async fn purge_empty_room_tokens( // Process each room for room_id in rooms { - total_rooms_processed += 1; + total_rooms_processed = total_rooms_processed.saturating_add(1); // Count local users in this room let local_users_count = self @@ -163,7 +182,7 @@ pub(super) async fn purge_empty_room_tokens( // Only process rooms with no local users if local_users_count == 0 { - empty_rooms_processed += 1; + empty_rooms_processed = empty_rooms_processed.saturating_add(1); // In dry run mode, just count what would be deleted, don't actually delete debug!( @@ -182,13 +201,13 @@ pub(super) async fn purge_empty_room_tokens( | Ok(count) => if count > 0 { debug!("Would delete {} sync tokens for room {}", count, room_id); - total_tokens_deleted += count; + total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { debug!("No sync tokens found for room {}", room_id); }, | Err(e) => { debug!("Error counting sync tokens for room {}: {:?}", room_id, e); - error_count += 1; + error_count = error_count.saturating_add(1); }, } } else { @@ -197,13 +216,13 @@ pub(super) async fn purge_empty_room_tokens( | Ok(count) => if count > 0 { debug!("Deleted {} sync tokens for room {}", count, room_id); - total_tokens_deleted += count; + total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { debug!("No sync tokens found for room {}", room_id); }, | Err(e) => { debug!("Error purging sync tokens for room {}: {:?}", room_id, e); - error_count += 1; + error_count = error_count.saturating_add(1); }, } } diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 61114b90..1b4650c3 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -5,6 +5,7 @@ mod info; mod moderation; use clap::Subcommand; +use commands::RoomTargetOption; use conduwuit::Result; use ruma::{OwnedRoomId, OwnedRoomOrAliasId}; @@ -66,21 +67,15 @@ pub(super) enum RoomCommand { /// - Delete sync tokens for all rooms that have no local users /// - /// By default, processes all empty rooms. You can use --target-disabled - /// and/or --target-banned to exclusively process rooms matching those - /// conditions. + /// By default, processes all empty rooms. PurgeEmptyRoomTokens { /// Confirm you want to delete tokens from potentially many rooms #[arg(long)] yes: bool, - /// Only purge rooms that have federation disabled - #[arg(long)] - target_disabled: bool, - - /// Only purge rooms that have been banned - #[arg(long)] - target_banned: bool, + /// Target specific room types + #[arg(long, value_enum)] + target_option: Option, /// Perform a dry run without actually deleting any tokens #[arg(long)] diff --git a/src/service/rooms/user/mod.rs b/src/service/rooms/user/mod.rs index 58df427b..aaf735c1 100644 --- a/src/service/rooms/user/mod.rs +++ b/src/service/rooms/user/mod.rs @@ -166,9 +166,6 @@ pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result { // short ID let prefix = &[shortroomid]; - // Get all keys with this room prefix - let mut count = 0; - // Collect all keys into a Vec first, then delete them let keys = self .db @@ -184,8 +181,9 @@ pub async fn delete_room_tokens(&self, room_id: &RoomId) -> Result { // Delete each key individually for key in &keys { self.db.roomsynctoken_shortstatehash.del(key); - count += 1; } + let count = keys.len(); + Ok(count) } From 986876c812469d64cfece807a2571536669d5aaf Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Thu, 22 May 2025 15:28:46 +0100 Subject: [PATCH 17/18] chore: cleanup --- src/admin/room/commands.rs | 155 ++++++++++++++++--------------------- src/admin/room/mod.rs | 11 +-- 2 files changed, 69 insertions(+), 97 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 5e25ec7a..2a4e60ac 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -96,32 +96,23 @@ pub(crate) enum RoomTargetOption { } #[admin_command] -pub(super) async fn purge_empty_room_tokens( +pub(super) async fn purge_all_sync_tokens( &self, - yes: bool, target_option: Option, - dry_run: bool, + execute: bool, ) -> Result { use conduwuit::{debug, info}; - if !yes && !dry_run { - return Err!( - "Please confirm this operation with --yes as it may delete tokens from many rooms, \ - or use --dry-run to simulate" - ); - } - - let mode = if dry_run { "Simulating" } else { "Starting" }; + let mode = if !execute { "Simulating" } else { "Starting" }; // strictly, we should check if these reach the max value after the loop and // warn the user that the count is too large - let mut total_rooms_processed: usize = 0; - let mut empty_rooms_processed: u32 = 0; + let mut total_rooms_checked: usize = 0; let mut total_tokens_deleted: usize = 0; let mut error_count: u32 = 0; - let mut skipped_rooms: u32 = 0; + let mut skipped_rooms: usize = 0; - info!("{} purge of sync tokens for rooms with no local users", mode); + info!("{} purge of sync tokens", mode); // Get all rooms in the server let all_rooms = self @@ -169,102 +160,86 @@ pub(super) async fn purge_empty_room_tokens( // Process each room for room_id in rooms { - total_rooms_processed = total_rooms_processed.saturating_add(1); - - // Count local users in this room - let local_users_count = self - .services - .rooms - .state_cache - .local_users_in_room(room_id) - .count() - .await; - - // Only process rooms with no local users - if local_users_count == 0 { - empty_rooms_processed = empty_rooms_processed.saturating_add(1); - - // In dry run mode, just count what would be deleted, don't actually delete - debug!( - "Room {} has no local users, {}", - room_id, - if dry_run { - "would purge sync tokens" - } else { - "purging sync tokens" - } - ); - - if dry_run { - // For dry run mode, count tokens without deleting - match self.services.rooms.user.count_room_tokens(room_id).await { - | Ok(count) => - if count > 0 { - debug!("Would delete {} sync tokens for room {}", count, room_id); - total_tokens_deleted = total_tokens_deleted.saturating_add(count); - } else { - debug!("No sync tokens found for room {}", room_id); - }, - | Err(e) => { - debug!("Error counting sync tokens for room {}: {:?}", room_id, e); - error_count = error_count.saturating_add(1); - }, - } - } else { - // Real deletion mode - match self.services.rooms.user.delete_room_tokens(room_id).await { - | Ok(count) => - if count > 0 { - debug!("Deleted {} sync tokens for room {}", count, room_id); - total_tokens_deleted = total_tokens_deleted.saturating_add(count); - } else { - debug!("No sync tokens found for room {}", room_id); - }, - | Err(e) => { - debug!("Error purging sync tokens for room {}: {:?}", room_id, e); - error_count = error_count.saturating_add(1); - }, - } - } - } else { - debug!("Room {} has {} local users, skipping", room_id, local_users_count); - } + total_rooms_checked = total_rooms_checked.saturating_add(1); // Log progress periodically - if total_rooms_processed % 100 == 0 || total_rooms_processed == total_rooms { + if total_rooms_checked % 100 == 0 || total_rooms_checked == total_rooms { info!( - "Progress: {}/{} rooms processed, {} empty rooms found, {} tokens {}", - total_rooms_processed, + "Progress: {}/{} rooms checked, {} tokens {}", + total_rooms_checked, total_rooms, - empty_rooms_processed, total_tokens_deleted, - if dry_run { "would be deleted" } else { "deleted" } + if !execute { "would be deleted" } else { "deleted" } ); } + + // In dry run mode, just count what would be deleted, don't actually delete + debug!( + "Room {} has no local users, {}", + room_id, + if !execute { + "would purge sync tokens" + } else { + "purging sync tokens" + } + ); + + if !execute { + // For dry run mode, count tokens without deleting + match self.services.rooms.user.count_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Would delete {} sync tokens for room {}", count, room_id); + total_tokens_deleted = total_tokens_deleted.saturating_add(count); + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error counting sync tokens for room {}: {:?}", room_id, e); + error_count = error_count.saturating_add(1); + }, + } + } else { + // Real deletion mode + match self.services.rooms.user.delete_room_tokens(room_id).await { + | Ok(count) => + if count > 0 { + debug!("Deleted {} sync tokens for room {}", count, room_id); + total_tokens_deleted = total_tokens_deleted.saturating_add(count); + } else { + debug!("No sync tokens found for room {}", room_id); + }, + | Err(e) => { + debug!("Error purging sync tokens for room {}: {:?}", room_id, e); + error_count = error_count.saturating_add(1); + }, + } + } } - let action = if dry_run { "would be deleted" } else { "deleted" }; + let action = if !execute { "would be deleted" } else { "deleted" }; info!( - "Finished {}: processed {} empty rooms out of {} total, {} tokens {}, errors: {}", - if dry_run { + "Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}", + if !execute { "purge simulation" } else { "purging sync tokens" }, - empty_rooms_processed, + total_rooms_checked, total_rooms, total_tokens_deleted, action, error_count ); - let mode_msg = if dry_run { "DRY RUN: " } else { "" }; self.write_str(&format!( - "{}Successfully processed {empty_rooms_processed} empty rooms (out of {total_rooms} \ - total rooms), {total_tokens_deleted} tokens {}. Skipped {skipped_rooms} rooms based on \ - filters. Failed for {error_count} rooms.", - mode_msg, - if dry_run { "would be deleted" } else { "deleted" } + "Finished {}: checked {} rooms out of {} total, {} tokens {}, errors: {}", + if !execute { "simulation" } else { "purging sync tokens" }, + total_rooms_checked, + total_rooms, + total_tokens_deleted, + action, + error_count )) .await } diff --git a/src/admin/room/mod.rs b/src/admin/room/mod.rs index 1b4650c3..fe44dec4 100644 --- a/src/admin/room/mod.rs +++ b/src/admin/room/mod.rs @@ -68,17 +68,14 @@ pub(super) enum RoomCommand { /// - Delete sync tokens for all rooms that have no local users /// /// By default, processes all empty rooms. - PurgeEmptyRoomTokens { - /// Confirm you want to delete tokens from potentially many rooms - #[arg(long)] - yes: bool, - + PurgeAllSyncTokens { /// Target specific room types #[arg(long, value_enum)] target_option: Option, - /// Perform a dry run without actually deleting any tokens + /// Execute token deletions. Otherwise, + /// Performs a dry run without actually deleting any tokens #[arg(long)] - dry_run: bool, + execute: bool, }, } From 12e2706effe034a8cf956985edea93040f88316f Mon Sep 17 00:00:00 2001 From: Jade Ellis Date: Sat, 24 May 2025 21:48:28 +0100 Subject: [PATCH 18/18] fix: Use to_str methods on room IDs --- src/admin/room/commands.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/admin/room/commands.rs b/src/admin/room/commands.rs index 2a4e60ac..c03c5101 100644 --- a/src/admin/room/commands.rs +++ b/src/admin/room/commands.rs @@ -74,11 +74,12 @@ pub(super) async fn purge_sync_tokens(&self, room: OwnedRoomOrAliasId) -> Result // Delete all tokens for this room using the service method let Ok(deleted_count) = self.services.rooms.user.delete_room_tokens(&room_id).await else { - return Err!("Failed to delete sync tokens for room {}", room_id); + return Err!("Failed to delete sync tokens for room {}", room_id.as_str()); }; self.write_str(&format!( - "Successfully deleted {deleted_count} sync tokens for room {room_id}" + "Successfully deleted {deleted_count} sync tokens for room {}", + room_id.as_str() )) .await } @@ -132,14 +133,14 @@ pub(super) async fn purge_all_sync_tokens( match target { | RoomTargetOption::DisabledOnly => { if !self.services.rooms.metadata.is_disabled(room_id).await { - debug!("Skipping room {} as it's not disabled", room_id); + debug!("Skipping room {} as it's not disabled", room_id.as_str()); skipped_rooms = skipped_rooms.saturating_add(1); continue; } }, | RoomTargetOption::BannedOnly => { if !self.services.rooms.metadata.is_banned(room_id).await { - debug!("Skipping room {} as it's not banned", room_id); + debug!("Skipping room {} as it's not banned", room_id.as_str()); skipped_rooms = skipped_rooms.saturating_add(1); continue; } @@ -175,8 +176,8 @@ pub(super) async fn purge_all_sync_tokens( // In dry run mode, just count what would be deleted, don't actually delete debug!( - "Room {} has no local users, {}", - room_id, + "Room {}: {}", + room_id.as_str(), if !execute { "would purge sync tokens" } else { @@ -189,13 +190,17 @@ pub(super) async fn purge_all_sync_tokens( match self.services.rooms.user.count_room_tokens(room_id).await { | Ok(count) => if count > 0 { - debug!("Would delete {} sync tokens for room {}", count, room_id); + debug!( + "Would delete {} sync tokens for room {}", + count, + room_id.as_str() + ); total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { - debug!("No sync tokens found for room {}", room_id); + debug!("No sync tokens found for room {}", room_id.as_str()); }, | Err(e) => { - debug!("Error counting sync tokens for room {}: {:?}", room_id, e); + debug!("Error counting sync tokens for room {}: {:?}", room_id.as_str(), e); error_count = error_count.saturating_add(1); }, } @@ -204,13 +209,13 @@ pub(super) async fn purge_all_sync_tokens( match self.services.rooms.user.delete_room_tokens(room_id).await { | Ok(count) => if count > 0 { - debug!("Deleted {} sync tokens for room {}", count, room_id); + debug!("Deleted {} sync tokens for room {}", count, room_id.as_str()); total_tokens_deleted = total_tokens_deleted.saturating_add(count); } else { - debug!("No sync tokens found for room {}", room_id); + debug!("No sync tokens found for room {}", room_id.as_str()); }, | Err(e) => { - debug!("Error purging sync tokens for room {}: {:?}", room_id, e); + debug!("Error purging sync tokens for room {}: {:?}", room_id.as_str(), e); error_count = error_count.saturating_add(1); }, }