modlog entry for allow instance

This commit is contained in:
Felix Ableitner 2024-11-20 14:29:07 +01:00
parent 675d8fad46
commit f0838ed965
18 changed files with 407 additions and 252 deletions

View file

@ -0,0 +1,46 @@
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
site::AdminAllowInstanceParams,
utils::is_admin,
LemmyErrorType,
SuccessResponse,
};
use lemmy_db_schema::source::{
federation_allowlist::{AdminAllowInstance, AdminAllowInstanceForm},
instance::Instance,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn admin_allow_instance(
data: Json<AdminAllowInstanceParams>,
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> LemmyResult<Json<SuccessResponse>> {
is_admin(&local_user_view)?;
let allowlist = Instance::allowlist(&mut context.pool()).await?;
if !allowlist.is_empty() {
return Err(
LemmyErrorType::Unknown("Using allowlist requires that blocklist be empty".to_string())
.into(),
);
}
let instance_block_form = AdminAllowInstanceForm {
instance_id: data.instance_id,
admin_person_id: local_user_view.person.id,
reason: data.reason.clone(),
};
if data.allow {
AdminAllowInstance::allow(&mut context.pool(), &instance_block_form).await?;
} else {
AdminAllowInstance::unallow(&mut context.pool(), data.instance_id).await?;
}
Ok(Json(SuccessResponse::default()))
}

View file

@ -2,21 +2,34 @@ use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
site::{AdminBlockInstanceParams, AdminBlockInstanceResponse}, site::AdminBlockInstanceParams,
utils::is_admin, utils::is_admin,
LemmyErrorType,
SuccessResponse,
};
use lemmy_db_schema::source::{
federation_blocklist::{AdminBlockInstance, AdminBlockInstanceForm},
instance::Instance,
}; };
use lemmy_db_schema::source::federation_blocklist::{AdminBlockInstance, AdminBlockInstanceForm};
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn block_instance( pub async fn admin_block_instance(
data: Json<AdminBlockInstanceParams>, data: Json<AdminBlockInstanceParams>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
context: Data<LemmyContext>, context: Data<LemmyContext>,
) -> LemmyResult<Json<AdminBlockInstanceResponse>> { ) -> LemmyResult<Json<SuccessResponse>> {
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
let allowlist = Instance::allowlist(&mut context.pool()).await?;
if !allowlist.is_empty() {
return Err(
LemmyErrorType::Unknown("Using blocklist requires that allowlist be empty".to_string())
.into(),
);
}
let instance_block_form = AdminBlockInstanceForm { let instance_block_form = AdminBlockInstanceForm {
instance_id: data.instance_id, instance_id: data.instance_id,
admin_person_id: local_user_view.person.id, admin_person_id: local_user_view.person.id,
@ -30,7 +43,5 @@ pub async fn block_instance(
AdminBlockInstance::unblock(&mut context.pool(), data.instance_id).await?; AdminBlockInstance::unblock(&mut context.pool(), data.instance_id).await?;
} }
Ok(Json(AdminBlockInstanceResponse { Ok(Json(SuccessResponse::default()))
blocked: data.block,
}))
} }

View file

@ -1,3 +1,4 @@
pub mod admin_allow_instance;
pub mod admin_block_instance; pub mod admin_block_instance;
pub mod federated_instances; pub mod federated_instances;
pub mod leave_admin; pub mod leave_admin;

View file

@ -7,6 +7,7 @@ use lemmy_api_common::{
use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType}; use lemmy_db_schema::{source::local_site::LocalSite, ModlogActionType};
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_moderator::structs::{ use lemmy_db_views_moderator::structs::{
AdminAllowInstanceView,
AdminBlockInstanceView, AdminBlockInstanceView,
AdminPurgeCommentView, AdminPurgeCommentView,
AdminPurgeCommunityView, AdminPurgeCommunityView,
@ -123,6 +124,7 @@ pub async fn get_mod_log(
admin_purged_posts, admin_purged_posts,
admin_purged_comments, admin_purged_comments,
admin_block_instance, admin_block_instance,
admin_allow_instance,
) = if data.community_id.is_none() { ) = if data.community_id.is_none() {
( (
match type_ { match type_ {
@ -169,6 +171,12 @@ pub async fn get_mod_log(
} }
_ => Default::default(), _ => Default::default(),
}, },
match type_ {
All | AdminAllowInstance if other_person_id.is_none() => {
AdminAllowInstanceView::list(&mut context.pool(), params).await?
}
_ => Default::default(),
},
) )
} else { } else {
Default::default() Default::default()
@ -192,5 +200,6 @@ pub async fn get_mod_log(
admin_purged_comments, admin_purged_comments,
hidden_communities, hidden_communities,
admin_block_instance, admin_block_instance,
admin_allow_instance,
})) }))
} }

