diff --git a/.forgejo/workflows/build-fedora.yml b/.forgejo/workflows/build-fedora.yml index 60404896..c101ed04 100644 --- a/.forgejo/workflows/build-fedora.yml +++ b/.forgejo/workflows/build-fedora.yml @@ -81,7 +81,7 @@ jobs: # Aggressive GC since cache restores don't increment counter echo "CARGO_INCREMENTAL_GC_TRIGGER=5" >> $GITHUB_ENV - - name: Install build dependencies + - name: Install base RPM tools run: | dnf install -y --setopt=keepcache=1 \ wget \ @@ -91,25 +91,14 @@ jobs: rpkg \ cargo-rpm-macros \ systemd-rpm-macros \ - clang \ - liburing-devel \ - rust \ - cargo \ - gcc \ - gcc-c++ \ - make \ - openssl-devel \ - pkg-config \ python3-pip - name: Setup build environment and build SRPM run: | - # Configure git for rpkg git config --global --add safe.directory "$GITHUB_WORKSPACE" git config --global user.email "ci@continuwuity.org" git config --global user.name "Continuwuity" - # Setup RPM build tree rpmdev-setuptree cd "$GITHUB_WORKSPACE" @@ -143,10 +132,8 @@ jobs: fedora/continuwuity.spec.rpkg > continuwuity.spec.rpkg fi - # Build the SRPM rpkg srpm --outdir "$HOME/rpmbuild/SRPMS" - # Show SRPM info ls -la $HOME/rpmbuild/SRPMS/ - name: Setup GPG for RPM signing @@ -157,23 +144,32 @@ jobs: exit 0 fi - # Import the signing key echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import # Get the key ID (look for the sec line, not the uid line) KEY_ID=$(gpg --list-secret-keys --keyid-format=long | grep "^sec" | head -1 | awk '{print $2}' | cut -d'/' -f2) echo "Using GPG key: $KEY_ID" - # Configure RPM macros for signing cat > ~/.rpmmacros << EOF %_signature gpg %_gpg_name $KEY_ID %__gpg /usr/bin/gpg EOF + - name: Install build dependencies from SRPM + run: | + SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) + + if [ -z "$SRPM" ]; then + echo "Error: No SRPM file found" + exit 1 + fi + + echo "Installing build dependencies from: $(basename $SRPM)" + dnf builddep -y "$SRPM" + - name: Build RPM from SRPM run: | - # Find the SRPM file SRPM=$(find "$HOME/rpmbuild/SRPMS" -name "*.src.rpm" | head -1) if [ -z "$SRPM" ]; then @@ -183,7 +179,6 @@ jobs: echo "Building from SRPM: $SRPM" - # Build the binary RPM rpmbuild --rebuild "$SRPM" \ --define "_topdir $HOME/rpmbuild" \ --define "_sourcedir $GITHUB_WORKSPACE" \ @@ -197,32 +192,20 @@ jobs: exit 0 fi - # Track signing failures - FAILED_COUNT=0 - TOTAL_COUNT=0 - # Export GPG_TTY to avoid terminal warnings export GPG_TTY=/dev/null - # Sign all RPMs (binary and source) for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do echo "Signing: $(basename $rpm)" - TOTAL_COUNT=$((TOTAL_COUNT + 1)) # Use expect or provide empty passphrase via stdin for batch signing if ! echo "" | rpmsign --addsign "$rpm" 2>&1; then echo "ERROR: Failed to sign $rpm" - FAILED_COUNT=$((FAILED_COUNT + 1)) + exit 1 fi done - # Fail if any RPMs failed to sign - if [ "$FAILED_COUNT" -gt 0 ]; then - echo "ERROR: Failed to sign $FAILED_COUNT out of $TOTAL_COUNT RPMs" - exit 1 - fi - - echo "Successfully signed all $TOTAL_COUNT RPMs" + echo "Successfully signed all RPMs" - name: Verify RPM signatures run: | @@ -232,15 +215,12 @@ jobs: exit 0 fi - # Import our public key for verification echo "Importing GPG public key for verification..." rpm --import fedora/RPM-GPG-KEY-continuwuity.asc - # Track verification failures FAILED_COUNT=0 TOTAL_COUNT=0 - # Verify all RPMs for rpm in $(find "$HOME/rpmbuild" -name "*.rpm" -type f); do echo -n "Verifying $(basename $rpm): " TOTAL_COUNT=$((TOTAL_COUNT + 1)) @@ -281,8 +261,8 @@ jobs: echo "" rpm -qpl "$RPM" - # Actually install it (would need --nodeps if dependencies aren't met) - dnf install -y "$RPM" || rpm -ivh --nodeps "$RPM" + # Actually install it + dnf install -y "$RPM" # Verify installation rpm -qa | grep continuwuity @@ -305,11 +285,9 @@ jobs: run: | mkdir -p artifacts - # Copy all RPMs to artifacts directory find "$HOME/rpmbuild/RPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; find "$HOME/rpmbuild/SRPMS" -name "*.rpm" -type f -exec cp {} artifacts/ \; - # Create metadata file cd artifacts echo "Build Information:" > BUILD_INFO.txt echo "==================" >> BUILD_INFO.txt @@ -336,7 +314,6 @@ jobs: ! -name "*.src.rpm" \ -type f) - # Create temp directory for this artifact mkdir -p upload-bin cp $BIN_RPM upload-bin/ @@ -367,7 +344,6 @@ jobs: exit 0 fi - # Extract version from RPM filename RPM_BASENAME=$(basename "$RPM") echo "Publishing: $RPM_BASENAME" @@ -383,7 +359,6 @@ jobs: GROUP=$(echo "${{ github.ref_name }}" | sed 's/[^a-zA-Z0-9]/-/g' | tr '[:upper:]' '[:lower:]' | cut -c1-30) fi - # Extract package info from RPM for deletion PACKAGE_INFO=$(rpm -qpi "$RPM" 2>/dev/null) PACKAGE_NAME=$(echo "$PACKAGE_INFO" | grep "^Name" | awk '{print $3}') PACKAGE_VERSION=$(echo "$PACKAGE_INFO" | grep "^Version" | awk '{print $3}') @@ -393,15 +368,20 @@ jobs: # Full version includes release FULL_VERSION="${PACKAGE_VERSION}-${PACKAGE_RELEASE}" - # Try to delete existing package first (ignore errors if it doesn't exist) + # Forgejo's RPM registry cannot overwrite existing packages, so we must delete first + # 404 is OK if package doesn't exist yet echo "Removing any existing package: $PACKAGE_NAME-$FULL_VERSION.$PACKAGE_ARCH" - curl -X DELETE \ + RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ - "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH" \ - || echo "Package didn't exist or deletion failed (this is OK)" + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/$GROUP/package/$PACKAGE_NAME/$FULL_VERSION/$PACKAGE_ARCH") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then + echo "ERROR: Failed to delete package (HTTP $HTTP_CODE)" + echo "$RESPONSE" | head -n -1 + exit 1 + fi - # Upload to Forgejo package registry - # Using the RPM registry endpoint with group support curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ @@ -422,7 +402,6 @@ jobs: for DEBUG_RPM in $DEBUG_RPMS; do echo "Publishing: $(basename "$DEBUG_RPM")" - # Extract debug RPM info DEBUG_INFO=$(rpm -qpi "$DEBUG_RPM" 2>/dev/null) DEBUG_NAME=$(echo "$DEBUG_INFO" | grep "^Name" | awk '{print $3}') DEBUG_VERSION=$(echo "$DEBUG_INFO" | grep "^Version" | awk '{print $3}') @@ -430,13 +409,18 @@ jobs: DEBUG_ARCH=$(echo "$DEBUG_INFO" | grep "^Architecture" | awk '{print $2}') DEBUG_FULL_VERSION="${DEBUG_VERSION}-${DEBUG_RELEASE}" - # Try to delete existing debug package first - curl -X DELETE \ + # Must delete existing package first (Forgejo limitation) + RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ - "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH" \ - || echo "Debug package didn't exist or deletion failed (this is OK)" + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-debug/package/$DEBUG_NAME/$DEBUG_FULL_VERSION/$DEBUG_ARCH") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then + echo "ERROR: Failed to delete debug package (HTTP $HTTP_CODE)" + echo "$RESPONSE" | head -n -1 + exit 1 + fi - # Upload debug RPM curl --fail-with-body \ -X PUT \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ @@ -455,19 +439,24 @@ jobs: echo "Publishing source RPM: $(basename "$SRPM")" echo "Publishing to group: ${GROUP}-src" - # Extract SRPM info SRPM_INFO=$(rpm -qpi "$SRPM" 2>/dev/null) SRPM_NAME=$(echo "$SRPM_INFO" | grep "^Name" | awk '{print $3}') SRPM_VERSION=$(echo "$SRPM_INFO" | grep "^Version" | awk '{print $3}') SRPM_RELEASE=$(echo "$SRPM_INFO" | grep "^Release" | awk '{print $3}') SRPM_FULL_VERSION="${SRPM_VERSION}-${SRPM_RELEASE}" - # Try to delete existing SRPM first + # Must delete existing SRPM first (Forgejo limitation) echo "Removing any existing SRPM: $SRPM_NAME-$SRPM_FULL_VERSION.src" - curl -X DELETE \ + RESPONSE=$(curl -s -w "\n%{http_code}" -X DELETE \ -H "Authorization: token ${{ secrets.BUILTIN_REGISTRY_PASSWORD || secrets.GITHUB_TOKEN }}" \ - "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src" \ - || echo "SRPM didn't exist or deletion failed (this is OK)" + "https://forgejo.ellis.link/api/packages/continuwuation/rpm/${GROUP}-src/package/$SRPM_NAME/$SRPM_FULL_VERSION/src") + HTTP_CODE=$(echo "$RESPONSE" | tail -n1) + + if [ "$HTTP_CODE" != "204" ] && [ "$HTTP_CODE" != "404" ]; then + echo "ERROR: Failed to delete SRPM (HTTP $HTTP_CODE)" + echo "$RESPONSE" | head -n -1 + exit 1 + fi curl --fail-with-body \ -X PUT \ diff --git a/.mailmap b/.mailmap index fa267e13..7c25737d 100644 --- a/.mailmap +++ b/.mailmap @@ -13,3 +13,4 @@ Rudi Floren Tamara Schmitz <15906939+tamara-schmitz@users.noreply.github.com> Timo Kösters x4u <14617923-x4u@users.noreply.gitlab.com> +Ginger <75683114+gingershaped@users.noreply.github.com> diff --git a/Cargo.lock b/Cargo.lock index 2b044a1f..a3962c31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4058,7 +4058,7 @@ checksum = "88f8660c1ff60292143c98d08fc6e2f654d722db50410e3f3797d40baaf9d8f3" [[package]] name = "ruma" version = "0.10.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "assign", "js_int", @@ -4078,7 +4078,7 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.10.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "ruma-common", @@ -4090,7 +4090,7 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.18.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "as_variant", "assign", @@ -4113,7 +4113,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "as_variant", "base64 0.22.1", @@ -4145,7 +4145,7 @@ dependencies = [ [[package]] name = "ruma-events" version = "0.28.1" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "as_variant", "indexmap 2.10.0", @@ -4170,7 +4170,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "bytes", "headers", @@ -4192,7 +4192,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.5" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "thiserror 2.0.12", @@ -4201,7 +4201,7 @@ dependencies = [ [[package]] name = "ruma-identity-service-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "ruma-common", @@ -4211,7 +4211,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.13.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "cfg-if", "proc-macro-crate", @@ -4226,7 +4226,7 @@ dependencies = [ [[package]] name = "ruma-push-gateway-api" version = "0.9.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "js_int", "ruma-common", @@ -4238,7 +4238,7 @@ dependencies = [ [[package]] name = "ruma-signatures" version = "0.15.0" -source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=b753738047d1f443aca870896ef27ecaacf027da#b753738047d1f443aca870896ef27ecaacf027da" +source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd#8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" dependencies = [ "base64 0.22.1", "ed25519-dalek", diff --git a/Cargo.toml b/Cargo.toml index 9452066c..e2af2d94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -352,7 +352,7 @@ version = "0.1.2" [workspace.dependencies.ruma] git = "https://forgejo.ellis.link/continuwuation/ruwuma" #branch = "conduwuit-changes" -rev = "b753738047d1f443aca870896ef27ecaacf027da" +rev = "8fb268fa2771dfc3a1c8075ef1246e7c9a0a53fd" features = [ "compat", "rand", diff --git a/arch/conduwuit.service b/arch/conduwuit.service index 34c3995e..18c34f33 100644 --- a/arch/conduwuit.service +++ b/arch/conduwuit.service @@ -20,6 +20,7 @@ StandardError=journal+console Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" +Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit" TTYReset=yes # uncomment to allow buffer to be cleared every restart diff --git a/conduwuit-example.toml b/conduwuit-example.toml index f0e510b4..fa65cbf2 100644 --- a/conduwuit-example.toml +++ b/conduwuit-example.toml @@ -79,9 +79,11 @@ # This is the only directory where continuwuity will save its data, # including media. Note: this was previously "/var/lib/matrix-conduit". # -# YOU NEED TO EDIT THIS. +# YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service. +# The service file sets it to `/var/lib/conduwuit` using an environment variable +# and also grants write access. # -# example: "/var/lib/continuwuity" +# example: "/var/lib/conduwuit" # #database_path = diff --git a/debian/conduwuit.service b/debian/conduwuit.service index da78f09f..ec2505b5 100644 --- a/debian/conduwuit.service +++ b/debian/conduwuit.service @@ -16,6 +16,7 @@ Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" +Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit" ExecStart=/usr/sbin/conduwuit diff --git a/docs/admin_reference.md b/docs/admin_reference.md index 18e039e4..1c9d4fb0 100644 --- a/docs/admin_reference.md +++ b/docs/admin_reference.md @@ -21,6 +21,7 @@ This document contains the help content for the `admin` command-line program. * [`admin users list-joined-rooms`↴](#admin-users-list-joined-rooms) * [`admin users force-join-room`↴](#admin-users-force-join-room) * [`admin users force-leave-room`↴](#admin-users-force-leave-room) +* [`admin users force-leave-remote-room`↴](#admin-users-force-leave-remote-room) * [`admin users force-demote`↴](#admin-users-force-demote) * [`admin users make-user-admin`↴](#admin-users-make-user-admin) * [`admin users put-room-tag`↴](#admin-users-put-room-tag) @@ -295,6 +296,7 @@ You can find the ID using the `list-appservices` command. * `list-joined-rooms` — - Lists all the rooms (local and remote) that the specified user is joined in * `force-join-room` — - Manually join a local user to a room * `force-leave-room` — - Manually leave a local user from a room +* `force-leave-remote-room` — - Manually leave a remote room for a local user * `force-demote` — - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits * `make-user-admin` — - Grant server-admin privileges to a user * `put-room-tag` — - Puts a room tag for the specified user and room ID @@ -449,6 +451,19 @@ Reverses the effects of the `suspend` command, allowing the user to send message +## `admin users force-leave-remote-room` + +- Manually leave a remote room for a local user + +**Usage:** `admin users force-leave-remote-room ` + +###### **Arguments:** + +* `` +* `` + + + ## `admin users force-demote` - Forces the specified user to drop their power levels to the room default, if their permissions allow and the auth check permits diff --git a/fedora/conduwuit.service b/fedora/conduwuit.service index 6ab2af46..f37c7798 100644 --- a/fedora/conduwuit.service +++ b/fedora/conduwuit.service @@ -15,6 +15,7 @@ Environment="CONTINUWUITY_CONFIG=/etc/conduwuit/conduwuit.toml" Environment="CONTINUWUITY_LOG_TO_JOURNALD=true" Environment="CONTINUWUITY_JOURNALD_IDENTIFIER=%N" +Environment="CONTINUWUITY_DATABASE_PATH=/var/lib/conduwuit" ExecStart=/usr/bin/conduwuit diff --git a/src/admin/user/commands.rs b/src/admin/user/commands.rs index 56864a32..37ab030c 100644 --- a/src/admin/user/commands.rs +++ b/src/admin/user/commands.rs @@ -1,8 +1,8 @@ use std::{collections::BTreeMap, fmt::Write as _}; use api::client::{ - full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, update_avatar_url, - update_displayname, + full_user_deactivate, join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room, + update_avatar_url, update_displayname, }; use conduwuit::{ Err, Result, debug, debug_warn, error, info, is_equal_to, @@ -926,3 +926,29 @@ pub(super) async fn redact_event(&self, event_id: OwnedEventId) -> Result { )) .await } + +#[admin_command] +pub(super) async fn force_leave_remote_room( + &self, + user_id: String, + room_id: OwnedRoomOrAliasId, +) -> Result { + let user_id = parse_local_user_id(self.services, &user_id)?; + let (room_id, _) = self + .services + .rooms + .alias + .resolve_with_servers(&room_id, None) + .await?; + + assert!( + self.services.globals.user_is_local(&user_id), + "Parsed user_id must be a local user" + ); + remote_leave_room(self.services, &user_id, &room_id, None) + .boxed() + .await?; + + self.write_str(&format!("{user_id} has been joined to {room_id}.",)) + .await +} diff --git a/src/admin/user/mod.rs b/src/admin/user/mod.rs index 656cacaf..366f7dd5 100644 --- a/src/admin/user/mod.rs +++ b/src/admin/user/mod.rs @@ -103,6 +103,12 @@ pub enum UserCommand { room_id: OwnedRoomOrAliasId, }, + /// - Manually leave a remote room for a local user. + ForceLeaveRemoteRoom { + user_id: String, + room_id: OwnedRoomOrAliasId, + }, + /// - Forces the specified user to drop their power levels to the room /// default, if their permissions allow and the auth check permits ForceDemote { diff --git a/src/api/client/admin/mod.rs b/src/api/client/admin/mod.rs new file mode 100644 index 00000000..8355a600 --- /dev/null +++ b/src/api/client/admin/mod.rs @@ -0,0 +1,3 @@ +mod suspend; + +pub(crate) use self::suspend::*; diff --git a/src/api/client/admin/suspend.rs b/src/api/client/admin/suspend.rs new file mode 100644 index 00000000..bab1cb5a --- /dev/null +++ b/src/api/client/admin/suspend.rs @@ -0,0 +1,89 @@ +use axum::extract::State; +use conduwuit::{Err, Result}; +use futures::future::{join, join3}; +use ruma::api::client::admin::{get_suspended, set_suspended}; + +use crate::Ruma; + +/// # `GET /_matrix/client/v1/admin/suspend/{userId}` +/// +/// Check the suspension status of a target user +pub(crate) async fn get_suspended_status( + State(services): State, + body: Ruma, +) -> Result { + 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"))); + } + if !services.globals.user_is_local(&body.user_id) { + return Err!(Request(InvalidParam("Can only check the suspended status of local users"))); + } + if !active { + return Err!(Request(NotFound("Unknown user"))); + } + Ok(get_suspended::v1::Response::new( + services.users.is_suspended(&body.user_id).await?, + )) +} + +/// # `PUT /_matrix/client/v1/admin/suspend/{userId}` +/// +/// Set the suspension status of a target user +pub(crate) async fn put_suspended_status( + State(services): State, + body: Ruma, +) -> Result { + let sender_user = body.sender_user(); + + let (sender_admin, active, target_admin) = join3( + services.users.is_admin(sender_user), + services.users.is_active(&body.user_id), + services.users.is_admin(&body.user_id), + ) + .await; + + if !sender_admin { + return Err!(Request(Forbidden("Only server administrators can use this endpoint"))); + } + if !services.globals.user_is_local(&body.user_id) { + return Err!(Request(InvalidParam("Can only set the suspended status of local users"))); + } + if !active { + return Err!(Request(NotFound("Unknown user"))); + } + if body.user_id == *sender_user { + return Err!(Request(Forbidden("You cannot suspend yourself"))); + } + if target_admin { + return Err!(Request(Forbidden("You cannot suspend another server administrator"))); + } + if services.users.is_suspended(&body.user_id).await? == body.suspended { + // No change + return Ok(set_suspended::v1::Response::new(body.suspended)); + } + + let action = if body.suspended { + services + .users + .suspend_account(&body.user_id, sender_user) + .await; + "suspended" + } else { + services.users.unsuspend_account(&body.user_id).await; + "unsuspended" + }; + + if services.config.admin_room_notices { + // Notify the admin room that an account has been un/suspended + services + .admin + .send_text(&format!("{} has been {} by {}.", body.user_id, action, sender_user)) + .await; + } + + Ok(set_suspended::v1::Response::new(body.suspended)) +} diff --git a/src/api/client/capabilities.rs b/src/api/client/capabilities.rs index c42c6dfd..afa61498 100644 --- a/src/api/client/capabilities.rs +++ b/src/api/client/capabilities.rs @@ -19,7 +19,7 @@ use crate::Ruma; /// of this server. pub(crate) async fn get_capabilities_route( State(services): State, - _body: Ruma, + body: Ruma, ) -> Result { let available: BTreeMap = Server::available_room_versions().collect(); @@ -45,5 +45,14 @@ pub(crate) async fn get_capabilities_route( json!({"enabled": services.config.forget_forced_upon_leave}), )?; + 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}))?; + } + Ok(get_capabilities::v3::Response { capabilities }) } diff --git a/src/api/client/membership/join.rs b/src/api/client/membership/join.rs index dc170cbf..f3434bf5 100644 --- a/src/api/client/membership/join.rs +++ b/src/api/client/membership/join.rs @@ -156,31 +156,34 @@ pub(crate) async fn join_room_by_id_or_alias_route( .await?; let mut servers = body.via.clone(); - servers.extend( - services - .rooms - .state_cache - .servers_invite_via(&room_id) - .map(ToOwned::to_owned) - .collect::>() - .await, - ); + if servers.is_empty() { + debug!("No via servers provided for join, injecting some."); + servers.extend( + services + .rooms + .state_cache + .servers_invite_via(&room_id) + .map(ToOwned::to_owned) + .collect::>() + .await, + ); - servers.extend( - services - .rooms - .state_cache - .invite_state(sender_user, &room_id) - .await - .unwrap_or_default() - .iter() - .filter_map(|event| event.get_field("sender").ok().flatten()) - .filter_map(|sender: &str| UserId::parse(sender).ok()) - .map(|user| user.server_name().to_owned()), - ); + servers.extend( + services + .rooms + .state_cache + .invite_state(sender_user, &room_id) + .await + .unwrap_or_default() + .iter() + .filter_map(|event| event.get_field("sender").ok().flatten()) + .filter_map(|sender: &str| UserId::parse(sender).ok()) + .map(|user| user.server_name().to_owned()), + ); - if let Some(server) = room_id.server_name() { - servers.push(server.to_owned()); + if let Some(server) = room_id.server_name() { + servers.push(server.to_owned()); + } } servers.sort_unstable(); diff --git a/src/api/client/membership/leave.rs b/src/api/client/membership/leave.rs index f4f1666b..0aadd833 100644 --- a/src/api/client/membership/leave.rs +++ b/src/api/client/membership/leave.rs @@ -215,7 +215,7 @@ pub async fn leave_room( Ok(()) } -async fn remote_leave_room( +pub async fn remote_leave_room( services: &Services, user_id: &UserId, room_id: &RoomId, diff --git a/src/api/client/membership/mod.rs b/src/api/client/membership/mod.rs index 7a6f19ad..691419f6 100644 --- a/src/api/client/membership/mod.rs +++ b/src/api/client/membership/mod.rs @@ -29,7 +29,7 @@ pub(crate) use self::{ }; pub use self::{ join::join_room_by_id_helper, - leave::{leave_all_rooms, leave_room}, + leave::{leave_all_rooms, leave_room, remote_leave_room}, }; use crate::{Ruma, client::full_user_deactivate}; diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index e4be20b7..c8ca7757 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -1,5 +1,6 @@ pub(super) mod account; pub(super) mod account_data; +pub(super) mod admin; pub(super) mod alias; pub(super) mod appservice; pub(super) mod backup; @@ -43,6 +44,7 @@ pub(super) mod well_known; pub use account::full_user_deactivate; pub(super) use account::*; pub(super) use account_data::*; +pub(super) use admin::*; pub(super) use alias::*; pub(super) use appservice::*; pub(super) use backup::*; @@ -55,7 +57,7 @@ pub(super) use keys::*; pub(super) use media::*; pub(super) use media_legacy::*; pub(super) use membership::*; -pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room}; +pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, remote_leave_room}; pub(super) use message::*; pub(super) use openid::*; pub(super) use presence::*; diff --git a/src/api/client/unversioned.rs b/src/api/client/unversioned.rs index a4136d1a..7f19bc94 100644 --- a/src/api/client/unversioned.rs +++ b/src/api/client/unversioned.rs @@ -58,6 +58,7 @@ pub(crate) async fn get_supported_versions_route( ("uk.tcpip.msc4133".to_owned(), true), /* Extending User Profile API with Key:Value Pairs (https://github.com/matrix-org/matrix-spec-proposals/pull/4133) */ ("us.cloke.msc4175".to_owned(), true), /* Profile field for user time zone (https://github.com/matrix-org/matrix-spec-proposals/pull/4175) */ ("org.matrix.simplified_msc3575".to_owned(), true), /* Simplified Sliding sync (https://github.com/matrix-org/matrix-spec-proposals/pull/4186) */ + ("uk.timedout.msc4323".to_owned(), true), /* agnostic suspend (https://github.com/matrix-org/matrix-spec-proposals/pull/4323) */ ]), }; diff --git a/src/api/router.rs b/src/api/router.rs index 8072fa5b..42934f70 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -184,6 +184,8 @@ pub fn build(router: Router, server: &Server) -> Router { "/_matrix/client/unstable/im.nheko.summary/rooms/:room_id_or_alias/summary", get(client::get_room_summary_legacy) ) + .ruma_route(&client::get_suspended_status) + .ruma_route(&client::put_suspended_status) .ruma_route(&client::well_known_support) .ruma_route(&client::well_known_client) .route("/_conduwuit/server_version", get(client::conduwuit_server_version)) diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index e8518ed4..58a39a75 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -126,9 +126,11 @@ pub struct Config { /// This is the only directory where continuwuity will save its data, /// including media. Note: this was previously "/var/lib/matrix-conduit". /// - /// YOU NEED TO EDIT THIS. + /// YOU NEED TO EDIT THIS, UNLESS you are running continuwuity as a `systemd` service. + /// The service file sets it to `/var/lib/conduwuit` using an environment variable + /// and also grants write access. /// - /// example: "/var/lib/continuwuity" + /// example: "/var/lib/conduwuit" pub database_path: PathBuf, /// continuwuity supports online database backups using RocksDB's Backup