2023-09-21 10:42:28 +00:00
use crate ::{ context ::LemmyContext , request ::purge_image_from_pictrs , site ::FederatedInstances } ;
2022-11-28 14:29:33 +00:00
use anyhow ::Context ;
2023-08-24 15:27:00 +00:00
use chrono ::{ DateTime , Utc } ;
2022-05-03 17:44:13 +00:00
use lemmy_db_schema ::{
2022-07-28 21:14:07 +00:00
impls ::person ::is_banned ,
2023-09-21 10:42:28 +00:00
newtypes ::{ CommunityId , DbUrl , PersonId , PostId } ,
2022-05-03 17:44:13 +00:00
source ::{
2022-10-27 09:24:07 +00:00
comment ::{ Comment , CommentUpdateForm } ,
2023-04-15 23:41:05 +00:00
community ::{ Community , CommunityModerator , CommunityUpdateForm } ,
2022-05-03 17:44:13 +00:00
email_verification ::{ EmailVerification , EmailVerificationForm } ,
2022-10-27 09:24:07 +00:00
instance ::Instance ,
2023-04-17 19:19:51 +00:00
local_site ::LocalSite ,
2022-10-27 09:24:07 +00:00
local_site_rate_limit ::LocalSiteRateLimit ,
2022-05-03 17:44:13 +00:00
password_reset_request ::PasswordResetRequest ,
2022-10-27 09:24:07 +00:00
person ::{ Person , PersonUpdateForm } ,
2022-05-03 17:44:13 +00:00
person_block ::PersonBlock ,
post ::{ Post , PostRead , PostReadForm } ,
} ,
traits ::{ Crud , Readable } ,
utils ::DbPool ,
} ;
2023-03-01 17:19:46 +00:00
use lemmy_db_views ::{ comment_view ::CommentQuery , structs ::LocalUserView } ;
2022-05-03 17:44:13 +00:00
use lemmy_db_views_actor ::structs ::{
CommunityModeratorView ,
CommunityPersonBanView ,
CommunityView ,
} ;
use lemmy_utils ::{
email ::{ send_email , translations ::Lang } ,
2023-09-21 10:42:28 +00:00
error ::{ LemmyError , LemmyErrorExt , LemmyErrorType } ,
2022-11-28 14:29:33 +00:00
location_info ,
2022-10-27 09:24:07 +00:00
rate_limit ::RateLimitConfig ,
2022-05-03 17:44:13 +00:00
settings ::structs ::Settings ,
2023-02-16 04:05:14 +00:00
utils ::slurs ::build_slur_regex ,
2022-05-03 17:44:13 +00:00
} ;
2022-10-27 09:24:07 +00:00
use regex ::Regex ;
2022-05-03 17:44:13 +00:00
use rosetta_i18n ::{ Language , LanguageId } ;
use tracing ::warn ;
2022-11-28 14:29:33 +00:00
use url ::{ ParseError , Url } ;
2022-05-03 17:44:13 +00:00
#[ tracing::instrument(skip_all) ]
pub async fn is_mod_or_admin (
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
person_id : PersonId ,
community_id : CommunityId ,
) -> Result < ( ) , LemmyError > {
2022-11-09 10:05:00 +00:00
let is_mod_or_admin = CommunityView ::is_mod_or_admin ( pool , person_id , community_id ) . await ? ;
2022-05-03 17:44:13 +00:00
if ! is_mod_or_admin {
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::NotAModOrAdmin ) ?
} else {
Ok ( ( ) )
2022-05-03 17:44:13 +00:00
}
}
2023-03-01 03:46:15 +00:00
#[ tracing::instrument(skip_all) ]
pub async fn is_mod_or_admin_opt (
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2023-03-01 03:46:15 +00:00
local_user_view : Option < & LocalUserView > ,
community_id : Option < CommunityId > ,
) -> Result < ( ) , LemmyError > {
if let Some ( local_user_view ) = local_user_view {
if let Some ( community_id ) = community_id {
is_mod_or_admin ( pool , local_user_view . person . id , community_id ) . await
} else {
is_admin ( local_user_view )
}
} else {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::NotAModOrAdmin ) ?
2023-03-01 03:46:15 +00:00
}
}
2022-05-03 17:44:13 +00:00
pub fn is_admin ( local_user_view : & LocalUserView ) -> Result < ( ) , LemmyError > {
2023-08-24 09:40:08 +00:00
if ! local_user_view . local_user . admin {
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::NotAnAdmin ) ?
} else {
Ok ( ( ) )
2022-05-03 17:44:13 +00:00
}
}
2023-02-28 11:34:50 +00:00
pub fn is_top_mod (
local_user_view : & LocalUserView ,
community_mods : & [ CommunityModeratorView ] ,
) -> Result < ( ) , LemmyError > {
if local_user_view . person . id
! = community_mods
. first ( )
. map ( | cm | cm . moderator . id )
. unwrap_or ( PersonId ( 0 ) )
{
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::NotTopMod ) ?
} else {
Ok ( ( ) )
2023-02-28 11:34:50 +00:00
}
}
2022-05-03 17:44:13 +00:00
#[ tracing::instrument(skip_all) ]
2023-07-11 13:09:59 +00:00
pub async fn get_post ( post_id : PostId , pool : & mut DbPool < '_ > ) -> Result < Post , LemmyError > {
2022-11-09 10:05:00 +00:00
Post ::read ( pool , post_id )
. await
2023-07-10 14:50:07 +00:00
. with_lemmy_type ( LemmyErrorType ::CouldntFindPost )
2022-05-03 17:44:13 +00:00
}
#[ tracing::instrument(skip_all) ]
pub async fn mark_post_as_read (
person_id : PersonId ,
post_id : PostId ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < PostRead , LemmyError > {
let post_read_form = PostReadForm { post_id , person_id } ;
2022-11-09 10:05:00 +00:00
PostRead ::mark_as_read ( pool , & post_read_form )
. await
2023-07-10 14:50:07 +00:00
. with_lemmy_type ( LemmyErrorType ::CouldntMarkPostAsRead )
2022-05-03 17:44:13 +00:00
}
#[ tracing::instrument(skip_all) ]
pub async fn mark_post_as_unread (
person_id : PersonId ,
post_id : PostId ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < usize , LemmyError > {
let post_read_form = PostReadForm { post_id , person_id } ;
2022-11-09 10:05:00 +00:00
PostRead ::mark_as_unread ( pool , & post_read_form )
. await
2023-07-10 14:50:07 +00:00
. with_lemmy_type ( LemmyErrorType ::CouldntMarkPostAsRead )
2022-05-03 17:44:13 +00:00
}
2022-07-28 21:14:07 +00:00
pub fn check_user_valid (
banned : bool ,
2023-08-24 15:27:00 +00:00
ban_expires : Option < DateTime < Utc > > ,
2022-07-28 21:14:07 +00:00
deleted : bool ,
) -> Result < ( ) , LemmyError > {
// Check for a site ban
if is_banned ( banned , ban_expires ) {
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::SiteBan ) ?
2022-07-28 21:14:07 +00:00
}
// check for account deletion
2023-08-31 13:01:08 +00:00
else if deleted {
Err ( LemmyErrorType ::Deleted ) ?
} else {
Ok ( ( ) )
2022-07-28 21:14:07 +00:00
}
}
2022-05-03 17:44:13 +00:00
#[ tracing::instrument(skip_all) ]
pub async fn check_community_ban (
person_id : PersonId ,
community_id : CommunityId ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < ( ) , LemmyError > {
2022-11-09 10:05:00 +00:00
let is_banned = CommunityPersonBanView ::get ( pool , person_id , community_id )
. await
. is_ok ( ) ;
if is_banned {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::BannedFromCommunity ) ?
2022-05-03 17:44:13 +00:00
} else {
Ok ( ( ) )
}
}
#[ tracing::instrument(skip_all) ]
pub async fn check_community_deleted_or_removed (
community_id : CommunityId ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < ( ) , LemmyError > {
2022-11-09 10:05:00 +00:00
let community = Community ::read ( pool , community_id )
. await
2023-07-10 14:50:07 +00:00
. with_lemmy_type ( LemmyErrorType ::CouldntFindCommunity ) ? ;
2022-05-03 17:44:13 +00:00
if community . deleted | | community . removed {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::Deleted ) ?
2022-05-03 17:44:13 +00:00
} else {
Ok ( ( ) )
}
}
pub fn check_post_deleted_or_removed ( post : & Post ) -> Result < ( ) , LemmyError > {
if post . deleted | | post . removed {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::Deleted ) ?
2022-05-03 17:44:13 +00:00
} else {
Ok ( ( ) )
}
}
#[ tracing::instrument(skip_all) ]
pub async fn check_person_block (
my_id : PersonId ,
potential_blocker_id : PersonId ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < ( ) , LemmyError > {
2022-11-09 10:05:00 +00:00
let is_blocked = PersonBlock ::read ( pool , potential_blocker_id , my_id )
. await
. is_ok ( ) ;
if is_blocked {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::PersonIsBlocked ) ?
2022-05-03 17:44:13 +00:00
} else {
Ok ( ( ) )
}
}
#[ tracing::instrument(skip_all) ]
2022-10-27 09:24:07 +00:00
pub fn check_downvotes_enabled ( score : i16 , local_site : & LocalSite ) -> Result < ( ) , LemmyError > {
if score = = - 1 & & ! local_site . enable_downvotes {
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::DownvotesAreDisabled ) ?
} else {
Ok ( ( ) )
2022-05-03 17:44:13 +00:00
}
}
#[ tracing::instrument(skip_all) ]
2022-10-27 09:24:07 +00:00
pub fn check_private_instance (
2022-05-03 17:44:13 +00:00
local_user_view : & Option < LocalUserView > ,
2022-10-27 09:24:07 +00:00
local_site : & LocalSite ,
2022-05-03 17:44:13 +00:00
) -> Result < ( ) , LemmyError > {
2022-10-27 09:24:07 +00:00
if local_user_view . is_none ( ) & & local_site . private_instance {
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::InstanceIsPrivate ) ?
} else {
Ok ( ( ) )
2022-05-03 17:44:13 +00:00
}
}
#[ tracing::instrument(skip_all) ]
pub async fn build_federated_instances (
2022-10-27 09:24:07 +00:00
local_site : & LocalSite ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < Option < FederatedInstances > , LemmyError > {
2022-10-27 09:24:07 +00:00
if local_site . federation_enabled {
// TODO I hate that this requires 3 queries
2023-07-11 13:09:59 +00:00
let ( linked , allowed , blocked ) = lemmy_db_schema ::try_join_with_pool! ( pool = > (
Instance ::linked ,
Instance ::allowlist ,
Instance ::blocklist
) ) ? ;
2022-05-03 17:44:13 +00:00
Ok ( Some ( FederatedInstances {
linked ,
allowed ,
blocked ,
} ) )
} else {
Ok ( None )
}
}
/// Checks the password length
pub fn password_length_check ( pass : & str ) -> Result < ( ) , LemmyError > {
2022-11-05 00:56:38 +00:00
if ! ( 10 ..= 60 ) . contains ( & pass . chars ( ) . count ( ) ) {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::InvalidPassword ) ?
2022-05-03 17:44:13 +00:00
} else {
Ok ( ( ) )
}
}
/// Checks for a honeypot. If this field is filled, fail the rest of the function
pub fn honeypot_check ( honeypot : & Option < String > ) -> Result < ( ) , LemmyError > {
2022-12-01 21:33:59 +00:00
if honeypot . is_some ( ) & & honeypot ! = & Some ( String ::new ( ) ) {
2023-07-10 14:50:07 +00:00
Err ( LemmyErrorType ::HoneypotFailed ) ?
2022-05-03 17:44:13 +00:00
} else {
Ok ( ( ) )
}
}
2023-07-10 12:04:39 +00:00
pub async fn send_email_to_user (
2022-05-03 17:44:13 +00:00
local_user_view : & LocalUserView ,
subject : & str ,
body : & str ,
settings : & Settings ,
) {
if local_user_view . person . banned | | ! local_user_view . local_user . send_notifications_to_email {
return ;
}
if let Some ( user_email ) = & local_user_view . local_user . email {
match send_email (
subject ,
user_email ,
& local_user_view . person . name ,
body ,
settings ,
2023-07-10 12:04:39 +00:00
)
. await
{
2022-05-03 17:44:13 +00:00
Ok ( _o ) = > _o ,
Err ( e ) = > warn! ( " {} " , e ) ,
} ;
}
}
pub async fn send_password_reset_email (
user : & LocalUserView ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
settings : & Settings ,
) -> Result < ( ) , LemmyError > {
// Generate a random token
2023-02-16 04:05:14 +00:00
let token = uuid ::Uuid ::new_v4 ( ) . to_string ( ) ;
2022-05-03 17:44:13 +00:00
// Insert the row
let local_user_id = user . local_user . id ;
2023-08-02 17:02:53 +00:00
PasswordResetRequest ::create_token ( pool , local_user_id , token . clone ( ) ) . await ? ;
2022-05-03 17:44:13 +00:00
2022-11-19 04:33:54 +00:00
let email = & user . local_user . email . clone ( ) . expect ( " email " ) ;
2022-08-18 19:11:19 +00:00
let lang = get_interface_language ( user ) ;
2022-05-03 17:44:13 +00:00
let subject = & lang . password_reset_subject ( & user . person . name ) ;
let protocol_and_hostname = settings . get_protocol_and_hostname ( ) ;
let reset_link = format! ( " {} /password_change/ {} " , protocol_and_hostname , & token ) ;
2022-05-13 16:24:29 +00:00
let body = & lang . password_reset_body ( reset_link , & user . person . name ) ;
2023-07-10 12:04:39 +00:00
send_email ( subject , email , & user . person . name , body , settings ) . await
2022-05-03 17:44:13 +00:00
}
/// Send a verification email
pub async fn send_verification_email (
user : & LocalUserView ,
new_email : & str ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
settings : & Settings ,
) -> Result < ( ) , LemmyError > {
let form = EmailVerificationForm {
local_user_id : user . local_user . id ,
email : new_email . to_string ( ) ,
2023-02-16 04:05:14 +00:00
verification_token : uuid ::Uuid ::new_v4 ( ) . to_string ( ) ,
2022-05-03 17:44:13 +00:00
} ;
let verify_link = format! (
" {}/verify_email/{} " ,
settings . get_protocol_and_hostname ( ) ,
& form . verification_token
) ;
2022-11-09 10:05:00 +00:00
EmailVerification ::create ( pool , & form ) . await ? ;
2022-05-03 17:44:13 +00:00
2022-08-18 19:11:19 +00:00
let lang = get_interface_language ( user ) ;
2022-05-03 17:44:13 +00:00
let subject = lang . verify_email_subject ( & settings . hostname ) ;
2022-05-13 16:24:29 +00:00
let body = lang . verify_email_body ( & settings . hostname , & user . person . name , verify_link ) ;
2023-07-10 12:04:39 +00:00
send_email ( & subject , new_email , & user . person . name , & body , settings ) . await ? ;
2022-05-03 17:44:13 +00:00
Ok ( ( ) )
}
2022-08-18 19:11:19 +00:00
pub fn get_interface_language ( user : & LocalUserView ) -> Lang {
2022-09-27 16:48:44 +00:00
lang_str_to_lang ( & user . local_user . interface_language )
}
2023-03-01 17:19:46 +00:00
pub fn get_interface_language_from_settings ( user : & LocalUserView ) -> Lang {
2022-09-27 16:48:44 +00:00
lang_str_to_lang ( & user . local_user . interface_language )
}
fn lang_str_to_lang ( lang : & str ) -> Lang {
let lang_id = LanguageId ::new ( lang ) ;
Lang ::from_language_id ( & lang_id ) . unwrap_or_else ( | | {
2022-05-03 17:44:13 +00:00
let en = LanguageId ::new ( " en " ) ;
Lang ::from_language_id ( & en ) . expect ( " default language " )
} )
}
2022-10-27 09:24:07 +00:00
pub fn local_site_rate_limit_to_rate_limit_config (
local_site_rate_limit : & LocalSiteRateLimit ,
) -> RateLimitConfig {
let l = local_site_rate_limit ;
RateLimitConfig {
message : l . message ,
message_per_second : l . message_per_second ,
post : l . post ,
post_per_second : l . post_per_second ,
register : l . register ,
register_per_second : l . register_per_second ,
image : l . image ,
image_per_second : l . image_per_second ,
comment : l . comment ,
comment_per_second : l . comment_per_second ,
search : l . search ,
search_per_second : l . search_per_second ,
}
}
pub fn local_site_to_slur_regex ( local_site : & LocalSite ) -> Option < Regex > {
build_slur_regex ( local_site . slur_filter_regex . as_deref ( ) )
}
pub fn local_site_opt_to_slur_regex ( local_site : & Option < LocalSite > ) -> Option < Regex > {
local_site
. as_ref ( )
. map ( local_site_to_slur_regex )
. unwrap_or ( None )
}
2023-06-30 10:42:42 +00:00
pub fn local_site_opt_to_sensitive ( local_site : & Option < LocalSite > ) -> bool {
local_site
. as_ref ( )
. map ( | site | site . enable_nsfw )
. unwrap_or ( false )
}
2023-07-10 12:04:39 +00:00
pub async fn send_application_approved_email (
2022-05-03 17:44:13 +00:00
user : & LocalUserView ,
settings : & Settings ,
) -> Result < ( ) , LemmyError > {
2022-11-19 04:33:54 +00:00
let email = & user . local_user . email . clone ( ) . expect ( " email " ) ;
2022-08-18 19:11:19 +00:00
let lang = get_interface_language ( user ) ;
2022-05-03 17:44:13 +00:00
let subject = lang . registration_approved_subject ( & user . person . actor_id ) ;
let body = lang . registration_approved_body ( & settings . hostname ) ;
2023-07-10 12:04:39 +00:00
send_email ( & subject , email , & user . person . name , & body , settings ) . await
2022-05-03 17:44:13 +00:00
}
2022-09-27 16:48:44 +00:00
/// Send a new applicant email notification to all admins
pub async fn send_new_applicant_email_to_admins (
applicant_username : & str ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-09-27 16:48:44 +00:00
settings : & Settings ,
) -> Result < ( ) , LemmyError > {
// Collect the admins with emails
2023-03-01 17:19:46 +00:00
let admins = LocalUserView ::list_admins_with_emails ( pool ) . await ? ;
2022-09-27 16:48:44 +00:00
let applications_link = & format! (
" {}/registration_applications " ,
settings . get_protocol_and_hostname ( ) ,
) ;
for admin in & admins {
2022-11-19 04:33:54 +00:00
let email = & admin . local_user . email . clone ( ) . expect ( " email " ) ;
2022-09-27 16:48:44 +00:00
let lang = get_interface_language_from_settings ( admin ) ;
2023-02-03 14:24:19 +00:00
let subject = lang . new_application_subject ( & settings . hostname , applicant_username ) ;
2022-09-27 16:48:44 +00:00
let body = lang . new_application_body ( applications_link ) ;
2023-07-10 12:04:39 +00:00
send_email ( & subject , email , & admin . person . name , & body , settings ) . await ? ;
2022-09-27 16:48:44 +00:00
}
Ok ( ( ) )
}
2023-02-14 15:57:08 +00:00
/// Send a report to all admins
pub async fn send_new_report_email_to_admins (
reporter_username : & str ,
reported_username : & str ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2023-02-14 15:57:08 +00:00
settings : & Settings ,
) -> Result < ( ) , LemmyError > {
// Collect the admins with emails
2023-03-01 17:19:46 +00:00
let admins = LocalUserView ::list_admins_with_emails ( pool ) . await ? ;
2023-02-14 15:57:08 +00:00
let reports_link = & format! ( " {} /reports " , settings . get_protocol_and_hostname ( ) , ) ;
for admin in & admins {
let email = & admin . local_user . email . clone ( ) . expect ( " email " ) ;
let lang = get_interface_language_from_settings ( admin ) ;
2023-06-20 15:24:04 +00:00
let subject = lang . new_report_subject ( & settings . hostname , reported_username , reporter_username ) ;
2023-02-14 15:57:08 +00:00
let body = lang . new_report_body ( reports_link ) ;
2023-07-10 12:04:39 +00:00
send_email ( & subject , email , & admin . person . name , & body , settings ) . await ? ;
2023-02-14 15:57:08 +00:00
}
Ok ( ( ) )
}
2022-10-27 09:24:07 +00:00
pub fn check_private_instance_and_federation_enabled (
local_site : & LocalSite ,
2022-05-03 17:44:13 +00:00
) -> Result < ( ) , LemmyError > {
2022-10-27 09:24:07 +00:00
if local_site . private_instance & & local_site . federation_enabled {
2023-08-31 13:01:08 +00:00
Err ( LemmyErrorType ::CantEnablePrivateInstanceAndFederationTogether ) ?
} else {
Ok ( ( ) )
2022-05-03 17:44:13 +00:00
}
}
2022-06-13 19:15:04 +00:00
pub async fn purge_image_posts_for_person (
banned_person_id : PersonId ,
2023-08-28 10:23:45 +00:00
context : & LemmyContext ,
2022-06-13 19:15:04 +00:00
) -> Result < ( ) , LemmyError > {
2023-08-28 10:23:45 +00:00
let pool = & mut context . pool ( ) ;
2022-11-09 10:05:00 +00:00
let posts = Post ::fetch_pictrs_posts_for_creator ( pool , banned_person_id ) . await ? ;
2022-06-13 19:15:04 +00:00
for post in posts {
if let Some ( url ) = post . url {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & url , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
if let Some ( thumbnail_url ) = post . thumbnail_url {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & thumbnail_url , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
}
2022-11-09 10:05:00 +00:00
Post ::remove_pictrs_post_images_and_thumbnails_for_creator ( pool , banned_person_id ) . await ? ;
2022-06-13 19:15:04 +00:00
Ok ( ( ) )
}
pub async fn purge_image_posts_for_community (
banned_community_id : CommunityId ,
2023-08-28 10:23:45 +00:00
context : & LemmyContext ,
2022-06-13 19:15:04 +00:00
) -> Result < ( ) , LemmyError > {
2023-08-28 10:23:45 +00:00
let pool = & mut context . pool ( ) ;
2022-11-09 10:05:00 +00:00
let posts = Post ::fetch_pictrs_posts_for_community ( pool , banned_community_id ) . await ? ;
2022-06-13 19:15:04 +00:00
for post in posts {
if let Some ( url ) = post . url {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & url , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
if let Some ( thumbnail_url ) = post . thumbnail_url {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & thumbnail_url , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
}
2022-11-09 10:05:00 +00:00
Post ::remove_pictrs_post_images_and_thumbnails_for_community ( pool , banned_community_id ) . await ? ;
2022-06-13 19:15:04 +00:00
Ok ( ( ) )
}
pub async fn remove_user_data (
banned_person_id : PersonId ,
2023-08-28 10:23:45 +00:00
context : & LemmyContext ,
2022-06-13 19:15:04 +00:00
) -> Result < ( ) , LemmyError > {
2023-08-28 10:23:45 +00:00
let pool = & mut context . pool ( ) ;
2022-06-13 19:15:04 +00:00
// Purge user images
2022-11-09 10:05:00 +00:00
let person = Person ::read ( pool , banned_person_id ) . await ? ;
2022-06-13 19:15:04 +00:00
if let Some ( avatar ) = person . avatar {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & avatar , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
if let Some ( banner ) = person . banner {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & banner , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
// Update the fields to None
2022-11-09 10:05:00 +00:00
Person ::update (
pool ,
banned_person_id ,
2023-08-08 09:41:41 +00:00
& PersonUpdateForm {
avatar : Some ( None ) ,
banner : Some ( None ) ,
.. Default ::default ( )
} ,
2022-11-09 10:05:00 +00:00
)
. await ? ;
2022-06-13 19:15:04 +00:00
2022-05-03 17:44:13 +00:00
// Posts
2022-11-09 10:05:00 +00:00
Post ::update_removed_for_creator ( pool , banned_person_id , None , true ) . await ? ;
2022-05-03 17:44:13 +00:00
2022-06-13 19:15:04 +00:00
// Purge image posts
2023-08-28 10:23:45 +00:00
purge_image_posts_for_person ( banned_person_id , context ) . await ? ;
2022-06-13 19:15:04 +00:00
2022-05-03 17:44:13 +00:00
// Communities
// Remove all communities where they're the top mod
// for now, remove the communities manually
2022-11-09 10:05:00 +00:00
let first_mod_communities = CommunityModeratorView ::get_community_first_mods ( pool ) . await ? ;
2022-05-03 17:44:13 +00:00
// Filter to only this banned users top communities
let banned_user_first_communities : Vec < CommunityModeratorView > = first_mod_communities
. into_iter ( )
. filter ( | fmc | fmc . moderator . id = = banned_person_id )
. collect ( ) ;
for first_mod_community in banned_user_first_communities {
2022-06-13 19:15:04 +00:00
let community_id = first_mod_community . community . id ;
2022-11-09 10:05:00 +00:00
Community ::update (
pool ,
community_id ,
2023-08-08 09:41:41 +00:00
& CommunityUpdateForm {
removed : Some ( true ) ,
.. Default ::default ( )
} ,
2022-11-09 10:05:00 +00:00
)
. await ? ;
2022-06-13 19:15:04 +00:00
// Delete the community images
if let Some ( icon ) = first_mod_community . community . icon {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & icon , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
if let Some ( banner ) = first_mod_community . community . banner {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & banner , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
// Update the fields to None
2022-11-09 10:05:00 +00:00
Community ::update (
pool ,
community_id ,
2023-08-08 09:41:41 +00:00
& CommunityUpdateForm {
icon : Some ( None ) ,
banner : Some ( None ) ,
.. Default ::default ( )
} ,
2022-11-09 10:05:00 +00:00
)
. await ? ;
2022-05-03 17:44:13 +00:00
}
// Comments
2022-11-09 10:05:00 +00:00
Comment ::update_removed_for_creator ( pool , banned_person_id , true ) . await ? ;
2022-05-03 17:44:13 +00:00
Ok ( ( ) )
}
pub async fn remove_user_data_in_community (
community_id : CommunityId ,
banned_person_id : PersonId ,
2023-07-11 13:09:59 +00:00
pool : & mut DbPool < '_ > ,
2022-05-03 17:44:13 +00:00
) -> Result < ( ) , LemmyError > {
// Posts
2022-11-09 10:05:00 +00:00
Post ::update_removed_for_creator ( pool , banned_person_id , Some ( community_id ) , true ) . await ? ;
2022-05-03 17:44:13 +00:00
// Comments
// TODO Diesel doesn't allow updates with joins, so this has to be a loop
2023-07-17 10:20:25 +00:00
let comments = CommentQuery {
creator_id : Some ( banned_person_id ) ,
community_id : Some ( community_id ) ,
.. Default ::default ( )
}
. list ( pool )
. await ? ;
2022-05-03 17:44:13 +00:00
for comment_view in & comments {
let comment_id = comment_view . comment . id ;
2022-11-09 10:05:00 +00:00
Comment ::update (
pool ,
comment_id ,
2023-08-08 09:41:41 +00:00
& CommentUpdateForm {
removed : Some ( true ) ,
.. Default ::default ( )
} ,
2022-11-09 10:05:00 +00:00
)
. await ? ;
2022-05-03 17:44:13 +00:00
}
Ok ( ( ) )
}
2023-08-28 10:23:45 +00:00
pub async fn purge_user_account (
2022-06-13 19:15:04 +00:00
person_id : PersonId ,
2023-08-28 10:23:45 +00:00
context : & LemmyContext ,
2022-06-13 19:15:04 +00:00
) -> Result < ( ) , LemmyError > {
2023-08-28 10:23:45 +00:00
let pool = & mut context . pool ( ) ;
2022-06-13 19:15:04 +00:00
// Delete their images
2022-11-09 10:05:00 +00:00
let person = Person ::read ( pool , person_id ) . await ? ;
2022-06-13 19:15:04 +00:00
if let Some ( avatar ) = person . avatar {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & avatar , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
if let Some ( banner ) = person . banner {
2023-08-28 10:23:45 +00:00
purge_image_from_pictrs ( & banner , context ) . await . ok ( ) ;
2022-06-13 19:15:04 +00:00
}
// No need to update avatar and banner, those are handled in Person::delete_account
2022-05-03 17:44:13 +00:00
// Comments
2022-11-09 10:05:00 +00:00
Comment ::permadelete_for_creator ( pool , person_id )
. await
2023-07-10 14:50:07 +00:00
. with_lemmy_type ( LemmyErrorType ::CouldntUpdateComment ) ? ;
2022-05-03 17:44:13 +00:00
// Posts
2022-11-09 10:05:00 +00:00
Post ::permadelete_for_creator ( pool , person_id )
. await
2023-07-10 14:50:07 +00:00
. with_lemmy_type ( LemmyErrorType ::CouldntUpdatePost ) ? ;
2022-05-03 17:44:13 +00:00
2022-06-13 19:15:04 +00:00
// Purge image posts
2023-08-28 10:23:45 +00:00
purge_image_posts_for_person ( person_id , context ) . await ? ;
2022-06-13 19:15:04 +00:00
2023-04-15 23:41:05 +00:00
// Leave communities they mod
CommunityModerator ::leave_all_communities ( pool , person_id ) . await ? ;
2022-11-09 10:05:00 +00:00
Person ::delete_account ( pool , person_id ) . await ? ;
2022-05-03 17:44:13 +00:00
Ok ( ( ) )
}
2022-07-08 10:21:33 +00:00
2022-11-28 14:29:33 +00:00
pub enum EndpointType {
Community ,
Person ,
Post ,
Comment ,
PrivateMessage ,
}
/// Generates an apub endpoint for a given domain, IE xyz.tld
pub fn generate_local_apub_endpoint (
endpoint_type : EndpointType ,
name : & str ,
domain : & str ,
) -> Result < DbUrl , ParseError > {
let point = match endpoint_type {
EndpointType ::Community = > " c " ,
EndpointType ::Person = > " u " ,
EndpointType ::Post = > " post " ,
EndpointType ::Comment = > " comment " ,
EndpointType ::PrivateMessage = > " private_message " ,
} ;
2023-01-30 19:17:24 +00:00
Ok ( Url ::parse ( & format! ( " {domain} / {point} / {name} " ) ) ? . into ( ) )
2022-11-28 14:29:33 +00:00
}
pub fn generate_followers_url ( actor_id : & DbUrl ) -> Result < DbUrl , ParseError > {
2023-01-30 19:17:24 +00:00
Ok ( Url ::parse ( & format! ( " {actor_id} /followers " ) ) ? . into ( ) )
2022-11-28 14:29:33 +00:00
}
pub fn generate_inbox_url ( actor_id : & DbUrl ) -> Result < DbUrl , ParseError > {
2023-01-30 19:17:24 +00:00
Ok ( Url ::parse ( & format! ( " {actor_id} /inbox " ) ) ? . into ( ) )
2022-11-28 14:29:33 +00:00
}
pub fn generate_site_inbox_url ( actor_id : & DbUrl ) -> Result < DbUrl , ParseError > {
let mut actor_id : Url = actor_id . clone ( ) . into ( ) ;
actor_id . set_path ( " site_inbox " ) ;
Ok ( actor_id . into ( ) )
}
pub fn generate_shared_inbox_url ( actor_id : & DbUrl ) -> Result < DbUrl , LemmyError > {
let actor_id : Url = actor_id . clone ( ) . into ( ) ;
let url = format! (
" {}://{}{}/inbox " ,
& actor_id . scheme ( ) ,
& actor_id . host_str ( ) . context ( location_info! ( ) ) ? ,
if let Some ( port ) = actor_id . port ( ) {
2023-01-30 19:17:24 +00:00
format! ( " : {port} " )
2022-11-28 14:29:33 +00:00
} else {
String ::new ( )
} ,
) ;
Ok ( Url ::parse ( & url ) ? . into ( ) )
}
pub fn generate_outbox_url ( actor_id : & DbUrl ) -> Result < DbUrl , ParseError > {
2023-01-30 19:17:24 +00:00
Ok ( Url ::parse ( & format! ( " {actor_id} /outbox " ) ) ? . into ( ) )
2022-11-28 14:29:33 +00:00
}
2023-02-18 14:50:28 +00:00
pub fn generate_featured_url ( actor_id : & DbUrl ) -> Result < DbUrl , ParseError > {
Ok ( Url ::parse ( & format! ( " {actor_id} /featured " ) ) ? . into ( ) )
}
2022-11-28 14:29:33 +00:00
pub fn generate_moderators_url ( community_id : & DbUrl ) -> Result < DbUrl , LemmyError > {
2023-01-30 19:17:24 +00:00
Ok ( Url ::parse ( & format! ( " {community_id} /moderators " ) ) ? . into ( ) )
2022-11-28 14:29:33 +00:00
}
2023-07-26 18:01:15 +00:00
2023-09-06 14:56:26 +00:00
/// Replace special HTML characters in API parameters to prevent XSS attacks.
///
/// Taken from https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md#output-encoding-for-html-contexts
///
/// `>` is left in place because it is interpreted as markdown quote.
pub fn sanitize_html_api ( data : & str ) -> String {
data
. replace ( '&' , " & " )
. replace ( '<' , " < " )
. replace ( '\"' , " " " )
. replace ( '\'' , " ' " )
2023-07-26 18:01:15 +00:00
}
2023-09-06 14:56:26 +00:00
pub fn sanitize_html_api_opt ( data : & Option < String > ) -> Option < String > {
data . as_ref ( ) . map ( | d | sanitize_html_api ( d ) )
}
/// Replace special HTML characters in federation parameters to prevent XSS attacks.
///
/// Unlike [sanitize_html_api()] it leaves `&` in place to avoid double escaping.
pub fn sanitize_html_federation ( data : & str ) -> String {
data
. replace ( '<' , " < " )
. replace ( '\"' , " " " )
. replace ( '\'' , " ' " )
}
pub fn sanitize_html_federation_opt ( data : & Option < String > ) -> Option < String > {
data . as_ref ( ) . map ( | d | sanitize_html_federation ( d ) )
2023-07-26 18:01:15 +00:00
}
#[ cfg(test) ]
mod tests {
#![ allow(clippy::unwrap_used) ]
#![ allow(clippy::indexing_slicing) ]
2023-09-06 14:56:26 +00:00
use crate ::utils ::{ honeypot_check , password_length_check } ;
2023-07-26 18:01:15 +00:00
#[ test ]
#[ rustfmt::skip ]
fn password_length ( ) {
assert! ( password_length_check ( " Õ¼¾°3yË,o¸ ãtÌÈú|ÇÁÙAøüÒI©·¤(T]/ð>æºWæ[C¤bªWöaÃÎñ·{=û³&§½K/c " ) . is_ok ( ) ) ;
assert! ( password_length_check ( " 1234567890 " ) . is_ok ( ) ) ;
assert! ( password_length_check ( " short " ) . is_err ( ) ) ;
assert! ( password_length_check ( " looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong " ) . is_err ( ) ) ;
}
#[ test ]
fn honeypot ( ) {
assert! ( honeypot_check ( & None ) . is_ok ( ) ) ;
assert! ( honeypot_check ( & Some ( String ::new ( ) ) ) . is_ok ( ) ) ;
assert! ( honeypot_check ( & Some ( " 1 " . to_string ( ) ) ) . is_err ( ) ) ;
assert! ( honeypot_check ( & Some ( " message " . to_string ( ) ) ) . is_err ( ) ) ;
}
}