View file

@ -43,6 +43,7 @@ use lemmy_db_views_actor::structs::{
PersonView, PersonView,
}; };
use lemmy_db_views_moderator::structs::{ use lemmy_db_views_moderator::structs::{
AdminAllowInstanceView,
AdminBlockInstanceView, AdminBlockInstanceView,
AdminPurgeCommentView, AdminPurgeCommentView,
AdminPurgeCommunityView, AdminPurgeCommunityView,
@ -185,6 +186,7 @@ pub struct GetModlogResponse {
pub admin_purged_comments: Vec<AdminPurgeCommentView>, pub admin_purged_comments: Vec<AdminPurgeCommentView>,
pub hidden_communities: Vec<ModHideCommunityView>, pub hidden_communities: Vec<ModHideCommunityView>,
pub admin_block_instance: Vec<AdminBlockInstanceView>, pub admin_block_instance: Vec<AdminBlockInstanceView>,
pub admin_allow_instance: Vec<AdminAllowInstanceView>,
} }
#[skip_serializing_none] #[skip_serializing_none]
@ -678,6 +680,9 @@ pub struct AdminBlockInstanceParams {
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
pub struct AdminBlockInstanceResponse { pub struct AdminAllowInstanceParams {
pub blocked: bool, pub instance_id: InstanceId,
pub allow: bool,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
} }

View file

@ -1,97 +1,48 @@
use crate::{ use crate::{
schema::federation_allowlist, newtypes::InstanceId,
source::{ schema::{admin_allow_instance, federation_allowlist},
federation_allowlist::{FederationAllowList, FederationAllowListForm}, source::federation_allowlist::{
instance::Instance, AdminAllowInstance,
AdminAllowInstanceForm,
FederationAllowListForm,
}, },
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
}; };
use diesel::{dsl::insert_into, result::Error}; use diesel::{delete, dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::{AsyncPgConnection, RunQueryDsl}; use diesel_async::RunQueryDsl;
impl FederationAllowList { impl AdminAllowInstance {
pub async fn replace(pool: &mut DbPool<'_>, list_opt: Option<Vec<String>>) -> Result<(), Error> { pub async fn allow(pool: &mut DbPool<'_>, form: &AdminAllowInstanceForm) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
conn conn
.build_transaction() .build_transaction()
.run(|conn| { .run(|conn| {
Box::pin(async move { Box::pin(async move {
if let Some(list) = list_opt { insert_into(admin_allow_instance::table)
Self::clear(conn).await?; .values(form)
.execute(conn)
.await?;
for domain in list { let form2 = FederationAllowListForm {
// Upsert all of these as instances instance_id: form.instance_id,
let instance = Instance::read_or_create(&mut conn.into(), domain).await?; updated: None,
};
let form = FederationAllowListForm { insert_into(federation_allowlist::table)
instance_id: instance.id, .values(form2)
updated: None, .execute(conn)
}; .await?;
insert_into(federation_allowlist::table) Ok(())
.values(form) })
.get_result::<Self>(conn)
.await?;
}
Ok(())
} else {
Ok(())
}
}) as _
}) })
.await .await
} }
pub async fn unallow(pool: &mut DbPool<'_>, instance_id: InstanceId) -> Result<(), Error> {
async fn clear(conn: &mut AsyncPgConnection) -> Result<usize, Error> { let conn = &mut get_conn(pool).await?;
diesel::delete(federation_allowlist::table) delete(
.execute(conn) federation_allowlist::table.filter(federation_allowlist::dsl::instance_id.eq(instance_id)),
.await )
} .execute(conn)
} .await?;
#[cfg(test)]
mod tests {
use crate::{
source::{federation_allowlist::FederationAllowList, instance::Instance},
utils::build_db_pool_for_tests,
};
use diesel::result::Error;
use pretty_assertions::assert_eq;
use serial_test::serial;
#[tokio::test]
#[serial]
async fn test_allowlist_insert_and_clear() -> Result<(), Error> {
let pool = &build_db_pool_for_tests();
let pool = &mut pool.into();
let domains = vec![
"tld1.xyz".to_string(),
"tld2.xyz".to_string(),
"tld3.xyz".to_string(),
];
let allowed = Some(domains.clone());
FederationAllowList::replace(pool, allowed).await?;
let allows = Instance::allowlist(pool).await?;
let allows_domains = allows
.iter()
.map(|i| i.domain.clone())
.collect::<Vec<String>>();
assert_eq!(3, allows.len());
assert_eq!(domains, allows_domains);
// Now test clearing them via Some(empty vec)
let clear_allows = Some(Vec::new());
FederationAllowList::replace(pool, clear_allows).await?;
let allows = Instance::allowlist(pool).await?;
assert_eq!(0, allows.len());
Instance::delete_all(pool).await?;
Ok(()) Ok(())
} }
} }

View file

@ -216,6 +216,7 @@ pub enum ModlogActionType {
AdminPurgePost, AdminPurgePost,
AdminPurgeComment, AdminPurgeComment,
AdminBlockInstance, AdminBlockInstance,
AdminAllowInstance,
} }
#[derive( #[derive(

