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 #[tracing::instrument(skip_all)]
48 pub async fn is_mod_or_admin(
51 community_id: CommunityId,
52 ) -> Result<(), LemmyError> {
53 let is_mod_or_admin = CommunityView::is_mod_or_admin(pool, person_id, community_id).await?;
55 return Err(LemmyError::from_message("not_a_mod_or_admin"));
60 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
61 if !local_user_view.person.admin {
62 return Err(LemmyError::from_message("not_an_admin"));
67 #[tracing::instrument(skip_all)]
68 pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
69 Post::read(pool, post_id)
71 .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))
74 #[tracing::instrument(skip_all)]
75 pub async fn mark_post_as_read(
79 ) -> Result<PostRead, LemmyError> {
80 let post_read_form = PostReadForm { post_id, person_id };
82 PostRead::mark_as_read(pool, &post_read_form)
84 .map_err(|e| LemmyError::from_error_message(e, "couldnt_mark_post_as_read"))
87 #[tracing::instrument(skip_all)]
88 pub async fn mark_post_as_unread(
92 ) -> Result<usize, LemmyError> {
93 let post_read_form = PostReadForm { post_id, person_id };
95 PostRead::mark_as_unread(pool, &post_read_form)
97 .map_err(|e| LemmyError::from_error_message(e, "couldnt_mark_post_as_read"))
100 #[tracing::instrument(skip_all)]
101 pub async fn get_local_user_view_from_jwt(
105 ) -> Result<LocalUserView, LemmyError> {
106 let claims = Claims::decode(jwt, &secret.jwt_secret)
107 .map_err(|e| e.with_message("not_logged_in"))?
109 let local_user_id = LocalUserId(claims.sub);
110 let local_user_view = LocalUserView::read(pool, local_user_id).await?;
112 local_user_view.person.banned,
113 local_user_view.person.ban_expires,
114 local_user_view.person.deleted,
117 check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
122 /// Checks if user's token was issued before user's password reset.
123 pub fn check_validator_time(
124 validator_time: &NaiveDateTime,
126 ) -> Result<(), LemmyError> {
127 let user_validation_time = validator_time.timestamp();
128 if user_validation_time > claims.iat {
129 Err(LemmyError::from_message("not_logged_in"))
135 #[tracing::instrument(skip_all)]
136 pub async fn get_local_user_view_from_jwt_opt(
137 jwt: Option<&Sensitive<String>>,
140 ) -> Result<Option<LocalUserView>, LemmyError> {
142 Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool, secret).await?)),
147 #[tracing::instrument(skip_all)]
148 pub async fn get_local_user_settings_view_from_jwt_opt(
149 jwt: Option<&Sensitive<String>>,
152 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
155 let claims = Claims::decode(jwt.as_ref(), &secret.jwt_secret)
156 .map_err(|e| e.with_message("not_logged_in"))?
158 let local_user_id = LocalUserId(claims.sub);
159 let local_user_view = LocalUserSettingsView::read(pool, local_user_id).await?;
161 local_user_view.person.banned,
162 local_user_view.person.ban_expires,
163 local_user_view.person.deleted,
166 check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
168 Ok(Some(local_user_view))
173 pub fn check_user_valid(
175 ban_expires: Option<NaiveDateTime>,
177 ) -> Result<(), LemmyError> {
178 // Check for a site ban
179 if is_banned(banned, ban_expires) {
180 return Err(LemmyError::from_message("site_ban"));
183 // check for account deletion
185 return Err(LemmyError::from_message("deleted"));
191 #[tracing::instrument(skip_all)]
192 pub async fn check_community_ban(
194 community_id: CommunityId,
196 ) -> Result<(), LemmyError> {
197 let is_banned = CommunityPersonBanView::get(pool, person_id, community_id)
201 Err(LemmyError::from_message("community_ban"))
207 #[tracing::instrument(skip_all)]
208 pub async fn check_community_deleted_or_removed(
209 community_id: CommunityId,
211 ) -> Result<(), LemmyError> {
212 let community = Community::read(pool, community_id)
214 .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
215 if community.deleted || community.removed {
216 Err(LemmyError::from_message("deleted"))
222 pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
223 if post.deleted || post.removed {
224 Err(LemmyError::from_message("deleted"))
230 #[tracing::instrument(skip_all)]
231 pub async fn check_person_block(
233 potential_blocker_id: PersonId,
235 ) -> Result<(), LemmyError> {
236 let is_blocked = PersonBlock::read(pool, potential_blocker_id, my_id)
240 Err(LemmyError::from_message("person_block"))
246 #[tracing::instrument(skip_all)]
247 pub fn check_downvotes_enabled(score: i16, local_site: &LocalSite) -> Result<(), LemmyError> {
248 if score == -1 && !local_site.enable_downvotes {
249 return Err(LemmyError::from_message("downvotes_disabled"));
254 #[tracing::instrument(skip_all)]
255 pub fn check_private_instance(
256 local_user_view: &Option<LocalUserView>,
257 local_site: &LocalSite,
258 ) -> Result<(), LemmyError> {
259 if local_user_view.is_none() && local_site.private_instance {
260 return Err(LemmyError::from_message("instance_is_private"));
265 #[tracing::instrument(skip_all)]
266 pub async fn build_federated_instances(
267 local_site: &LocalSite,
269 ) -> Result<Option<FederatedInstances>, LemmyError> {
270 if local_site.federation_enabled {
271 // TODO I hate that this requires 3 queries
272 let linked = Instance::linked(pool).await?;
273 let allowed = Instance::allowlist(pool).await?;
274 let blocked = Instance::blocklist(pool).await?;
276 // These can return empty vectors, so convert them to options
277 let allowed = (!allowed.is_empty()).then(|| allowed);
278 let blocked = (!blocked.is_empty()).then(|| blocked);
280 Ok(Some(FederatedInstances {
290 /// Checks the password length
291 pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
292 if !(10..=60).contains(&pass.chars().count()) {
293 Err(LemmyError::from_message("invalid_password"))
299 /// Checks the site description length
300 pub fn site_description_length_check(description: &str) -> Result<(), LemmyError> {
301 if description.len() > 150 {
302 Err(LemmyError::from_message("site_description_length_overflow"))
308 /// Checks for a honeypot. If this field is filled, fail the rest of the function
309 pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
310 if honeypot.is_some() {
311 Err(LemmyError::from_message("honeypot_fail"))
317 pub fn send_email_to_user(
318 local_user_view: &LocalUserView,
323 if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email {
327 if let Some(user_email) = &local_user_view.local_user.email {
331 &local_user_view.person.name,
336 Err(e) => warn!("{}", e),
341 pub async fn send_password_reset_email(
342 user: &LocalUserView,
345 ) -> Result<(), LemmyError> {
346 // Generate a random token
347 let token = generate_random_string();
350 let token2 = token.clone();
351 let local_user_id = user.local_user.id;
352 PasswordResetRequest::create_token(pool, local_user_id, &token2).await?;
354 let email = &user.local_user.email.to_owned().expect("email");
355 let lang = get_interface_language(user);
356 let subject = &lang.password_reset_subject(&user.person.name);
357 let protocol_and_hostname = settings.get_protocol_and_hostname();
358 let reset_link = format!("{}/password_change/{}", protocol_and_hostname, &token);
359 let body = &lang.password_reset_body(reset_link, &user.person.name);
360 send_email(subject, email, &user.person.name, body, settings)
363 /// Send a verification email
364 pub async fn send_verification_email(
365 user: &LocalUserView,
369 ) -> Result<(), LemmyError> {
370 let form = EmailVerificationForm {
371 local_user_id: user.local_user.id,
372 email: new_email.to_string(),
373 verification_token: generate_random_string(),
375 let verify_link = format!(
376 "{}/verify_email/{}",
377 settings.get_protocol_and_hostname(),
378 &form.verification_token
380 EmailVerification::create(pool, &form).await?;
382 let lang = get_interface_language(user);
383 let subject = lang.verify_email_subject(&settings.hostname);
384 let body = lang.verify_email_body(&settings.hostname, &user.person.name, verify_link);
385 send_email(&subject, new_email, &user.person.name, &body, settings)?;
390 pub fn send_email_verification_success(
391 user: &LocalUserView,
393 ) -> Result<(), LemmyError> {
394 let email = &user.local_user.email.to_owned().expect("email");
395 let lang = get_interface_language(user);
396 let subject = &lang.email_verified_subject(&user.person.actor_id);
397 let body = &lang.email_verified_body();
398 send_email(subject, email, &user.person.name, body, settings)
401 pub fn get_interface_language(user: &LocalUserView) -> Lang {
402 lang_str_to_lang(&user.local_user.interface_language)
405 pub fn get_interface_language_from_settings(user: &LocalUserSettingsView) -> Lang {
406 lang_str_to_lang(&user.local_user.interface_language)
409 fn lang_str_to_lang(lang: &str) -> Lang {
410 let lang_id = LanguageId::new(lang);
411 Lang::from_language_id(&lang_id).unwrap_or_else(|| {
412 let en = LanguageId::new("en");
413 Lang::from_language_id(&en).expect("default language")
417 pub fn local_site_rate_limit_to_rate_limit_config(
418 local_site_rate_limit: &LocalSiteRateLimit,
419 ) -> RateLimitConfig {
420 let l = local_site_rate_limit;
423 message_per_second: l.message_per_second,
425 post_per_second: l.post_per_second,
426 register: l.register,
427 register_per_second: l.register_per_second,
429 image_per_second: l.image_per_second,
431 comment_per_second: l.comment_per_second,
433 search_per_second: l.search_per_second,
437 pub fn local_site_to_slur_regex(local_site: &LocalSite) -> Option<Regex> {
438 build_slur_regex(local_site.slur_filter_regex.as_deref())
441 pub fn local_site_opt_to_slur_regex(local_site: &Option<LocalSite>) -> Option<Regex> {
444 .map(local_site_to_slur_regex)
448 pub fn send_application_approved_email(
449 user: &LocalUserView,
451 ) -> Result<(), LemmyError> {
452 let email = &user.local_user.email.to_owned().expect("email");
453 let lang = get_interface_language(user);
454 let subject = lang.registration_approved_subject(&user.person.actor_id);
455 let body = lang.registration_approved_body(&settings.hostname);
456 send_email(&subject, email, &user.person.name, &body, settings)
459 /// Send a new applicant email notification to all admins
460 pub async fn send_new_applicant_email_to_admins(
461 applicant_username: &str,
464 ) -> Result<(), LemmyError> {
465 // Collect the admins with emails
466 let admins = LocalUserSettingsView::list_admins_with_emails(pool).await?;
468 let applications_link = &format!(
469 "{}/registration_applications",
470 settings.get_protocol_and_hostname(),
473 for admin in &admins {
474 let email = &admin.local_user.email.to_owned().expect("email");
475 let lang = get_interface_language_from_settings(admin);
476 let subject = lang.new_application_subject(applicant_username, &settings.hostname);
477 let body = lang.new_application_body(applications_link);
478 send_email(&subject, email, &admin.person.name, &body, settings)?;
483 pub async fn check_registration_application(
484 local_user_view: &LocalUserView,
485 local_site: &LocalSite,
487 ) -> Result<(), LemmyError> {
488 if local_site.require_application
489 && !local_user_view.local_user.accepted_application
490 && !local_user_view.person.admin
492 // Fetch the registration, see if its denied
493 let local_user_id = local_user_view.local_user.id;
494 let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
495 if let Some(deny_reason) = registration.deny_reason {
496 let lang = get_interface_language(local_user_view);
497 let registration_denied_message = format!("{}: {}", lang.registration_denied(), &deny_reason);
498 return Err(LemmyError::from_message(®istration_denied_message));
500 return Err(LemmyError::from_message("registration_application_pending"));
506 pub fn check_private_instance_and_federation_enabled(
507 local_site: &LocalSite,
508 ) -> Result<(), LemmyError> {
509 if local_site.private_instance && local_site.federation_enabled {
510 return Err(LemmyError::from_message(
511 "Cannot have both private instance and federation enabled.",
517 pub async fn purge_image_posts_for_person(
518 banned_person_id: PersonId,
521 client: &ClientWithMiddleware,
522 ) -> Result<(), LemmyError> {
523 let posts = Post::fetch_pictrs_posts_for_creator(pool, banned_person_id).await?;
525 if let Some(url) = post.url {
526 purge_image_from_pictrs(client, settings, &url).await.ok();
528 if let Some(thumbnail_url) = post.thumbnail_url {
529 purge_image_from_pictrs(client, settings, &thumbnail_url)
535 Post::remove_pictrs_post_images_and_thumbnails_for_creator(pool, banned_person_id).await?;
540 pub async fn purge_image_posts_for_community(
541 banned_community_id: CommunityId,
544 client: &ClientWithMiddleware,
545 ) -> Result<(), LemmyError> {
546 let posts = Post::fetch_pictrs_posts_for_community(pool, banned_community_id).await?;
548 if let Some(url) = post.url {
549 purge_image_from_pictrs(client, settings, &url).await.ok();
551 if let Some(thumbnail_url) = post.thumbnail_url {
552 purge_image_from_pictrs(client, settings, &thumbnail_url)
558 Post::remove_pictrs_post_images_and_thumbnails_for_community(pool, banned_community_id).await?;
563 pub async fn remove_user_data(
564 banned_person_id: PersonId,
567 client: &ClientWithMiddleware,
568 ) -> Result<(), LemmyError> {
570 let person = Person::read(pool, banned_person_id).await?;
571 if let Some(avatar) = person.avatar {
572 purge_image_from_pictrs(client, settings, &avatar)
576 if let Some(banner) = person.banner {
577 purge_image_from_pictrs(client, settings, &banner)
582 // Update the fields to None
586 &PersonUpdateForm::builder()
594 Post::update_removed_for_creator(pool, banned_person_id, None, true).await?;
597 purge_image_posts_for_person(banned_person_id, pool, settings, client).await?;
600 // Remove all communities where they're the top mod
601 // for now, remove the communities manually
602 let first_mod_communities = CommunityModeratorView::get_community_first_mods(pool).await?;
604 // Filter to only this banned users top communities
605 let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
607 .filter(|fmc| fmc.moderator.id == banned_person_id)
610 for first_mod_community in banned_user_first_communities {
611 let community_id = first_mod_community.community.id;
615 &CommunityUpdateForm::builder().removed(Some(true)).build(),
619 // Delete the community images
620 if let Some(icon) = first_mod_community.community.icon {
621 purge_image_from_pictrs(client, settings, &icon).await.ok();
623 if let Some(banner) = first_mod_community.community.banner {
624 purge_image_from_pictrs(client, settings, &banner)
628 // Update the fields to None
632 &CommunityUpdateForm::builder()
641 Comment::update_removed_for_creator(pool, banned_person_id, true).await?;
646 pub async fn remove_user_data_in_community(
647 community_id: CommunityId,
648 banned_person_id: PersonId,
650 ) -> Result<(), LemmyError> {
652 Post::update_removed_for_creator(pool, banned_person_id, Some(community_id), true).await?;
655 // TODO Diesel doesn't allow updates with joins, so this has to be a loop
656 let comments = CommentQuery::builder()
658 .creator_id(Some(banned_person_id))
659 .community_id(Some(community_id))
660 .limit(Some(i64::MAX))
665 for comment_view in &comments {
666 let comment_id = comment_view.comment.id;
670 &CommentUpdateForm::builder().removed(Some(true)).build(),
678 pub async fn delete_user_account(
682 client: &ClientWithMiddleware,
683 ) -> Result<(), LemmyError> {
684 // Delete their images
685 let person = Person::read(pool, person_id).await?;
686 if let Some(avatar) = person.avatar {
687 purge_image_from_pictrs(client, settings, &avatar)
691 if let Some(banner) = person.banner {
692 purge_image_from_pictrs(client, settings, &banner)
696 // No need to update avatar and banner, those are handled in Person::delete_account
699 Comment::permadelete_for_creator(pool, person_id)
701 .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_comment"))?;
704 Post::permadelete_for_creator(pool, person_id)
706 .map_err(|e| LemmyError::from_error_message(e, "couldnt_update_post"))?;
709 purge_image_posts_for_person(person_id, pool, settings, client).await?;
711 Person::delete_account(pool, person_id).await?;
716 pub fn listing_type_with_site_default(
717 listing_type: Option<ListingType>,
718 local_site: &LocalSite,
719 ) -> Result<ListingType, LemmyError> {
720 Ok(listing_type.unwrap_or(ListingType::from_str(
721 &local_site.default_post_listing_type,
727 use crate::utils::password_length_check;
731 fn password_length() {
732 assert!(password_length_check("Õ¼¾°3yË,o¸ãtÌÈú|ÇÁÙAøüÒI©·¤(T]/ð>æºWæ[C¤bªWöaÃÎñ·{=û³&§½K/c").is_ok());
733 assert!(password_length_check("1234567890").is_ok());
734 assert!(password_length_check("short").is_err());
735 assert!(password_length_check("looooooooooooooooooooooooooooooooooooooooooooooooooooooooooong").is_err());