1 use crate::{request::purge_image_from_pictrs, sensitive::Sensitive, site::FederatedInstances};
2 use chrono::NaiveDateTime;
4 impls::person::is_banned,
5 newtypes::{CommunityId, LocalUserId, PersonId, PostId},
7 comment::{Comment, CommentUpdateForm},
8 community::{Community, CommunityUpdateForm},
9 email_verification::{EmailVerification, EmailVerificationForm},
11 local_site::LocalSite,
12 local_site_rate_limit::LocalSiteRateLimit,
13 password_reset_request::PasswordResetRequest,
14 person::{Person, PersonUpdateForm},
15 person_block::PersonBlock,
16 post::{Post, PostRead, PostReadForm},
17 registration_application::RegistrationApplication,
20 traits::{Crud, Readable},
25 comment_view::CommentQuery,
26 structs::{LocalUserSettingsView, LocalUserView},
28 use lemmy_db_views_actor::structs::{
29 CommunityModeratorView,
30 CommunityPersonBanView,
35 email::{send_email, translations::Lang},
37 rate_limit::RateLimitConfig,
38 settings::structs::Settings,
39 utils::{build_slur_regex, generate_random_string},
42 use reqwest_middleware::ClientWithMiddleware;
43 use rosetta_i18n::{Language, LanguageId};
44 use std::str::FromStr;
47 pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
49 F: FnOnce(&mut diesel::PgConnection) -> T + Send + 'static,
52 let pool = pool.clone();
53 let blocking_span = tracing::info_span!("blocking operation");
54 actix_web::web::block(move || {
55 let entered = blocking_span.enter();
56 let mut conn = pool.get()?;
57 let res = (f)(&mut conn);
59 Ok(res) as Result<T, LemmyError>
64 #[tracing::instrument(skip_all)]
65 pub async fn is_mod_or_admin(
68 community_id: CommunityId,
69 ) -> Result<(), LemmyError> {
70 let is_mod_or_admin = blocking(pool, move |conn| {
71 CommunityView::is_mod_or_admin(conn, person_id, community_id)
75 return Err(LemmyError::from_message("not_a_mod_or_admin"));
80 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
81 if !local_user_view.person.admin {
82 return Err(LemmyError::from_message("not_an_admin"));
87 #[tracing::instrument(skip_all)]
88 pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
89 blocking(pool, move |conn| Post::read(conn, post_id))
91 .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))
94 #[tracing::instrument(skip_all)]
95 pub async fn mark_post_as_read(
99 ) -> Result<PostRead, LemmyError> {
100 let post_read_form = PostReadForm { post_id, person_id };
102 blocking(pool, move |conn| {
103 PostRead::mark_as_read(conn, &post_read_form)
106 .map_err(|e| LemmyError::from_error_message(e, "couldnt_mark_post_as_read"))
109 #[tracing::instrument(skip_all)]
110 pub async fn mark_post_as_unread(
114 ) -> Result<usize, LemmyError> {
115 let post_read_form = PostReadForm { post_id, person_id };
117 blocking(pool, move |conn| {
118 PostRead::mark_as_unread(conn, &post_read_form)
121 .map_err(|e| LemmyError::from_error_message(e, "couldnt_mark_post_as_read"))
124 #[tracing::instrument(skip_all)]
125 pub async fn get_local_user_view_from_jwt(
129 ) -> Result<LocalUserView, LemmyError> {
130 let claims = Claims::decode(jwt, &secret.jwt_secret)
131 .map_err(|e| e.with_message("not_logged_in"))?
133 let local_user_id = LocalUserId(claims.sub);
134 let local_user_view =
135 blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
137 local_user_view.person.banned,
138 local_user_view.person.ban_expires,
139 local_user_view.person.deleted,
142 check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
147 /// Checks if user's token was issued before user's password reset.
148 pub fn check_validator_time(
149 validator_time: &NaiveDateTime,
151 ) -> Result<(), LemmyError> {
152 let user_validation_time = validator_time.timestamp();
153 if user_validation_time > claims.iat {
154 Err(LemmyError::from_message("not_logged_in"))
160 #[tracing::instrument(skip_all)]
161 pub async fn get_local_user_view_from_jwt_opt(
162 jwt: Option<&Sensitive<String>>,
165 ) -> Result<Option<LocalUserView>, LemmyError> {
167 Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool, secret).await?)),
172 #[tracing::instrument(skip_all)]
173 pub async fn get_local_user_settings_view_from_jwt_opt(
174 jwt: Option<&Sensitive<String>>,
177 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
180 let claims = Claims::decode(jwt.as_ref(), &secret.jwt_secret)
181 .map_err(|e| e.with_message("not_logged_in"))?
183 let local_user_id = LocalUserId(claims.sub);
184 let local_user_view = blocking(pool, move |conn| {
185 LocalUserSettingsView::read(conn, local_user_id)
189 local_user_view.person.banned,
190 local_user_view.person.ban_expires,
191 local_user_view.person.deleted,
194 check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
196 Ok(Some(local_user_view))
201 pub fn check_user_valid(
203 ban_expires: Option<NaiveDateTime>,
205 ) -> Result<(), LemmyError> {
206 // Check for a site ban
207 if is_banned(banned, ban_expires) {
208 return Err(LemmyError::from_message("site_ban"));
211 // check for account deletion
213 return Err(LemmyError::from_message("deleted"));
219 #[tracing::instrument(skip_all)]
220 pub async fn check_community_ban(
222 community_id: CommunityId,
224 ) -> Result<(), LemmyError> {
226 move |conn: &mut _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
227 if blocking(pool, is_banned).await? {
228 Err(LemmyError::from_message("community_ban"))
234 #[tracing::instrument(skip_all)]
235 pub async fn check_community_deleted_or_removed(
236 community_id: CommunityId,
238 ) -> Result<(), LemmyError> {
239 let community = blocking(pool, move |conn| Community::read(conn, community_id))
241 .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
242 if community.deleted || community.removed {
243 Err(LemmyError::from_message("deleted"))
249 pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
250 if post.deleted || post.removed {
251 Err(LemmyError::from_message("deleted"))
257 #[tracing::instrument(skip_all)]
258 pub async fn check_person_block(
260 potential_blocker_id: PersonId,
262 ) -> Result<(), LemmyError> {
263 let is_blocked = move |conn: &mut _| PersonBlock::read(conn, potential_blocker_id, my_id).is_ok();
264 if blocking(pool, is_blocked).await? {
265 Err(LemmyError::from_message("person_block"))
271 #[tracing::instrument(skip_all)]
272 pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> {
273 if score == -1 && !local_site.enable_downvotes {
274 return Err(LemmyError::from_message("downvotes_disabled"));
279 #[tracing::instrument(skip_all)]
280 pub fn check_private_instance(
281 local_user_view: &Option<LocalUserView>,
282 local_site: &LocalSite,
283 ) -> Result<(), LemmyError> {
284 if local_user_view.is_none() && local_site.private_instance {
285 return Err(LemmyError::from_message("instance_is_private"));
290 #[tracing::instrument(skip_all)]
291 pub async fn build_federated_instances(
292 local_site: &LocalSite,
294 ) -> Result<Option<FederatedInstances>, LemmyError> {
295 if local_site.federation_enabled {
296 // TODO I hate that this requires 3 queries
297 let linked = blocking(pool, Instance::linked).await??;
298 let allowed = blocking(pool, Instance::allowlist).await??;
299 let blocked = blocking(pool, Instance::blocklist).await??;
301 // These can return empty vectors, so convert them to options
302 let allowed = (!allowed.is_empty()).then(|| allowed);
303 let blocked = (!blocked.is_empty()).then(|| blocked);
305 Ok(Some(FederatedInstances {
315 /// Checks the password length
316 pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
317 if !(10..=60).contains(&pass.len()) {
318 Err(LemmyError::from_message("invalid_password"))
324 /// Checks the site description length
325 pub fn site_description_length_check(description: &str) -> Result<(), LemmyError> {
326 if description.len() > 150 {
327 Err(LemmyError::from_message("site_description_length_overflow"))
333 /// Checks for a honeypot. If this field is filled, fail the rest of the function
334 pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
335 if honeypot.is_some() {
336 Err(LemmyError::from_message("honeypot_fail"))
342 pub fn send_email_to_user(
343 local_user_view: &LocalUserView,
348 if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
352 if let Some(user_email) = &local_user_view.local_user.email {
356 &local_user_view.person.name,
361 Err(e) => warn!("{}", e),
366 pub async fn send_password_reset_email(
367 user: &LocalUserView,
370 ) -> Result<(), LemmyError> {
371 // Generate a random token
372 let token = generate_random_string();
375 let token2 = token.clone();
376 let local_user_id = user.local_user.id;
377 blocking(pool, move |conn| {
378 PasswordResetRequest::create_token(conn, local_user_id, &token2)
382 let email = &user.local_user.email.to_owned().expect("email");
383 let lang = get_interface_language(user);
384 let subject = &lang.password_reset_subject(&user.person.name);
385 let protocol_and_hostname = settings.get_protocol_and_hostname();
386 let reset_link = format!("{}/password_change/{}", protocol_and_hostname, &token);
387 let body = &lang.password_reset_body(reset_link, &user.person.name);
388 send_email(subject, email, &user.person.name, body, settings)
391 /// Send a verification email
392 pub async fn send_verification_email(
393 user: &LocalUserView,
397 ) -> Result<(), LemmyError> {
398 let form = EmailVerificationForm {
399 local_user_id: user.local_user.id,
400 email: new_email.to_string(),
401 verification_token: generate_random_string(),
403 let verify_link = format!(
404 "{}/verify_email/{}",
405 settings.get_protocol_and_hostname(),
406 &form.verification_token
408 blocking(pool, move |conn| EmailVerification::create(conn, &form)).await??;
410 let lang = get_interface_language(user);
411 let subject = lang.verify_email_subject(&settings.hostname);
412 let body = lang.verify_email_body(&settings.hostname, &user.person.name, verify_link);
413 send_email(&subject, new_email, &user.person.name, &body, settings)?;
418 pub fn send_email_verification_success(
419 user: &LocalUserView,
421 ) -> Result<(), LemmyError> {
422 let email = &user.local_user.email.to_owned().expect("email");
423 let lang = get_interface_language(user);
424 let subject = &lang.email_verified_subject(&user.person.actor_id);
425 let body = &lang.email_verified_body();
426 send_email(subject, email, &user.person.name, body, settings)
429 pub fn get_interface_language(user: &LocalUserView) -> Lang {
430 lang_str_to_lang(&user.local_user.interface_language)
433 pub fn get_interface_language_from_settings(user: &LocalUserSettingsView) -> Lang {
434 lang_str_to_lang(&user.local_user.interface_language)
437 fn lang_str_to_lang(lang: &str) -> Lang {
438 let lang_id = LanguageId::new(lang);
439 Lang::from_language_id(&lang_id).unwrap_or_else(|| {
440 let en = LanguageId::new("en");
441 Lang::from_language_id(&en).expect("default language")
445 pub fn local_site_rate_limit_to_rate_limit_config(
446 local_site_rate_limit: &LocalSiteRateLimit,
447 ) -> RateLimitConfig {
448 let l = local_site_rate_limit;
451 message_per_second: l.message_per_second,
453 post_per_second: l.post_per_second,
454 register: l.register,
455 register_per_second: l.register_per_second,
457 image_per_second: l.image_per_second,
459 comment_per_second: l.comment_per_second,
461 search_per_second: l.search_per_second,
465 pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<Regex> {
466 build_slur_regex(local_site.slur_filter_regex.as_deref())
469 pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<Regex> {
472 .map(local_site_to_slur_regex)
476 pub fn send_application_approved_email(
477 user: &LocalUserView,
479 ) -> Result<(), LemmyError> {
480 let email = &user.local_user.email.to_owned().expect("email");
481 let lang = get_interface_language(user);
482 let subject = lang.registration_approved_subject(&user.person.actor_id);
483 let body = lang.registration_approved_body(&settings.hostname);
484 send_email(&subject, email, &user.person.name, &body, settings)
487 /// Send a new applicant email notification to all admins
488 pub async fn send_new_applicant_email_to_admins(
489 applicant_username: &str,
492 ) -> Result<(), LemmyError> {
493 // Collect the admins with emails
494 let admins = blocking(pool, move |conn| {
495 LocalUserSettingsView::list_admins_with_emails(conn)
499 let applications_link = &format!(
500 "{}/registration_applications",
501 settings.get_protocol_and_hostname(),
504 for admin in &admins {
505 let email = &admin.local_user.email.to_owned().expect("email");
506 let lang = get_interface_language_from_settings(admin);
507 let subject = lang.new_application_subject(applicant_username, &settings.hostname);
508 let body = lang.new_application_body(applications_link);
509 send_email(&subject, email, &admin.person.name, &body, settings)?;
514 pub async fn check_registration_application(
515 local_user_view: &LocalUserView,
516 local_site: &LocalSite,
518 ) -> Result<(), LemmyError> {
519 if local_site.require_application
520 && !local_user_view.local_user.accepted_application
521 && !local_user_view.person.admin
523 // Fetch the registration, see if its denied
524 let local_user_id = local_user_view.local_user.id;
525 let registration = blocking(pool, move |conn| {
526 RegistrationApplication::find_by_local_user_id(conn, local_user_id)
529 if let Some(deny_reason) = registration.deny_reason {
530 let lang = get_interface_language(local_user_view);
531 let registration_denied_message = format!("{}: {}", lang.registration_denied(), &deny_reason);
532 return Err(LemmyError::from_message(®istration_denied_message));
534 return Err(LemmyError::from_message("registration_application_pending"));
540 pub fn check_private_instance_and_federation_enabled(
541 local_site: &LocalSite,
542 ) -> Result<(), LemmyError> {
543 if local_site.private_instance && local_site.federation_enabled {
544 return Err(LemmyError::from_message(
545 "Cannot have both private instance and federation enabled.",
551 pub async fn purge_image_posts_for_person(
552 banned_person_id: PersonId,
555 client: &ClientWithMiddleware,
556 ) -> Result<(), LemmyError> {
557 let posts = blocking(pool, move |conn: &mut _| {
558 Post::fetch_pictrs_posts_for_creator(conn, banned_person_id)
562 if let Some(url) = post.url {
563 purge_image_from_pictrs(client, settings, &url).await.ok();
565 if let Some(thumbnail_url) = post.thumbnail_url {
566 purge_image_from_pictrs(client, settings, &thumbnail_url)
572 blocking(pool, move |conn| {
573 Post::remove_pictrs_post_images_and_thumbnails_for_creator(conn, banned_person_id)
580 pub async fn purge_image_posts_for_community(
581 banned_community_id: CommunityId,
584 client: &ClientWithMiddleware,
585 ) -> Result<(), LemmyError> {
586 let posts = blocking(pool, move |conn: &mut _| {
587 Post::fetch_pictrs_posts_for_community(conn, banned_community_id)
591 if let Some(url) = post.url {
592 purge_image_from_pictrs(client, settings, &url).await.ok();
594 if let Some(thumbnail_url) = post.thumbnail_url {
595 purge_image_from_pictrs(client, settings, &thumbnail_url)
601 blocking(pool, move |conn| {
602 Post::remove_pictrs_post_images_and_thumbnails_for_community(conn, banned_community_id)
609 pub async fn remove_user_data(
610 banned_person_id: PersonId,
613 client: &ClientWithMiddleware,
614 ) -> Result<(), LemmyError> {
616 let person = blocking(pool, move |conn| Person::read(conn, banned_person_id)).await??;
617 if let Some(avatar) = person.avatar {
618 purge_image_from_pictrs(client, settings, &avatar)
622 if let Some(banner) = person.banner {
623 purge_image_from_pictrs(client, settings, &banner)
628 // Update the fields to None
629 blocking(pool, move |conn| {
633 &PersonUpdateForm::builder()
642 blocking(pool, move |conn: &mut _| {
643 Post::update_removed_for_creator(conn, banned_person_id, None, true)
648 purge_image_posts_for_person(banned_person_id, pool, settings, client).await?;
651 // Remove all communities where they're the top mod
652 // for now, remove the communities manually
653 let first_mod_communities = blocking(pool, move |conn: &mut _| {
654 CommunityModeratorView::get_community_first_mods(conn)
658 // Filter to only this banned users top communities
659 let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
661 .filter(|fmc| fmc.moderator.id == banned_person_id)
664 for first_mod_community in banned_user_first_communities {
665 let community_id = first_mod_community.community.id;
666 blocking(pool, move |conn| {
670 &CommunityUpdateForm::builder().removed(Some(true)).build(),
675 // Delete the community images
676 if let Some(icon) = first_mod_community.community.icon {
677 purge_image_from_pictrs(client, settings, &icon).await.ok();
679 if let Some(banner) = first_mod_community.community.banner {
680 purge_image_from_pictrs(client, settings, &banner)
684 // Update the fields to None
685 blocking(pool, move |conn| {
689 &CommunityUpdateForm::builder()
699 blocking(pool, move |conn: &mut _| {
700 Comment::update_removed_for_creator(conn, banned_person_id, true)
707 pub async fn remove_user_data_in_community(
708 community_id: CommunityId,
709 banned_person_id: PersonId,
711 ) -> Result<(), LemmyError> {
713 blocking(pool, move |conn| {
714 Post::update_removed_for_creator(conn, banned_person_id, Some(community_id), true)
719 // TODO Diesel doesn't allow updates with joins, so this has to be a loop
720 let comments = blocking(pool, move |conn| {
721 CommentQuery::builder()
723 .creator_id(Some(banned_person_id))
724 .community_id(Some(community_id))
725 .limit(Some(i64::MAX))
731 for comment_view in &comments {
732 let comment_id = comment_view.comment.id;
733 blocking(pool, move |conn| {
737 &CommentUpdateForm::builder().removed(Some(true)).build(),
746 pub async fn delete_user_account(
750 client: &ClientWithMiddleware,
751 ) -> Result<(), LemmyError> {
752 // Delete their images
753 let person = blocking(pool, move |conn| Person::read(conn, person_id)).await??;
754 if let Some(avatar) = person.avatar {
755 purge_image_from_pictrs(client, settings, &avatar)
759 if let Some(banner) = person.banner {
760 purge_image_from_pictrs(client, settings, &banner)
764 // No need to update avatar and banner, those are handled in Person::delete_account
767 let permadelete = move |conn: &mut _| Comment::permadelete_for_creator(conn, person_id);
768 blocking(pool, permadelete)
770 .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
773 let permadelete = move |conn: &mut _| Post::permadelete_for_creator(conn, person_id);
774 blocking(pool, permadelete)
776 .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?;
779 purge_image_posts_for_person(person_id, pool, settings, client).await?;
781 blocking(pool, move |conn| Person::delete_account(conn, person_id)).await??;
786 pub fn listing_type_with_site_default(
787 listing_type: Option<ListingType>,
788 local_site: &LocalSite,
789 ) -> Result<ListingType, LemmyError> {
790 Ok(listing_type.unwrap_or(ListingType::from_str(
791 &local_site.default_post_listing_type,