mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2026-05-26 20:49:55 +00:00
feat: Consolidate antispam checks into a service
Also adds support for the spam checker join rule, and Draupnir callbacks
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{Result, config::Antispam, debug};
|
||||
use ruma::{OwnedRoomId, OwnedUserId, draupnir_antispam, meowlnir_antispam};
|
||||
|
||||
use crate::{client, config, sending, service::Dep};
|
||||
|
||||
struct Services {
|
||||
config: Dep<config::Service>,
|
||||
client: Dep<client::Service>,
|
||||
}
|
||||
|
||||
pub struct Service {
|
||||
services: Services,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl crate::Service for Service {
|
||||
fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
|
||||
Ok(Arc::new(Self {
|
||||
services: Services {
|
||||
client: args.depend::<client::Service>("client"),
|
||||
config: args.depend::<config::Service>("config"),
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
|
||||
}
|
||||
|
||||
impl Service {
|
||||
async fn send_antispam_request<T>(
|
||||
&self,
|
||||
base_url: &str,
|
||||
secret: &str,
|
||||
request: T,
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: ruma::api::OutgoingRequest + std::fmt::Debug + Send,
|
||||
{
|
||||
sending::antispam::send_antispam_request(
|
||||
&self.services.client.appservice,
|
||||
base_url,
|
||||
secret,
|
||||
request,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Checks with the antispam service whether `inviter` may invite `invitee`
|
||||
/// to `room_id`.
|
||||
///
|
||||
/// If no antispam service is configured, this always returns `Ok(())`.
|
||||
/// If an error is returned, the invite should be blocked - the antispam
|
||||
/// service was unreachable, or refused the invite.
|
||||
pub async fn user_may_invite(
|
||||
&self,
|
||||
inviter: OwnedUserId,
|
||||
invitee: OwnedUserId,
|
||||
room_id: OwnedRoomId,
|
||||
) -> Result<()> {
|
||||
if let Some(config) = &self.services.config.antispam {
|
||||
let result = if let Some(meowlnir) = &config.meowlnir {
|
||||
debug!("Asking meowlnir for user_may_invite");
|
||||
self.send_antispam_request(
|
||||
meowlnir.base_url.as_str(),
|
||||
&meowlnir.secret,
|
||||
meowlnir_antispam::user_may_invite::v1::Request::new(
|
||||
meowlnir.management_room.clone(),
|
||||
inviter,
|
||||
invitee,
|
||||
room_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("meowlnir allowed the invite"))
|
||||
.inspect_err(|e| debug!("meowlnir denied the invite: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else if let Some(draupnir) = &config.draupnir {
|
||||
debug!("Asking draupnir for user_may_invite");
|
||||
self.send_antispam_request(
|
||||
draupnir.base_url.as_str(),
|
||||
&draupnir.secret,
|
||||
draupnir_antispam::user_may_invite::v1::Request::new(
|
||||
room_id, inviter, invitee,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("draupnir allowed the invite"))
|
||||
.inspect_err(|e| debug!("draupnir denied the invite: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
return result;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks with the antispam service whether `user_id` may join `room_id`.
|
||||
pub async fn user_may_join_room(
|
||||
&self,
|
||||
user_id: OwnedUserId,
|
||||
room_id: OwnedRoomId,
|
||||
is_invited: bool,
|
||||
) -> Result<()> {
|
||||
if let Some(config) = &self.services.config.antispam {
|
||||
let result = if let Some(meowlnir) = &config.meowlnir {
|
||||
debug!("Asking meowlnir for user_may_join_room");
|
||||
self.send_antispam_request(
|
||||
meowlnir.base_url.as_str(),
|
||||
&meowlnir.secret,
|
||||
meowlnir_antispam::user_may_join_room::v1::Request::new(
|
||||
meowlnir.management_room.clone(),
|
||||
user_id,
|
||||
room_id,
|
||||
is_invited,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("meowlnir allowed the join"))
|
||||
.inspect_err(|e| debug!("meowlnir denied the join: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else if let Some(draupnir) = &config.draupnir {
|
||||
debug!("Asking draupnir for user_may_join_room");
|
||||
self.send_antispam_request(
|
||||
draupnir.base_url.as_str(),
|
||||
&draupnir.secret,
|
||||
draupnir_antispam::user_may_join_room::v1::Request::new(
|
||||
user_id, room_id, is_invited,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("draupnir allowed the join"))
|
||||
.inspect_err(|e| debug!("draupnir denied the join: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
return result;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks with Meowlnir whether the incoming federated `make_join` request
|
||||
/// should be allowed. Applies the `fi.mau.spam_checker` join rule.
|
||||
pub async fn meowlnir_accept_make_join(
|
||||
&self,
|
||||
room_id: OwnedRoomId,
|
||||
user_id: OwnedUserId,
|
||||
) -> Result<()> {
|
||||
if let Some(Antispam { meowlnir: Some(meowlnir), .. }) = &self.services.config.antispam {
|
||||
debug!("Asking meowlnir for meowlnir_accept_make_join");
|
||||
self.send_antispam_request(
|
||||
meowlnir.base_url.as_str(),
|
||||
&meowlnir.secret,
|
||||
meowlnir_antispam::accept_make_join::v1::Request::new(
|
||||
meowlnir.management_room.clone(),
|
||||
user_id,
|
||||
room_id,
|
||||
),
|
||||
)
|
||||
.await
|
||||
.inspect(|_| debug!("meowlnir allowed the make_join"))
|
||||
.inspect_err(|e| debug!("meowlnir denied the make_join: {e:?}"))
|
||||
.map(|_| ())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,6 +1,8 @@
|
||||
#![type_length_limit = "8192"]
|
||||
#![allow(refining_impl_trait)]
|
||||
|
||||
extern crate conduwuit_core as conduwuit;
|
||||
extern crate conduwuit_database as database;
|
||||
mod manager;
|
||||
mod migrations;
|
||||
mod service;
|
||||
@@ -10,6 +12,7 @@ pub mod state;
|
||||
pub mod account_data;
|
||||
pub mod admin;
|
||||
pub mod announcements;
|
||||
pub mod antispam;
|
||||
pub mod appservice;
|
||||
pub mod client;
|
||||
pub mod config;
|
||||
@@ -30,9 +33,6 @@ pub mod transaction_ids;
|
||||
pub mod uiaa;
|
||||
pub mod users;
|
||||
|
||||
extern crate conduwuit_core as conduwuit;
|
||||
extern crate conduwuit_database as database;
|
||||
|
||||
use ctor::{ctor, dtor};
|
||||
pub(crate) use service::{Args, Dep, Service};
|
||||
|
||||
|
||||
@@ -1,30 +1,23 @@
|
||||
use std::{fmt::Debug, mem};
|
||||
|
||||
use bytes::BytesMut;
|
||||
use conduwuit::{Err, Result, config::MeowlnirConfig, debug_error, err, utils, warn};
|
||||
use conduwuit::{Err, Result, debug_error, err, utils, warn};
|
||||
use reqwest::Client;
|
||||
use ruma::api::{IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken};
|
||||
|
||||
/// Sends a request to an antispam service
|
||||
pub(crate) async fn send_meowlnir_request<T>(
|
||||
pub(crate) async fn send_antispam_request<T>(
|
||||
client: &Client,
|
||||
config: &MeowlnirConfig,
|
||||
base_url: &str,
|
||||
secret: &str,
|
||||
request: T,
|
||||
) -> Result<Option<T::IncomingResponse>>
|
||||
) -> Result<T::IncomingResponse>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
{
|
||||
const VERSIONS: [MatrixVersion; 1] = [MatrixVersion::V1_15];
|
||||
if config.secret.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
let secret = config.secret.as_str();
|
||||
let http_request = request
|
||||
.try_into_http_request::<BytesMut>(
|
||||
config.base_url.as_str(),
|
||||
SendAccessToken::Always(secret),
|
||||
&VERSIONS,
|
||||
)?
|
||||
.try_into_http_request::<BytesMut>(base_url, SendAccessToken::Always(secret), &VERSIONS)?
|
||||
.map(BytesMut::freeze);
|
||||
let reqwest_request = reqwest::Request::try_from(http_request)?;
|
||||
|
||||
@@ -64,7 +57,7 @@ where
|
||||
.expect("reqwest body is valid http body"),
|
||||
);
|
||||
|
||||
response.map(Some).map_err(|e| {
|
||||
response.map_err(|e| {
|
||||
err!(BadServerResponse(warn!(
|
||||
"Antispam returned invalid/malformed response bytes: {e}",
|
||||
)))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod antispam;
|
||||
pub mod antispam;
|
||||
mod appservice;
|
||||
mod data;
|
||||
mod dest;
|
||||
@@ -13,9 +13,7 @@ use std::{
|
||||
|
||||
use async_trait::async_trait;
|
||||
use conduwuit::{
|
||||
Result, Server,
|
||||
config::MeowlnirConfig,
|
||||
debug, debug_warn, err, error,
|
||||
Result, Server, debug, debug_warn, err, error,
|
||||
smallvec::SmallVec,
|
||||
utils::{ReadyExt, TryReadyExt, available_parallelism, math::usize_from_u64_truncated},
|
||||
warn,
|
||||
@@ -337,18 +335,6 @@ impl Service {
|
||||
appservice::send_request(client, registration, request).await
|
||||
}
|
||||
|
||||
/// Sends a request to the chosen antispam configuration
|
||||
pub async fn send_meowlnir_antispam_request<T>(
|
||||
&self,
|
||||
config: &MeowlnirConfig,
|
||||
request: T,
|
||||
) -> Result<Option<T::IncomingResponse>>
|
||||
where
|
||||
T: OutgoingRequest + Debug + Send,
|
||||
{
|
||||
antispam::send_meowlnir_request(&self.services.client.appservice, config, request).await
|
||||
}
|
||||
|
||||
/// Clean up queued sending event data
|
||||
///
|
||||
/// Used after we remove an appservice registration or a user deletes a push
|
||||
|
||||
@@ -8,8 +8,8 @@ use futures::{Stream, StreamExt, TryStreamExt};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
account_data, admin, announcements, appservice, client, config, emergency, federation,
|
||||
globals, key_backups,
|
||||
account_data, admin, announcements, antispam, appservice, client, config, emergency,
|
||||
federation, globals, key_backups,
|
||||
manager::Manager,
|
||||
media, moderation, presence, pusher, resolver, rooms, sending, server_keys, service,
|
||||
service::{Args, Map, Service},
|
||||
@@ -39,6 +39,7 @@ pub struct Services {
|
||||
pub users: Arc<users::Service>,
|
||||
pub moderation: Arc<moderation::Service>,
|
||||
pub announcements: Arc<announcements::Service>,
|
||||
pub antispam: Arc<antispam::Service>,
|
||||
|
||||
manager: Mutex<Option<Arc<Manager>>>,
|
||||
pub(crate) service: Arc<Map>,
|
||||
@@ -107,6 +108,7 @@ impl Services {
|
||||
users: build!(users::Service),
|
||||
moderation: build!(moderation::Service),
|
||||
announcements: build!(announcements::Service),
|
||||
antispam: build!(antispam::Service),
|
||||
|
||||
manager: Mutex::new(None),
|
||||
service,
|
||||
|
||||
Reference in New Issue
Block a user