mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Add endpoints required for API-based takedowns and room bans
This commit is contained in:
Generated
+24
-13
@@ -1314,6 +1314,16 @@ dependencies = [
|
|||||||
"typewit",
|
"typewit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "continuwuity-admin-api"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
|
dependencies = [
|
||||||
|
"ruma-common",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "convert_case"
|
name = "convert_case"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -1687,7 +1697,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "draupnir-antispam"
|
name = "draupnir-antispam"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -3035,7 +3045,7 @@ checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "meowlnir-antispam"
|
name = "meowlnir-antispam"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -4250,9 +4260,10 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma"
|
name = "ruma"
|
||||||
version = "0.10.1"
|
version = "0.10.1"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"assign",
|
"assign",
|
||||||
|
"continuwuity-admin-api",
|
||||||
"draupnir-antispam",
|
"draupnir-antispam",
|
||||||
"js_int",
|
"js_int",
|
||||||
"js_option",
|
"js_option",
|
||||||
@@ -4272,7 +4283,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-appservice-api"
|
name = "ruma-appservice-api"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4284,7 +4295,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-client-api"
|
name = "ruma-client-api"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"assign",
|
"assign",
|
||||||
@@ -4307,7 +4318,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-common"
|
name = "ruma-common"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -4339,7 +4350,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-events"
|
name = "ruma-events"
|
||||||
version = "0.28.1"
|
version = "0.28.1"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"as_variant",
|
"as_variant",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
@@ -4364,7 +4375,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-federation-api"
|
name = "ruma-federation-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"headers",
|
"headers",
|
||||||
@@ -4386,7 +4397,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identifiers-validation"
|
name = "ruma-identifiers-validation"
|
||||||
version = "0.9.5"
|
version = "0.9.5"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
@@ -4395,7 +4406,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-identity-service-api"
|
name = "ruma-identity-service-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4405,7 +4416,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-macros"
|
name = "ruma-macros"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"proc-macro-crate",
|
"proc-macro-crate",
|
||||||
@@ -4420,7 +4431,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-push-gateway-api"
|
name = "ruma-push-gateway-api"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js_int",
|
"js_int",
|
||||||
"ruma-common",
|
"ruma-common",
|
||||||
@@ -4432,7 +4443,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "ruma-signatures"
|
name = "ruma-signatures"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=f9e74cb206cfa45cf5f17d39282253b43a15fcd5#f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
source = "git+https://forgejo.ellis.link/continuwuation/ruwuma?rev=85d00fb5746cba23904234b4fd3c838dcf141541#85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
|
|||||||
+1
-1
@@ -342,7 +342,7 @@ version = "0.1.2"
|
|||||||
# Used for matrix spec type definitions and helpers
|
# Used for matrix spec type definitions and helpers
|
||||||
[workspace.dependencies.ruma]
|
[workspace.dependencies.ruma]
|
||||||
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
git = "https://forgejo.ellis.link/continuwuation/ruwuma"
|
||||||
rev = "f9e74cb206cfa45cf5f17d39282253b43a15fcd5"
|
rev = "85d00fb5746cba23904234b4fd3c838dcf141541"
|
||||||
features = [
|
features = [
|
||||||
"compat",
|
"compat",
|
||||||
"rand",
|
"rand",
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod rooms;
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use conduwuit::{Err, Result, info, utils::ReadyExt, warn};
|
||||||
|
use futures::{FutureExt, StreamExt};
|
||||||
|
use ruma::{
|
||||||
|
OwnedRoomAliasId, continuwuity_admin_api::rooms,
|
||||||
|
events::room::message::RoomMessageEventContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Ruma, client::leave_room};
|
||||||
|
|
||||||
|
/// # `PUT /_continuwuity/admin/rooms/{roomID}/ban`
|
||||||
|
///
|
||||||
|
/// Bans or unbans a room.
|
||||||
|
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.sender_user();
|
||||||
|
if !services.users.is_admin(sender_user).await {
|
||||||
|
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if body.banned {
|
||||||
|
// Don't ban again if already banned
|
||||||
|
if services.rooms.metadata.is_banned(&body.room_id).await {
|
||||||
|
return Err!(Request(InvalidParam("Room is already banned")));
|
||||||
|
}
|
||||||
|
info!(%sender_user, "Banning room {}", body.room_id);
|
||||||
|
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!("{sender_user} banned {} (ban in progress)", body.room_id))
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let mut users = services
|
||||||
|
.rooms
|
||||||
|
.state_cache
|
||||||
|
.room_members(&body.room_id)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.ready_filter(|user| services.globals.user_is_local(user))
|
||||||
|
.boxed();
|
||||||
|
let mut evicted = Vec::new();
|
||||||
|
let mut failed_evicted = Vec::new();
|
||||||
|
|
||||||
|
while let Some(ref user_id) = users.next().await {
|
||||||
|
info!("Evicting user {} from room {}", user_id, body.room_id);
|
||||||
|
match leave_room(&services, user_id, &body.room_id, None)
|
||||||
|
.boxed()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
| Ok(()) => {
|
||||||
|
services.rooms.state_cache.forget(&body.room_id, user_id);
|
||||||
|
evicted.push(user_id.clone());
|
||||||
|
},
|
||||||
|
| Err(e) => {
|
||||||
|
warn!("Failed to evict user {} from room {}: {}", user_id, body.room_id, e);
|
||||||
|
failed_evicted.push(user_id.clone());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliases: Vec<OwnedRoomAliasId> = services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.local_aliases_for_room(&body.room_id)
|
||||||
|
.map(ToOwned::to_owned)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.await;
|
||||||
|
for alias in &aliases {
|
||||||
|
info!("Removing alias {} for banned room {}", alias, body.room_id);
|
||||||
|
services
|
||||||
|
.rooms
|
||||||
|
.alias
|
||||||
|
.remove_alias(alias, &services.globals.server_user)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
services.rooms.directory.set_not_public(&body.room_id); // remove from the room directory
|
||||||
|
services.rooms.metadata.ban_room(&body.room_id, true); // prevent further joins
|
||||||
|
services.rooms.metadata.disable_room(&body.room_id, true); // disable federation
|
||||||
|
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!(
|
||||||
|
"Finished banning {}: Removed {} users ({} failed) and {} aliases",
|
||||||
|
body.room_id,
|
||||||
|
evicted.len(),
|
||||||
|
failed_evicted.len(),
|
||||||
|
aliases.len()
|
||||||
|
))
|
||||||
|
.await;
|
||||||
|
if !evicted.is_empty() || !failed_evicted.is_empty() || !aliases.is_empty() {
|
||||||
|
let msg = services
|
||||||
|
.admin
|
||||||
|
.text_or_file(RoomMessageEventContent::text_markdown(format!(
|
||||||
|
"Removed users:\n{}\n\nFailed to remove users:\n{}\n\nRemoved aliases: {}",
|
||||||
|
evicted
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n"),
|
||||||
|
failed_evicted
|
||||||
|
.iter()
|
||||||
|
.map(|u| u.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n"),
|
||||||
|
aliases
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.as_str())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", "),
|
||||||
|
)))
|
||||||
|
.await;
|
||||||
|
services.admin.send_message(msg).await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(rooms::ban::v1::Response::new(evicted, failed_evicted, aliases))
|
||||||
|
} else {
|
||||||
|
// Don't unban if not banned
|
||||||
|
if !services.rooms.metadata.is_banned(&body.room_id).await {
|
||||||
|
return Err!(Request(InvalidParam("Room is not banned")));
|
||||||
|
}
|
||||||
|
info!(%sender_user, "Unbanning room {}", body.room_id);
|
||||||
|
services.rooms.metadata.disable_room(&body.room_id, false);
|
||||||
|
services.rooms.metadata.ban_room(&body.room_id, false);
|
||||||
|
services
|
||||||
|
.admin
|
||||||
|
.notice(&format!("{sender_user} unbanned {}", body.room_id))
|
||||||
|
.await;
|
||||||
|
Ok(rooms::ban::v1::Response::new(Vec::new(), Vec::new(), Vec::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use axum::extract::State;
|
||||||
|
use conduwuit::{Err, Result};
|
||||||
|
use futures::StreamExt;
|
||||||
|
use ruma::{OwnedRoomId, continuwuity_admin_api::rooms};
|
||||||
|
|
||||||
|
use crate::Ruma;
|
||||||
|
|
||||||
|
/// # `GET /_continuwuity/admin/rooms/list`
|
||||||
|
///
|
||||||
|
/// Lists all rooms known to this server, excluding banned ones.
|
||||||
|
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.sender_user();
|
||||||
|
if !services.users.is_admin(sender_user).await {
|
||||||
|
return Err!(Request(Forbidden("Only server administrators can use this endpoint")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rooms: Vec<OwnedRoomId> = services
|
||||||
|
.rooms
|
||||||
|
.metadata
|
||||||
|
.iter_ids()
|
||||||
|
.filter_map(|room_id| async move {
|
||||||
|
if !services.rooms.metadata.is_banned(room_id).await {
|
||||||
|
Some(room_id.to_owned())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
.await;
|
||||||
|
rooms.sort();
|
||||||
|
Ok(rooms::list::v1::Response::new(rooms))
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod ban;
|
||||||
|
pub mod list;
|
||||||
+3
-2
@@ -1,12 +1,13 @@
|
|||||||
#![type_length_limit = "16384"] //TODO: reduce me
|
#![type_length_limit = "16384"] //TODO: reduce me
|
||||||
#![allow(clippy::toplevel_ref_arg)]
|
#![allow(clippy::toplevel_ref_arg)]
|
||||||
|
|
||||||
|
extern crate conduwuit_core as conduwuit;
|
||||||
|
extern crate conduwuit_service as service;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod router;
|
pub mod router;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
||||||
extern crate conduwuit_core as conduwuit;
|
pub mod admin;
|
||||||
extern crate conduwuit_service as service;
|
|
||||||
|
|
||||||
pub(crate) use self::router::{Ruma, RumaResponse, State};
|
pub(crate) use self::router::{Ruma, RumaResponse, State};
|
||||||
|
|
||||||
|
|||||||
+4
-2
@@ -17,7 +17,7 @@ use http::{Uri, uri};
|
|||||||
|
|
||||||
use self::handler::RouterExt;
|
use self::handler::RouterExt;
|
||||||
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
|
pub(super) use self::{args::Args as Ruma, response::RumaResponse};
|
||||||
use crate::{client, server};
|
use crate::{admin, client, server};
|
||||||
|
|
||||||
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
||||||
let config = &server.config;
|
let config = &server.config;
|
||||||
@@ -187,7 +187,9 @@ pub fn build(router: Router<State>, server: &Server) -> Router<State> {
|
|||||||
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
.route("/_conduwuit/server_version", get(client::conduwuit_server_version))
|
||||||
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
.route("/_continuwuity/server_version", get(client::conduwuit_server_version))
|
||||||
.ruma_route(&client::room_initial_sync_route)
|
.ruma_route(&client::room_initial_sync_route)
|
||||||
.route("/client/server.json", get(client::syncv3_client_server_json));
|
.route("/client/server.json", get(client::syncv3_client_server_json))
|
||||||
|
.ruma_route(&admin::rooms::ban::ban_room)
|
||||||
|
.ruma_route(&admin::rooms::list::list_rooms);
|
||||||
|
|
||||||
if config.allow_federation {
|
if config.allow_federation {
|
||||||
router = router
|
router = router
|
||||||
|
|||||||
Reference in New Issue
Block a user