You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

909 lines
33 KiB

use std::{collections::HashMap, num::NonZeroU64};
use bytesize::ByteSize;
use ruma::api::Metadata;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct WrappedShadowConfig {
#[serde(default)]
pub inherits: ConfigPreset,
#[serde(flatten)]
pub config: ShadowConfig,
}
impl From<WrappedShadowConfig> for Config {
fn from(value: WrappedShadowConfig) -> Self {
Config::get_preset(value.inherits).apply_overrides(value.config)
}
}
#[derive(Debug, Clone, Deserialize, Default, Copy)]
#[cfg_attr(feature = "doc-generators", derive(serde::Serialize))]
#[serde(rename_all = "snake_case")]
pub enum ConfigPreset {
/// Default rate-limiting configuration, recommended for small private servers (i.e. single-user
/// or for family and/or friends)
#[default]
PrivateSmall,
PrivateMedium,
PublicMedium,
PublicLarge,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ShadowConfig {
pub client:
ShadowConfigFragment<ClientRestriction, ShadowClientMediaConfig, AuthenticationFailures>,
pub federation:
ShadowConfigFragment<FederationRestriction, ShadowFederationMediaConfig, Nothing>,
}
pub trait RestrictionGeneric: ConfigPart + std::hash::Hash + Eq {}
impl<T> RestrictionGeneric for T where T: ConfigPart + std::hash::Hash + Eq {}
pub trait ConfigPart: Clone + std::fmt::Debug + serde::de::DeserializeOwned {}
impl<T> ConfigPart for T where T: Clone + std::fmt::Debug + serde::de::DeserializeOwned {}
#[derive(Debug, Clone, Deserialize)]
pub struct ShadowConfigFragment<R, M, T>
where
R: RestrictionGeneric,
M: ConfigPart,
T: ConfigPart,
{
#[serde(bound(deserialize = "R: RestrictionGeneric, M: ConfigPart, T: ConfigPart"))]
pub target: Option<ShadowConfigFragmentFragment<R, M, T>>,
#[serde(bound(deserialize = "R: RestrictionGeneric, M: ConfigPart"))]
pub global: Option<ShadowConfigFragmentFragment<R, M, Nothing>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ShadowConfigFragmentFragment<R, M, T>
where
R: RestrictionGeneric,
M: ConfigPart,
T: ConfigPart,
{
#[serde(
flatten,
// https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=fe75063b73c6d9860991c41572e00035
//
// For some reason specifying the default function fixes the issue in the playground link
// above. Makes no sense to me, but hey, it works.
default = "HashMap::new",
bound(deserialize = "R: RestrictionGeneric")
)]
pub map: HashMap<R, RequestLimitation>,
#[serde(bound(deserialize = "M: ConfigPart"))]
pub media: Option<M>,
#[serde(flatten)]
#[serde(bound(deserialize = "T: ConfigPart"))]
pub additional_fields: Option<T>,
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct ShadowClientMediaConfig {
pub download: Option<MediaLimitation>,
pub upload: Option<MediaLimitation>,
pub fetch: Option<MediaLimitation>,
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct ShadowFederationMediaConfig {
pub download: Option<MediaLimitation>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(from = "WrappedShadowConfig")]
pub struct Config {
pub target: ConfigFragment<AuthenticationFailures>,
pub global: ConfigFragment<Nothing>,
}
impl Default for Config {
fn default() -> Self {
Self::get_preset(ConfigPreset::default())
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct ConfigFragment<T>
where
T: ConfigPart,
{
#[serde(bound(deserialize = "T: ConfigPart"))]
pub client: ConfigFragmentFragment<ClientRestriction, ClientMediaConfig, T>,
pub federation: ConfigFragmentFragment<FederationRestriction, FederationMediaConfig, Nothing>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ConfigFragmentFragment<R, M, T>
where
R: RestrictionGeneric,
M: ConfigPart,
T: ConfigPart,
{
#[serde(flatten)]
#[serde(bound(deserialize = "R: RestrictionGeneric"))]
pub map: HashMap<R, RequestLimitation>,
#[serde(bound(deserialize = "M: ConfigPart"))]
pub media: M,
#[serde(flatten)]
#[serde(bound(deserialize = "T: ConfigPart"))]
pub additional_fields: T,
}
impl<R, M, T> ConfigFragmentFragment<R, M, T>
where
R: RestrictionGeneric,
M: ConfigPart + MediaConfig,
T: ConfigPart,
{
pub fn apply_overrides(
self,
shadow: Option<ShadowConfigFragmentFragment<R, M::Shadow, T>>,
) -> Self {
let Some(shadow) = shadow else {
return self;
};
let ConfigFragmentFragment {
mut map,
media,
additional_fields,
} = self;
map.extend(shadow.map);
Self {
map,
media: if let Some(sm) = shadow.media {
media.apply_overrides(sm)
} else {
media
},
additional_fields: shadow.additional_fields.unwrap_or(additional_fields),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct AuthenticationFailures {
pub authentication_failures: RequestLimitation,
}
impl AuthenticationFailures {
fn new(timeframe: Timeframe, burst_capacity: NonZeroU64) -> Self {
Self {
authentication_failures: RequestLimitation::new(timeframe, burst_capacity),
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Nothing;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Restriction {
Client(ClientRestriction),
Federation(FederationRestriction),
}
impl From<ClientRestriction> for Restriction {
fn from(value: ClientRestriction) -> Self {
Self::Client(value)
}
}
impl From<FederationRestriction> for Restriction {
fn from(value: FederationRestriction) -> Self {
Self::Federation(value)
}
}
#[cfg(feature = "doc-generators")]
pub trait DocumentRestrictions: Sized {
fn variant_doc_comments() -> Vec<(Self, String)>;
fn container_doc_comment() -> String;
}
/// Applies for endpoints on the client-server API, which are used by clients, appservices, and
/// bots. Appservices can bypass rate-limiting though if `rate_limited` is set to `false` in their
/// registration file.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(
feature = "doc-generators",
derive(conduit_macros::DocumentRestrictions, serde::Serialize)
)]
#[serde(rename_all = "snake_case")]
pub enum ClientRestriction {
/// For registering a new user account. May be called multiples times for a single
/// registration if there are extra steps, e.g. providing a registration token.
Registration,
/// For logging into an existing account.
Login,
/// For checking whether a given registration token would allow the user to register an
/// account.
RegistrationTokenValidity,
/// For sending an event to a room.
///
/// Note that this is not used for state events, but for users who are unprivliged in a room,
/// the only state event they'll be able to send are ones to update their room profile.
SendEvent,
/// For joining a room.
Join,
/// For inviting a user to a room.
Invite,
/// For knocking on a room.
Knock,
/// For reporting a user, event, or room.
SendReport,
/// For adding an alias to a room.
CreateAlias,
/// For downloading a media file.
///
/// For rate-limiting based on the size of files downloaded, see the media rate-limiting
/// configuration.
MediaDownload,
/// For uploading a media file.
///
/// For rate-limiting based on the size of files uploaded, see the media rate-limiting
/// configuration.
MediaCreate,
}
/// Applies for endpoints on the federation API of this server, hence restricting how
/// many times other servers can use these endpoints on this server in a given timeframe.
#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[cfg_attr(
feature = "doc-generators",
derive(conduit_macros::DocumentRestrictions, serde::Serialize)
)]
#[serde(rename_all = "snake_case")]
pub enum FederationRestriction {
/// For joining a room.
Join,
/// For knocking on a room.
Knock,
/// For inviting a local user to a room.
Invite,
// Transactions should be handled by a completely dedicated rate-limiter
/* /// For sending transactions of PDU/EDUs.
///
///
Transaction, */
/// For downloading media.
MediaDownload,
}
impl TryFrom<Metadata> for Restriction {
type Error = ();
fn try_from(value: Metadata) -> Result<Self, Self::Error> {
use Restriction::*;
use ruma::api::{
IncomingRequest,
client::{
account::{check_registration_token_validity, register},
alias::create_alias,
authenticated_media::{
get_content, get_content_as_filename, get_content_thumbnail, get_media_preview,
},
knock::knock_room,
media::{self, create_content, create_mxc_uri},
membership::{invite_user, join_room_by_id, join_room_by_id_or_alias},
message::send_message_event,
reporting::report_user,
room::{report_content, report_room},
session::login,
state::send_state_event,
},
federation::{
authenticated_media::{
get_content as federation_get_content,
get_content_thumbnail as federation_get_content_thumbnail,
},
membership::{create_invite, create_join_event, create_knock_event},
},
};
Ok(match value {
register::v3::Request::METADATA => Client(ClientRestriction::Registration),
check_registration_token_validity::v1::Request::METADATA => {
Client(ClientRestriction::RegistrationTokenValidity)
}
login::v3::Request::METADATA => Client(ClientRestriction::Login),
send_message_event::v3::Request::METADATA | send_state_event::v3::Request::METADATA => {
Client(ClientRestriction::SendEvent)
}
join_room_by_id::v3::Request::METADATA
| join_room_by_id_or_alias::v3::Request::METADATA => Client(ClientRestriction::Join),
invite_user::v3::Request::METADATA => Client(ClientRestriction::Invite),
knock_room::v3::Request::METADATA => Client(ClientRestriction::Knock),
report_user::v3::Request::METADATA
| report_content::v3::Request::METADATA
| report_room::v3::Request::METADATA => Client(ClientRestriction::SendReport),
create_alias::v3::Request::METADATA => Client(ClientRestriction::CreateAlias),
// NOTE: handle async media upload in a way that doesn't half the number of uploads you can do within a short timeframe, while not allowing pre-generation of MXC uris to allow uploading double the number of media at once
create_content::v3::Request::METADATA | create_mxc_uri::v1::Request::METADATA => {
Client(ClientRestriction::MediaCreate)
}
// Unauthenticate media is deprecated
#[allow(deprecated)]
media::get_content::v3::Request::METADATA
| media::get_content_as_filename::v3::Request::METADATA
| media::get_content_thumbnail::v3::Request::METADATA
| media::get_media_preview::v3::Request::METADATA
| get_content::v1::Request::METADATA
| get_content_as_filename::v1::Request::METADATA
| get_content_thumbnail::v1::Request::METADATA
| get_media_preview::v1::Request::METADATA => Client(ClientRestriction::MediaDownload),
federation_get_content::v1::Request::METADATA
| federation_get_content_thumbnail::v1::Request::METADATA => {
Federation(FederationRestriction::MediaDownload)
}
// v1 is deprecated
#[allow(deprecated)]
create_join_event::v1::Request::METADATA | create_join_event::v2::Request::METADATA => {
Federation(FederationRestriction::Join)
}
create_knock_event::v1::Request::METADATA => Federation(FederationRestriction::Knock),
create_invite::v1::Request::METADATA | create_invite::v2::Request::METADATA => {
Federation(FederationRestriction::Invite)
}
_ => return Err(()),
})
}
}
impl<T> ConfigFragment<T>
where
T: ConfigPart,
{
pub fn get(&self, restriction: &Restriction) -> &RequestLimitation {
// Maybe look into https://github.com/moriyoshi-kasuga/enum-table
match restriction {
Restriction::Client(client_restriction) => {
self.client.map.get(client_restriction).unwrap()
}
Restriction::Federation(federation_restriction) => {
self.federation.map.get(federation_restriction).unwrap()
}
}
}
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct RequestLimitation {
#[serde(flatten)]
pub timeframe: Timeframe,
pub burst_capacity: NonZeroU64,
}
impl RequestLimitation {
pub fn new(timeframe: Timeframe, burst_capacity: NonZeroU64) -> Self {
Self {
timeframe,
burst_capacity,
}
}
}
#[derive(Deserialize, Clone, Copy, Debug)]
#[serde(rename_all = "snake_case")]
// When deserializing, we want this prefix
#[allow(clippy::enum_variant_names)]
pub enum Timeframe {
PerSecond(NonZeroU64),
PerMinute(NonZeroU64),
PerHour(NonZeroU64),
PerDay(NonZeroU64),
}
#[cfg(feature = "doc-generators")]
impl std::fmt::Display for Timeframe {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let value;
let string = match self {
Self::PerSecond(v) => {
value = v;
"second"
}
Self::PerMinute(v) => {
value = v;
"minute"
}
Self::PerHour(v) => {
value = v;
"hour"
}
Self::PerDay(v) => {
value = v;
"day"
}
};
write!(f, "{value} requests per {string}")
}
}
impl Timeframe {
pub fn nano_gap(&self) -> u64 {
match self {
Timeframe::PerSecond(t) => 1000 * 1000 * 1000 / t.get(),
Timeframe::PerMinute(t) => 1000 * 1000 * 1000 * 60 / t.get(),
Timeframe::PerHour(t) => 1000 * 1000 * 1000 * 60 * 60 / t.get(),
Timeframe::PerDay(t) => 1000 * 1000 * 1000 * 60 * 60 * 24 / t.get(),
}
}
}
pub trait MediaConfig {
type Shadow: ConfigPart;
fn apply_overrides(self, shadow: Self::Shadow) -> Self;
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct ClientMediaConfig {
pub download: MediaLimitation,
pub upload: MediaLimitation,
pub fetch: MediaLimitation,
}
impl MediaConfig for ClientMediaConfig {
type Shadow = ShadowClientMediaConfig;
fn apply_overrides(self, shadow: Self::Shadow) -> Self {
let Self::Shadow {
download,
upload,
fetch,
} = shadow;
Self {
download: download.unwrap_or(self.download),
upload: upload.unwrap_or(self.upload),
fetch: fetch.unwrap_or(self.fetch),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct FederationMediaConfig {
pub download: MediaLimitation,
}
impl MediaConfig for FederationMediaConfig {
type Shadow = ShadowFederationMediaConfig;
fn apply_overrides(self, shadow: Self::Shadow) -> Self {
Self {
download: shadow.download.unwrap_or(self.download),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize)]
pub struct MediaLimitation {
#[serde(flatten)]
pub timeframe: MediaTimeframe,
pub burst_capacity: ByteSize,
}
impl MediaLimitation {
pub fn new(timeframe: MediaTimeframe, burst_capacity: ByteSize) -> Self {
Self {
timeframe,
burst_capacity,
}
}
}
#[derive(Deserialize, Clone, Copy, Debug)]
#[serde(rename_all = "snake_case")]
// When deserializing, we want this prefix
#[allow(clippy::enum_variant_names)]
pub enum MediaTimeframe {
PerSecond(ByteSize),
PerMinute(ByteSize),
PerHour(ByteSize),
PerDay(ByteSize),
}
impl MediaTimeframe {
pub fn bytes_per_sec(&self) -> u64 {
match self {
MediaTimeframe::PerSecond(t) => t.as_u64(),
MediaTimeframe::PerMinute(t) => t.as_u64() / 60,
MediaTimeframe::PerHour(t) => t.as_u64() / (60 * 60),
MediaTimeframe::PerDay(t) => t.as_u64() / (60 * 60 * 24),
}
}
}
fn nz(int: u64) -> NonZeroU64 {
NonZeroU64::new(int).expect("Values are static")
}
macro_rules! default_restriction_map {
($restriction_type:ident; $($restriction:ident, $timeframe:ident, $timeframe_value:expr, $burst_capacity:expr;)*) => {
HashMap::from_iter([
$((
$restriction_type::$restriction,
RequestLimitation::new(Timeframe::$timeframe(nz($timeframe_value)), nz($burst_capacity)),
),)*
])
}
}
macro_rules! media_config {
($config_type:ident; $($key:ident: $timeframe:ident, $timeframe_value:expr, $burst_capacity:expr;)*) => {
$config_type {
$($key: MediaLimitation::new(MediaTimeframe::$timeframe($timeframe_value), $burst_capacity),)*
}
}
}
impl Config {
fn apply_overrides(self, shadow: ShadowConfig) -> Self {
let ShadowConfig {
client:
ShadowConfigFragment {
target: client_target,
global: client_global,
},
federation:
ShadowConfigFragment {
target: federation_target,
global: federation_global,
},
} = shadow;
Self {
target: ConfigFragment {
client: self.target.client.apply_overrides(client_target),
federation: self.target.federation.apply_overrides(federation_target),
},
global: ConfigFragment {
client: self.global.client.apply_overrides(client_global),
federation: self.global.federation.apply_overrides(federation_global),
},
}
}
pub fn get_preset(preset: ConfigPreset) -> Self {
// The client target map shouldn't really differ between presets, as individual user's
// behaviours shouldn't differ depending on the size of the server or whether it's private
// or public, but maybe I'm wrong.
let target_client_map = default_restriction_map!(
ClientRestriction;
Registration, PerDay, 3, 10;
Login, PerDay, 5, 20;
RegistrationTokenValidity, PerDay, 10, 20;
SendEvent, PerMinute, 15, 60;
Join, PerHour, 5, 30;
Knock, PerHour, 5, 30;
Invite, PerHour, 2, 20;
SendReport, PerDay, 5, 20;
CreateAlias, PerDay, 2, 20;
MediaDownload, PerHour, 30, 100;
MediaCreate, PerMinute, 4, 20;
);
// Same goes for media
let target_client_media = media_config! {
ClientMediaConfig;
download: PerMinute, ByteSize::mb(100), ByteSize::mb(50);
upload: PerMinute, ByteSize::mb(10), ByteSize::mb(100);
fetch: PerMinute, ByteSize::mb(100), ByteSize::mb(50);
};
// Currently, these values are completely arbitrary, not informed by any sort of
// knowledge. In the future, it would be good to have some sort of analytics to
// determine what some good defaults could be. Maybe getting some percentiles for
// burst_capacity & timeframes used. How we'd tell the difference between power users
// and malicilous attacks, I'm not sure.
match preset {
ConfigPreset::PrivateSmall => Self {
target: ConfigFragment {
client: ConfigFragmentFragment {
map: target_client_map,
media: target_client_media,
additional_fields: AuthenticationFailures::new(
Timeframe::PerHour(nz(1)),
nz(20),
),
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerHour, 10, 10;
Knock, PerHour, 10, 10;
Invite, PerHour, 10, 10;
MediaDownload, PerMinute, 10, 50;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(100), ByteSize::mb(100);
},
additional_fields: Nothing,
},
},
global: ConfigFragment {
client: ConfigFragmentFragment {
map: default_restriction_map!(
ClientRestriction;
Registration, PerDay, 10, 20;
Login, PerHour, 10, 10;
RegistrationTokenValidity, PerDay, 10, 20;
SendEvent, PerSecond, 2, 100;
Join, PerMinute, 1, 30;
Knock, PerMinute, 1, 30;
Invite, PerHour, 10, 20;
SendReport, PerHour, 1, 25;
CreateAlias, PerHour, 5, 20;
MediaDownload, PerMinute, 5, 150;
MediaCreate, PerMinute, 20, 50;
),
media: media_config! {
ClientMediaConfig;
download: PerMinute, ByteSize::mb(250), ByteSize::mb(100);
upload: PerMinute, ByteSize::mb(50), ByteSize::mb(100);
fetch: PerMinute, ByteSize::mb(250), ByteSize::mb(100);
},
additional_fields: Nothing,
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerMinute, 10, 10;
Knock, PerMinute, 10, 10;
Invite, PerMinute, 10, 10;
MediaDownload, PerSecond, 10, 250;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(250), ByteSize::mb(250);
},
additional_fields: Nothing,
},
},
},
ConfigPreset::PrivateMedium => Self {
target: ConfigFragment {
client: ConfigFragmentFragment {
map: target_client_map,
media: target_client_media,
additional_fields: AuthenticationFailures::new(
Timeframe::PerHour(nz(10)),
nz(20),
),
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerHour, 30, 10;
Knock, PerHour, 30, 10;
Invite, PerHour, 30, 10;
MediaDownload, PerMinute, 100, 50;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(200), ByteSize::mb(200);
},
additional_fields: Nothing,
},
},
global: ConfigFragment {
client: ConfigFragmentFragment {
map: default_restriction_map!(
ClientRestriction;
Registration, PerDay, 20, 20;
Login, PerHour, 25, 15;
RegistrationTokenValidity, PerDay, 20, 20;
SendEvent, PerSecond, 10, 100;
Join, PerMinute, 5, 30;
Knock, PerMinute, 5, 30;
Invite, PerMinute, 1, 20;
SendReport, PerHour, 10, 25;
CreateAlias, PerMinute, 1, 50;
MediaDownload, PerSecond, 1, 200;
MediaCreate, PerSecond, 2, 20;
),
media: media_config! {
ClientMediaConfig;
download: PerMinute, ByteSize::mb(500), ByteSize::mb(200);
upload: PerMinute, ByteSize::mb(100), ByteSize::mb(200);
fetch: PerMinute, ByteSize::mb(500), ByteSize::mb(200);
},
additional_fields: Nothing,
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerMinute, 25, 25;
Knock, PerMinute, 25, 25;
Invite, PerMinute, 25, 25;
MediaDownload, PerSecond, 10, 100;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(500), ByteSize::mb(500);
},
additional_fields: Nothing,
},
},
},
ConfigPreset::PublicMedium => Self {
target: ConfigFragment {
client: ConfigFragmentFragment {
map: target_client_map,
media: target_client_media,
additional_fields: AuthenticationFailures::new(
Timeframe::PerHour(nz(10)),
nz(20),
),
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerHour, 30, 10;
Knock, PerHour, 30, 10;
Invite, PerHour, 30, 10;
MediaDownload, PerMinute, 100, 50;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(200), ByteSize::mb(200);
},
additional_fields: Nothing,
},
},
global: ConfigFragment {
client: ConfigFragmentFragment {
map: default_restriction_map!(
ClientRestriction;
Registration, PerHour, 5, 20;
Login, PerHour, 25, 15;
// Public servers don't have registration tokens, so let's rate limit
// heavily so that if they revert to a private server again, it's a
// reminder to change their preset.
RegistrationTokenValidity, PerDay, 1, 1;
SendEvent, PerSecond, 10, 100;
Join, PerMinute, 5, 30;
Knock, PerMinute, 5, 30;
Invite, PerMinute, 1, 20;
SendReport, PerHour, 10, 25;
CreateAlias, PerMinute, 1, 50;
MediaDownload, PerSecond, 1, 200;
MediaCreate, PerSecond, 2, 20;
),
media: media_config! {
ClientMediaConfig;
download: PerMinute, ByteSize::mb(500), ByteSize::mb(200);
upload: PerMinute, ByteSize::mb(100), ByteSize::mb(200);
fetch: PerMinute, ByteSize::mb(500), ByteSize::mb(200);
},
additional_fields: Nothing,
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerMinute, 25, 25;
Knock, PerMinute, 25, 25;
Invite, PerMinute, 25, 25;
MediaDownload, PerSecond, 10, 100;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(500), ByteSize::mb(500);
},
additional_fields: Nothing,
},
},
},
ConfigPreset::PublicLarge => Self {
target: ConfigFragment {
client: ConfigFragmentFragment {
map: target_client_map,
media: target_client_media,
additional_fields: AuthenticationFailures::new(
Timeframe::PerMinute(nz(1)),
nz(20),
),
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerHour, 90, 30;
Knock, PerHour, 90, 30;
Invite, PerHour, 90, 30;
MediaDownload, PerMinute, 100, 50;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::mb(600), ByteSize::mb(300);
},
additional_fields: Nothing,
},
},
global: ConfigFragment {
client: ConfigFragmentFragment {
map: default_restriction_map!(
ClientRestriction;
Registration, PerMinute, 4, 25;
Login, PerMinute, 10, 25;
// Public servers don't have registration tokens, so let's rate limit
// heavily so that if they revert to a private server again, it's a
// reminder to change their preset.
RegistrationTokenValidity, PerDay, 1, 1;
SendEvent, PerSecond, 100, 50;
Join, PerSecond, 1, 20;
Knock, PerSecond, 1, 20;
Invite, PerMinute, 10, 40;
SendReport, PerMinute, 5, 25;
CreateAlias, PerMinute, 30, 20;
MediaDownload, PerSecond, 25, 200;
MediaCreate, PerSecond, 10, 30;
),
media: media_config! {
ClientMediaConfig;
download: PerMinute, ByteSize::gb(2), ByteSize::mb(500);
upload: PerMinute, ByteSize::mb(500), ByteSize::mb(500);
fetch: PerMinute, ByteSize::gb(2), ByteSize::mb(500);
},
additional_fields: Nothing,
},
federation: ConfigFragmentFragment {
map: default_restriction_map!(
FederationRestriction;
Join, PerSecond, 1, 50;
Knock, PerSecond, 1, 50;
Invite, PerSecond, 1, 50;
MediaDownload, PerSecond, 50, 100;
),
media: media_config! {
FederationMediaConfig;
download: PerMinute, ByteSize::gb(2), ByteSize::gb(1);
},
additional_fields: Nothing,
},
},
},
}
}
}