View file

@ -42,6 +42,16 @@ pub mod sql_types {
pub struct RegistrationModeEnum; pub struct RegistrationModeEnum;
} }
diesel::table! {
admin_allow_instance (id) {
id -> Int4,
instance_id -> Int4,
admin_person_id -> Int4,
reason -> Nullable<Text>,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
admin_block_instance (id) { admin_block_instance (id) {
id -> Int4, id -> Int4,
@ -943,6 +953,8 @@ diesel::table! {
} }
} }
diesel::joinable!(admin_allow_instance -> instance (instance_id));
diesel::joinable!(admin_allow_instance -> person (admin_person_id));
diesel::joinable!(admin_block_instance -> instance (instance_id)); diesel::joinable!(admin_block_instance -> instance (instance_id));
diesel::joinable!(admin_block_instance -> person (admin_person_id)); diesel::joinable!(admin_block_instance -> person (admin_person_id));
diesel::joinable!(admin_purge_comment -> person (admin_person_id)); diesel::joinable!(admin_purge_comment -> person (admin_person_id));
@ -1022,6 +1034,7 @@ diesel::joinable!(site_language -> language (language_id));
diesel::joinable!(site_language -> site (site_id)); diesel::joinable!(site_language -> site (site_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
admin_allow_instance,
admin_block_instance, admin_block_instance,
admin_purge_comment, admin_purge_comment,
admin_purge_community, admin_purge_community,

View file

@ -1,9 +1,12 @@
use crate::newtypes::InstanceId; use crate::newtypes::{InstanceId, PersonId};
#[cfg(feature = "full")]
use crate::schema::federation_allowlist;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Debug; use std::fmt::Debug;
#[cfg(feature = "full")]
use {
crate::schema::{admin_allow_instance, federation_allowlist},
ts_rs::TS,
};
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr( #[cfg_attr(
@ -26,7 +29,38 @@ pub struct FederationAllowList {
#[derive(Clone, Default)] #[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = federation_allowlist))] #[cfg_attr(feature = "full", diesel(table_name = federation_allowlist))]
pub struct FederationAllowListForm { pub(crate) struct FederationAllowListForm {
pub instance_id: InstanceId, pub instance_id: InstanceId,
pub updated: Option<DateTime<Utc>>, pub updated: Option<DateTime<Utc>>,
} }
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(TS, Queryable, Selectable, Associations, Identifiable)
)]
#[cfg_attr(
feature = "full",
diesel(belongs_to(crate::source::instance::Instance))
)]
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
#[cfg_attr(feature = "full", diesel(primary_key(instance_id)))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
pub struct AdminAllowInstance {
pub id: i32,
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
#[cfg_attr(feature = "full", ts(optional))]
pub reason: Option<String>,
pub published: DateTime<Utc>,
}
#[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = admin_allow_instance))]
pub struct AdminAllowInstanceForm {
pub instance_id: InstanceId,
pub admin_person_id: PersonId,
pub reason: Option<String>,
}

View file

@ -33,7 +33,7 @@ pub struct FederationBlockList {
#[derive(Clone, Default)] #[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))] #[cfg_attr(feature = "full", diesel(table_name = federation_blocklist))]
pub struct FederationBlockListForm { pub(crate) struct FederationBlockListForm {
pub instance_id: InstanceId, pub instance_id: InstanceId,
pub updated: Option<DateTime<Utc>>, pub updated: Option<DateTime<Utc>>,
pub block_expires: Option<DateTime<Utc>>, pub block_expires: Option<DateTime<Utc>>,

View file

@ -0,0 +1,52 @@
use crate::structs::{AdminAllowInstanceView, ModlogListParams};
use diesel::{
result::Error,
BoolExpressionMethods,
ExpressionMethods,
IntoSql,
JoinOnDsl,
NullableExpressionMethods,
QueryDsl,
};
use diesel_async::RunQueryDsl;
use lemmy_db_schema::{
newtypes::PersonId,
schema::{admin_allow_instance, instance, person},
utils::{get_conn, limit_and_offset, DbPool},
};
impl AdminAllowInstanceView {
pub async fn list(pool: &mut DbPool<'_>, params: ModlogListParams) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?;
let admin_person_id_join = params.mod_person_id.unwrap_or(PersonId(-1));
let show_mod_names = !params.hide_modlog_names;
let show_mod_names_expr = show_mod_names.as_sql::<diesel::sql_types::Bool>();
let admin_names_join = admin_allow_instance::admin_person_id
.eq(person::id)
.and(show_mod_names_expr.or(person::id.eq(admin_person_id_join)));
let mut query = admin_allow_instance::table
.left_join(person::table.on(admin_names_join))
.inner_join(instance::table)
.select((
admin_allow_instance::all_columns,
instance::all_columns,
person::all_columns.nullable(),
))
.into_boxed();
if let Some(admin_person_id) = params.mod_person_id {
query = query.filter(admin_allow_instance::admin_person_id.eq(admin_person_id));
};
let (limit, offset) = limit_and_offset(params.page, params.limit)?;
query
.limit(limit)
.offset(offset)
.order_by(admin_allow_instance::published.desc())
.load::<Self>(conn)
.await
}
}

View file

@ -46,7 +46,7 @@ impl AdminBlockInstanceView {
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.order_by(admin_block_instance::published.desc()) .order_by(admin_block_instance::published.desc())
.load::<AdminBlockInstanceView>(conn) .load::<Self>(conn)
.await .await
} }
} }

View file

@ -1,4 +1,6 @@
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod admin_allow_instance;
#[cfg(feature = "full")]
pub mod admin_block_instance; pub mod admin_block_instance;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod admin_purge_comment_view; pub mod admin_purge_comment_view;

View file

@ -5,6 +5,7 @@ use lemmy_db_schema::{
source::{ source::{
comment::Comment, comment::Comment,
community::Community, community::Community,
federation_allowlist::AdminAllowInstance,
federation_blocklist::AdminBlockInstance, federation_blocklist::AdminBlockInstance,
instance::Instance, instance::Instance,
moderator::{ moderator::{
@ -248,6 +249,19 @@ pub struct AdminBlockInstanceView {
pub admin: Option<Person>, pub admin: Option<Person>,
} }
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// When an admin purges a post.
pub struct AdminAllowInstanceView {
pub admin_block_instance: AdminAllowInstance,
pub instance: Instance,
#[cfg_attr(feature = "full", ts(optional))]
pub admin: Option<Person>,
}
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[derive(Debug, Serialize, Deserialize, Clone, Copy)]
#[cfg_attr(feature = "full", derive(TS, Queryable))] #[cfg_attr(feature = "full", derive(TS, Queryable))]

View file

@ -201,7 +201,7 @@ mod test {
use chrono::DateTime; use chrono::DateTime;
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
federation_allowlist::FederationAllowList, federation_allowlist::{AdminAllowInstance, AdminAllowInstanceForm},
federation_blocklist::{AdminBlockInstance, AdminBlockInstanceForm}, federation_blocklist::{AdminBlockInstance, AdminBlockInstanceForm},
instance::InstanceForm, instance::InstanceForm,
person::{Person, PersonInsertForm}, person::{Person, PersonInsertForm},
@ -349,13 +349,21 @@ mod test {
async fn test_send_manager_allowed() -> LemmyResult<()> { async fn test_send_manager_allowed() -> LemmyResult<()> {
let mut data = TestData::init(1, 1).await?; let mut data = TestData::init(1, 1).await?;
let domain = data.instances[0].domain.clone(); let instance_id = data.instances[0].id;
FederationAllowList::replace(&mut data.context.pool(), Some(vec![domain])).await?; let form = PersonInsertForm::new("tim".to_string(), String::new(), instance_id);
let person = Person::create(&mut data.context.pool(), &form).await?;
let form = AdminAllowInstanceForm {
instance_id: data.instances[0].id,
admin_person_id: person.id,
reason: None,
};
AdminAllowInstance::allow(&mut data.context.pool(), &form).await?;
data.run().await?; data.run().await?;
let workers = &data.send_manager.workers; let workers = &data.send_manager.workers;
assert_eq!(1, workers.len()); assert_eq!(1, workers.len());
assert!(workers.contains_key(&data.instances[0].id)); assert!(workers.contains_key(&data.instances[0].id));
Person::delete(&mut data.context.pool(), person.id).await?;
data.cleanup().await?; data.cleanup().await?;
Ok(()) Ok(())
} }

View file

@ -3,3 +3,5 @@ ALTER TABLE federation_blocklist
DROP TABLE admin_block_instance; DROP TABLE admin_block_instance;
DROP TABLE admin_allow_instance;

View file

@ -10,3 +10,11 @@ CREATE TABLE admin_block_instance (
published timestamptz NOT NULL DEFAULT now() published timestamptz NOT NULL DEFAULT now()
); );
CREATE TABLE admin_allow_instance (
id serial PRIMARY KEY,
instance_id int NOT NULL REFERENCES instance (id) ON UPDATE CASCADE ON DELETE CASCADE,
admin_person_id int NOT NULL REFERENCES person (id) ON UPDATE CASCADE ON DELETE CASCADE,
reason text,
published timestamptz NOT NULL DEFAULT now()
);

View file

@ -1,4 +1,4 @@
use actix_web::{guard, web}; use actix_web::{guard, web::*};
use lemmy_api::{ use lemmy_api::{
comment::{ comment::{
distinguish::distinguish_comment, distinguish::distinguish_comment,
@ -76,6 +76,8 @@ use lemmy_api::{
resolve::resolve_pm_report, resolve::resolve_pm_report,
}, },
site::{ site::{
admin_allow_instance::admin_allow_instance,
admin_block_instance::admin_block_instance,
federated_instances::get_federated_instances, federated_instances::get_federated_instances,
leave_admin::leave_admin, leave_admin::leave_admin,
list_all_media::list_all_media, list_all_media::list_all_media,
@ -159,146 +161,146 @@ use lemmy_apub::api::{
use lemmy_routes::images::image_proxy; use lemmy_routes::images::image_proxy;
use lemmy_utils::rate_limit::RateLimitCell; use lemmy_utils::rate_limit::RateLimitCell;
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { pub fn config(cfg: &mut ServiceConfig, rate_limit: &RateLimitCell) {
cfg.service( cfg.service(
web::scope("/api/v3") scope("/api/v3")
.route("/image_proxy", web::get().to(image_proxy)) .route("/image_proxy", get().to(image_proxy))
// Site // Site
.service( .service(
web::scope("/site") scope("/site")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_site)) .route("", get().to(get_site))
// Admin Actions // Admin Actions
.route("", web::post().to(create_site)) .route("", post().to(create_site))
.route("", web::put().to(update_site)) .route("", put().to(update_site))
.route("/block", web::post().to(user_block_instance)), .route("/block", post().to(user_block_instance)),
) )
.service( .service(
web::resource("/modlog") resource("/modlog")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route(web::get().to(get_mod_log)), .route(get().to(get_mod_log)),
) )
.service( .service(
web::resource("/search") resource("/search")
.wrap(rate_limit.search()) .wrap(rate_limit.search())
.route(web::get().to(search)), .route(get().to(search)),
) )
.service( .service(
web::resource("/resolve_object") resource("/resolve_object")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route(web::get().to(resolve_object)), .route(get().to(resolve_object)),
) )
// Community // Community
.service( .service(
web::resource("/community") resource("/community")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(create_community)), .route(post().to(create_community)),
) )
.service( .service(
web::scope("/community") scope("/community")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_community)) .route("", get().to(get_community))
.route("", web::put().to(update_community)) .route("", put().to(update_community))
.route("/random", web::get().to(get_random_community)) .route("/random", get().to(get_random_community))
.route("/hide", web::put().to(hide_community)) .route("/hide", put().to(hide_community))
.route("/list", web::get().to(list_communities)) .route("/list", get().to(list_communities))
.route("/follow", web::post().to(follow_community)) .route("/follow", post().to(follow_community))
.route("/block", web::post().to(block_community)) .route("/block", post().to(block_community))
.route("/delete", web::post().to(delete_community)) .route("/delete", post().to(delete_community))
// Mod Actions // Mod Actions
.route("/remove", web::post().to(remove_community)) .route("/remove", post().to(remove_community))
.route("/transfer", web::post().to(transfer_community)) .route("/transfer", post().to(transfer_community))
.route("/ban_user", web::post().to(ban_from_community)) .route("/ban_user", post().to(ban_from_community))
.route("/mod", web::post().to(add_mod_to_community)) .route("/mod", post().to(add_mod_to_community))
.service( .service(
web::scope("/pending_follows") scope("/pending_follows")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("/count", web::get().to(get_pending_follows_count)) .route("/count", get().to(get_pending_follows_count))
.route("/list", web::get().to(get_pending_follows_list)) .route("/list", get().to(get_pending_follows_list))
.route("/approve", web::post().to(post_pending_follows_approve)), .route("/approve", post().to(post_pending_follows_approve)),
), ),
) )
.service( .service(
web::scope("/federated_instances") scope("/federated_instances")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_federated_instances)), .route("", get().to(get_federated_instances)),
) )
// Post // Post
.service( .service(
// Handle POST to /post separately to add the post() rate limitter // Handle POST to /post separately to add the post() rate limitter
web::resource("/post") resource("/post")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.post()) .wrap(rate_limit.post())
.route(web::post().to(create_post)), .route(post().to(create_post)),
) )
.service( .service(
web::scope("/post") scope("/post")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_post)) .route("", get().to(get_post))
.route("", web::put().to(update_post)) .route("", put().to(update_post))
.route("/delete", web::post().to(delete_post)) .route("/delete", post().to(delete_post))
.route("/remove", web::post().to(remove_post)) .route("/remove", post().to(remove_post))
.route("/mark_as_read", web::post().to(mark_post_as_read)) .route("/mark_as_read", post().to(mark_post_as_read))
.route("/mark_many_as_read", web::post().to(mark_posts_as_read)) .route("/mark_many_as_read", post().to(mark_posts_as_read))
.route("/hide", web::post().to(hide_post)) .route("/hide", post().to(hide_post))
.route("/lock", web::post().to(lock_post)) .route("/lock", post().to(lock_post))
.route("/feature", web::post().to(feature_post)) .route("/feature", post().to(feature_post))
.route("/list", web::get().to(list_posts)) .route("/list", get().to(list_posts))
.route("/like", web::post().to(like_post)) .route("/like", post().to(like_post))
.route("/like/list", web::get().to(list_post_likes)) .route("/like/list", get().to(list_post_likes))
.route("/save", web::put().to(save_post)) .route("/save", put().to(save_post))
.route("/report", web::post().to(create_post_report)) .route("/report", post().to(create_post_report))
.route("/report/resolve", web::put().to(resolve_post_report)) .route("/report/resolve", put().to(resolve_post_report))
.route("/report/list", web::get().to(list_post_reports)) .route("/report/list", get().to(list_post_reports))
.route("/site_metadata", web::get().to(get_link_metadata)), .route("/site_metadata", get().to(get_link_metadata)),
) )
// Comment // Comment
.service( .service(
// Handle POST to /comment separately to add the comment() rate limitter // Handle POST to /comment separately to add the comment() rate limitter
web::resource("/comment") resource("/comment")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.comment()) .wrap(rate_limit.comment())
.route(web::post().to(create_comment)), .route(post().to(create_comment)),
) )
.service( .service(
web::scope("/comment") scope("/comment")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_comment)) .route("", get().to(get_comment))
.route("", web::put().to(update_comment)) .route("", put().to(update_comment))
.route("/delete", web::post().to(delete_comment)) .route("/delete", post().to(delete_comment))
.route("/remove", web::post().to(remove_comment)) .route("/remove", post().to(remove_comment))
.route("/mark_as_read", web::post().to(mark_reply_as_read)) .route("/mark_as_read", post().to(mark_reply_as_read))
.route("/distinguish", web::post().to(distinguish_comment)) .route("/distinguish", post().to(distinguish_comment))
.route("/like", web::post().to(like_comment)) .route("/like", post().to(like_comment))
.route("/like/list", web::get().to(list_comment_likes)) .route("/like/list", get().to(list_comment_likes))
.route("/save", web::put().to(save_comment)) .route("/save", put().to(save_comment))
.route("/list", web::get().to(list_comments)) .route("/list", get().to(list_comments))
.route("/report", web::post().to(create_comment_report)) .route("/report", post().to(create_comment_report))
.route("/report/resolve", web::put().to(resolve_comment_report)) .route("/report/resolve", put().to(resolve_comment_report))
.route("/report/list", web::get().to(list_comment_reports)), .route("/report/list", get().to(list_comment_reports)),
) )
// Private Message // Private Message
.service( .service(
web::scope("/private_message") scope("/private_message")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("/list", web::get().to(get_private_message)) .route("/list", get().to(get_private_message))
.route("", web::post().to(create_private_message)) .route("", post().to(create_private_message))
.route("", web::put().to(update_private_message)) .route("", put().to(update_private_message))
.route("/delete", web::post().to(delete_private_message)) .route("/delete", post().to(delete_private_message))
.route("/mark_as_read", web::post().to(mark_pm_as_read)) .route("/mark_as_read", post().to(mark_pm_as_read))
.route("/report", web::post().to(create_pm_report)) .route("/report", post().to(create_pm_report))
.route("/report/resolve", web::put().to(resolve_pm_report)) .route("/report/resolve", put().to(resolve_pm_report))
.route("/report/list", web::get().to(list_pm_reports)), .route("/report/list", get().to(list_pm_reports)),
) )
// User // User
.service( .service(
// Account action, I don't like that it's in /user maybe /accounts // Account action, I don't like that it's in /user maybe /accounts
// Handle /user/register separately to add the register() rate limiter // Handle /user/register separately to add the register() rate limiter
web::resource("/user/register") resource("/user/register")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(register)), .route(post().to(register)),
) )
// User // User
.service( .service(
@ -306,138 +308,134 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
// TODO: pretty annoying way to apply rate limits for register and login, we should // TODO: pretty annoying way to apply rate limits for register and login, we should
// group them under a common path so that rate limit is only applied once (eg under // group them under a common path so that rate limit is only applied once (eg under
// /account). // /account).
web::resource("/user/login") resource("/user/login")
.guard(guard::Post()) .guard(guard::Post())
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(login)), .route(post().to(login)),
) )
.service( .service(
web::resource("/user/password_reset") resource("/user/password_reset")
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route(web::post().to(reset_password)), .route(post().to(reset_password)),
) )
.service( .service(
// Handle captcha separately // Handle captcha separately
web::resource("/user/get_captcha") resource("/user/get_captcha")
.wrap(rate_limit.post()) .wrap(rate_limit.post())
.route(web::get().to(get_captcha)), .route(get().to(get_captcha)),
) )
.service( .service(
web::resource("/user/export_settings") resource("/user/export_settings")
.wrap(rate_limit.import_user_settings()) .wrap(rate_limit.import_user_settings())
.route(web::get().to(export_settings)), .route(get().to(export_settings)),
) )
.service( .service(
web::resource("/user/import_settings") resource("/user/import_settings")
.wrap(rate_limit.import_user_settings()) .wrap(rate_limit.import_user_settings())
.route(web::post().to(import_settings)), .route(post().to(import_settings)),
) )
// TODO, all the current account related actions under /user need to get moved here eventually // TODO, all the current account related actions under /user need to get moved here eventually
.service( .service(
web::scope("/account") scope("/account")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("/list_media", web::get().to(list_media)), .route("/list_media", get().to(list_media)),
) )
// User actions // User actions
.service( .service(
web::scope("/user") scope("/user")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(read_person)) .route("", get().to(read_person))
.route("/mention", web::get().to(list_mentions)) .route("/mention", get().to(list_mentions))
.route( .route(
"/mention/mark_as_read", "/mention/mark_as_read",
web::post().to(mark_person_mention_as_read), post().to(mark_person_mention_as_read),
) )
.route("/replies", web::get().to(list_replies)) .route("/replies", get().to(list_replies))
// Admin action. I don't like that it's in /user // Admin action. I don't like that it's in /user
.route("/ban", web::post().to(ban_from_site)) .route("/ban", post().to(ban_from_site))
.route("/banned", web::get().to(list_banned_users)) .route("/banned", get().to(list_banned_users))
.route("/block", web::post().to(block_person)) .route("/block", post().to(block_person))
// TODO Account actions. I don't like that they're in /user maybe /accounts // TODO Account actions. I don't like that they're in /user maybe /accounts
.route("/logout", web::post().to(logout)) .route("/logout", post().to(logout))
.route("/delete_account", web::post().to(delete_account)) .route("/delete_account", post().to(delete_account))
.route( .route("/password_change", post().to(change_password_after_reset))
"/password_change",
web::post().to(change_password_after_reset),
)
// TODO mark_all_as_read feels off being in this section as well // TODO mark_all_as_read feels off being in this section as well
.route( .route("/mark_all_as_read", post().to(mark_all_notifications_read))
"/mark_all_as_read", .route("/save_user_settings", put().to(save_user_settings))
web::post().to(mark_all_notifications_read), .route("/change_password", put().to(change_password))
) .route("/report_count", get().to(report_count))
.route("/save_user_settings", web::put().to(save_user_settings)) .route("/unread_count", get().to(unread_count))
.route("/change_password", web::put().to(change_password)) .route("/verify_email", post().to(verify_email))
.route("/report_count", web::get().to(report_count)) .route("/leave_admin", post().to(leave_admin))
.route("/unread_count", web::get().to(unread_count)) .route("/totp/generate", post().to(generate_totp_secret))
.route("/verify_email", web::post().to(verify_email)) .route("/totp/update", post().to(update_totp))
.route("/leave_admin", web::post().to(leave_admin)) .route("/list_logins", get().to(list_logins))
.route("/totp/generate", web::post().to(generate_totp_secret)) .route("/validate_auth", get().to(validate_auth)),
.route("/totp/update", web::post().to(update_totp))
.route("/list_logins", web::get().to(list_logins))
.route("/validate_auth", web::get().to(validate_auth)),
) )
// Admin Actions // Admin Actions
.service( .service(
web::scope("/admin") scope("/admin")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("/add", web::post().to(add_admin)) .route("/add", post().to(add_admin))
.route( .route(
"/registration_application/count", "/registration_application/count",
web::get().to(get_unread_registration_application_count), get().to(get_unread_registration_application_count),
) )
.route( .route(
"/registration_application/list", "/registration_application/list",
web::get().to(list_registration_applications), get().to(list_registration_applications),
) )
.route( .route(
"/registration_application/approve", "/registration_application/approve",
web::put().to(approve_registration_application), put().to(approve_registration_application),
) )
.route( .route(
"/registration_application", "/registration_application",
web::get().to(get_registration_application), get().to(get_registration_application),
) )
.route("/list_all_media", web::get().to(list_all_media)) .route("/list_all_media", get().to(list_all_media))
.service( .service(
web::scope("/purge") scope("/purge")
.route("/person", web::post().to(purge_person)) .route("/person", post().to(purge_person))
.route("/community", web::post().to(purge_community)) .route("/community", post().to(purge_community))
.route("/post", web::post().to(purge_post)) .route("/post", post().to(purge_post))
.route("/comment", web::post().to(purge_comment)), .route("/comment", post().to(purge_comment)),
) )
.service( .service(
web::scope("/tagline") scope("/tagline")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::post().to(create_tagline)) .route("", post().to(create_tagline))
.route("", web::put().to(update_tagline)) .route("", put().to(update_tagline))
.route("/delete", web::post().to(delete_tagline)) .route("/delete", post().to(delete_tagline))
.route("/list", web::get().to(list_taglines)), .route("/list", get().to(list_taglines)),
), )
.route("block_instance", post().to(admin_block_instance))
.route("allow_instance", post().to(admin_allow_instance)),
) )
.service( .service(
web::scope("/custom_emoji") scope("/custom_emoji")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::post().to(create_custom_emoji)) .route("", post().to(create_custom_emoji))
.route("", web::put().to(update_custom_emoji)) .route("", put().to(update_custom_emoji))
.route("/delete", web::post().to(delete_custom_emoji)) .route("/delete", post().to(delete_custom_emoji))
.route("/list", web::get().to(list_custom_emojis)), .route("/list", get().to(list_custom_emojis)),
) )
.service( .service(
web::scope("/oauth_provider") scope("/oauth_provider")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::post().to(create_oauth_provider)) .route("", post().to(create_oauth_provider))
.route("", web::put().to(update_oauth_provider)) .route("", put().to(update_oauth_provider))
.route("/delete", web::post().to(delete_oauth_provider)), .route("/delete", post().to(delete_oauth_provider)),
) )
.service( .service(
web::scope("/oauth") scope("/oauth")
.wrap(rate_limit.register()) .wrap(rate_limit.register())
.route("/authenticate", web::post().to(authenticate_with_oauth)), .route("/authenticate", post().to(authenticate_with_oauth)),
), ),
); );
cfg.service( cfg.service(
web::scope("/sitemap.xml") scope("/sitemap.xml")
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::get().to(get_sitemap)), .route("", get().to(get_sitemap)),
); );
} }