]> Untitled Git - lemmy.git/blob - crates/api/src/local_user.rs
Adding temporary bans. Fixes #1423 (#1999)
[lemmy.git] / crates / api / src / local_user.rs
1 use crate::{captcha_as_wav_base64, Perform};
2 use actix_web::web::Data;
3 use anyhow::Context;
4 use bcrypt::verify;
5 use captcha::{gen, Difficulty};
6 use chrono::Duration;
7 use lemmy_api_common::{
8   blocking,
9   check_registration_application,
10   get_local_user_view_from_jwt,
11   is_admin,
12   password_length_check,
13   person::*,
14   send_email_verification_success,
15   send_password_reset_email,
16   send_verification_email,
17 };
18 use lemmy_db_schema::{
19   diesel_option_overwrite,
20   diesel_option_overwrite_to_url,
21   from_opt_str_to_opt_enum,
22   naive_now,
23   source::{
24     comment::Comment,
25     community::Community,
26     email_verification::EmailVerification,
27     local_user::{LocalUser, LocalUserForm},
28     moderator::*,
29     password_reset_request::*,
30     person::*,
31     person_block::{PersonBlock, PersonBlockForm},
32     person_mention::*,
33     post::Post,
34     private_message::PrivateMessage,
35     site::*,
36   },
37   traits::{Blockable, Crud},
38   SortType,
39 };
40 use lemmy_db_views::{
41   comment_report_view::CommentReportView,
42   comment_view::{CommentQueryBuilder, CommentView},
43   local_user_view::LocalUserView,
44   post_report_view::PostReportView,
45   private_message_view::PrivateMessageView,
46 };
47 use lemmy_db_views_actor::{
48   community_moderator_view::CommunityModeratorView,
49   person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
50   person_view::PersonViewSafe,
51 };
52 use lemmy_utils::{
53   claims::Claims,
54   location_info,
55   utils::{is_valid_display_name, is_valid_matrix_id, naive_from_unix},
56   ConnectionId,
57   LemmyError,
58 };
59 use lemmy_websocket::{
60   messages::{CaptchaItem, SendAllMessage},
61   LemmyContext,
62   UserOperation,
63 };
64
65 #[async_trait::async_trait(?Send)]
66 impl Perform for Login {
67   type Response = LoginResponse;
68
69   #[tracing::instrument(skip(context, _websocket_id))]
70   async fn perform(
71     &self,
72     context: &Data<LemmyContext>,
73     _websocket_id: Option<ConnectionId>,
74   ) -> Result<LoginResponse, LemmyError> {
75     let data: &Login = self;
76
77     // Fetch that username / email
78     let username_or_email = data.username_or_email.clone();
79     let local_user_view = blocking(context.pool(), move |conn| {
80       LocalUserView::find_by_email_or_name(conn, &username_or_email)
81     })
82     .await?
83     .map_err(LemmyError::from)
84     .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
85
86     // Verify the password
87     let valid: bool = verify(
88       &data.password,
89       &local_user_view.local_user.password_encrypted,
90     )
91     .unwrap_or(false);
92     if !valid {
93       return Err(LemmyError::from_message("password_incorrect"));
94     }
95
96     let site = blocking(context.pool(), Site::read_simple).await??;
97     if site.require_email_verification && !local_user_view.local_user.email_verified {
98       return Err(LemmyError::from_message("email_not_verified"));
99     }
100
101     check_registration_application(&site, &local_user_view, context.pool()).await?;
102
103     // Return the jwt
104     Ok(LoginResponse {
105       jwt: Some(
106         Claims::jwt(
107           local_user_view.local_user.id.0,
108           &context.secret().jwt_secret,
109           &context.settings().hostname,
110         )?
111         .into(),
112       ),
113       verify_email_sent: false,
114       registration_created: false,
115     })
116   }
117 }
118
119 #[async_trait::async_trait(?Send)]
120 impl Perform for GetCaptcha {
121   type Response = GetCaptchaResponse;
122
123   #[tracing::instrument(skip(context, _websocket_id))]
124   async fn perform(
125     &self,
126     context: &Data<LemmyContext>,
127     _websocket_id: Option<ConnectionId>,
128   ) -> Result<Self::Response, LemmyError> {
129     let captcha_settings = context.settings().captcha;
130
131     if !captcha_settings.enabled {
132       return Ok(GetCaptchaResponse { ok: None });
133     }
134
135     let captcha = match captcha_settings.difficulty.as_str() {
136       "easy" => gen(Difficulty::Easy),
137       "medium" => gen(Difficulty::Medium),
138       "hard" => gen(Difficulty::Hard),
139       _ => gen(Difficulty::Medium),
140     };
141
142     let answer = captcha.chars_as_string();
143
144     let png = captcha.as_base64().expect("failed to generate captcha");
145
146     let uuid = uuid::Uuid::new_v4().to_string();
147
148     let wav = captcha_as_wav_base64(&captcha);
149
150     let captcha_item = CaptchaItem {
151       answer,
152       uuid: uuid.to_owned(),
153       expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
154     };
155
156     // Stores the captcha item on the queue
157     context.chat_server().do_send(captcha_item);
158
159     Ok(GetCaptchaResponse {
160       ok: Some(CaptchaResponse { png, wav, uuid }),
161     })
162   }
163 }
164
165 #[async_trait::async_trait(?Send)]
166 impl Perform for SaveUserSettings {
167   type Response = LoginResponse;
168
169   #[tracing::instrument(skip(context, _websocket_id))]
170   async fn perform(
171     &self,
172     context: &Data<LemmyContext>,
173     _websocket_id: Option<ConnectionId>,
174   ) -> Result<LoginResponse, LemmyError> {
175     let data: &SaveUserSettings = self;
176     let local_user_view =
177       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
178
179     let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
180     let banner = diesel_option_overwrite_to_url(&data.banner)?;
181     let bio = diesel_option_overwrite(&data.bio);
182     let display_name = diesel_option_overwrite(&data.display_name);
183     let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
184     let bot_account = data.bot_account;
185     let email_deref = data.email.as_deref().map(|e| e.to_owned());
186     let email = diesel_option_overwrite(&email_deref);
187
188     if let Some(Some(email)) = &email {
189       let previous_email = local_user_view.local_user.email.unwrap_or_default();
190       // Only send the verification email if there was an email change
191       if previous_email.ne(email) {
192         send_verification_email(
193           local_user_view.local_user.id,
194           email,
195           &local_user_view.person.name,
196           context.pool(),
197           &context.settings(),
198         )
199         .await?;
200       }
201     }
202
203     // When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
204     if let Some(email) = &email {
205       let site_fut = blocking(context.pool(), Site::read_simple);
206       if email.is_none() && site_fut.await??.require_email_verification {
207         return Err(LemmyError::from_message("email_required"));
208       }
209     }
210
211     if let Some(Some(bio)) = &bio {
212       if bio.chars().count() > 300 {
213         return Err(LemmyError::from_message("bio_length_overflow"));
214       }
215     }
216
217     if let Some(Some(display_name)) = &display_name {
218       if !is_valid_display_name(
219         display_name.trim(),
220         context.settings().actor_name_max_length,
221       ) {
222         return Err(LemmyError::from_message("invalid_username"));
223       }
224     }
225
226     if let Some(Some(matrix_user_id)) = &matrix_user_id {
227       if !is_valid_matrix_id(matrix_user_id) {
228         return Err(LemmyError::from_message("invalid_matrix_id"));
229       }
230     }
231
232     let local_user_id = local_user_view.local_user.id;
233     let person_id = local_user_view.person.id;
234     let default_listing_type = data.default_listing_type;
235     let default_sort_type = data.default_sort_type;
236     let password_encrypted = local_user_view.local_user.password_encrypted;
237     let public_key = local_user_view.person.public_key;
238
239     let person_form = PersonForm {
240       name: local_user_view.person.name,
241       avatar,
242       banner,
243       inbox_url: None,
244       display_name,
245       published: None,
246       updated: Some(naive_now()),
247       banned: None,
248       deleted: None,
249       actor_id: None,
250       bio,
251       local: None,
252       admin: None,
253       private_key: None,
254       public_key,
255       last_refreshed_at: None,
256       shared_inbox_url: None,
257       matrix_user_id,
258       bot_account,
259       ban_expires: None,
260     };
261
262     blocking(context.pool(), move |conn| {
263       Person::update(conn, person_id, &person_form)
264     })
265     .await?
266     .map_err(LemmyError::from)
267     .map_err(|e| e.with_message("user_already_exists"))?;
268
269     let local_user_form = LocalUserForm {
270       person_id: Some(person_id),
271       email,
272       password_encrypted: Some(password_encrypted),
273       show_nsfw: data.show_nsfw,
274       show_bot_accounts: data.show_bot_accounts,
275       show_scores: data.show_scores,
276       theme: data.theme.to_owned(),
277       default_sort_type,
278       default_listing_type,
279       lang: data.lang.to_owned(),
280       show_avatars: data.show_avatars,
281       show_read_posts: data.show_read_posts,
282       show_new_post_notifs: data.show_new_post_notifs,
283       send_notifications_to_email: data.send_notifications_to_email,
284       email_verified: None,
285       accepted_application: None,
286     };
287
288     let local_user_res = blocking(context.pool(), move |conn| {
289       LocalUser::update(conn, local_user_id, &local_user_form)
290     })
291     .await?;
292     let updated_local_user = match local_user_res {
293       Ok(u) => u,
294       Err(e) => {
295         let err_type = if e.to_string()
296           == "duplicate key value violates unique constraint \"local_user_email_key\""
297         {
298           "email_already_exists"
299         } else {
300           "user_already_exists"
301         };
302
303         return Err(LemmyError::from(e).with_message(err_type));
304       }
305     };
306
307     // Return the jwt
308     Ok(LoginResponse {
309       jwt: Some(
310         Claims::jwt(
311           updated_local_user.id.0,
312           &context.secret().jwt_secret,
313           &context.settings().hostname,
314         )?
315         .into(),
316       ),
317       verify_email_sent: false,
318       registration_created: false,
319     })
320   }
321 }
322
323 #[async_trait::async_trait(?Send)]
324 impl Perform for ChangePassword {
325   type Response = LoginResponse;
326
327   #[tracing::instrument(skip(self, context, _websocket_id))]
328   async fn perform(
329     &self,
330     context: &Data<LemmyContext>,
331     _websocket_id: Option<ConnectionId>,
332   ) -> Result<LoginResponse, LemmyError> {
333     let data: &ChangePassword = self;
334     let local_user_view =
335       get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
336
337     password_length_check(&data.new_password)?;
338
339     // Make sure passwords match
340     if data.new_password != data.new_password_verify {
341       return Err(LemmyError::from_message("passwords_dont_match"));
342     }
343
344     // Check the old password
345     let valid: bool = verify(
346       &data.old_password,
347       &local_user_view.local_user.password_encrypted,
348     )
349     .unwrap_or(false);
350     if !valid {
351       return Err(LemmyError::from_message("password_incorrect"));
352     }
353
354     let local_user_id = local_user_view.local_user.id;
355     let new_password = data.new_password.to_owned();
356     let updated_local_user = blocking(context.pool(), move |conn| {
357       LocalUser::update_password(conn, local_user_id, &new_password)
358     })
359     .await??;
360
361     // Return the jwt
362     Ok(LoginResponse {
363       jwt: Some(
364         Claims::jwt(
365           updated_local_user.id.0,
366           &context.secret().jwt_secret,
367           &context.settings().hostname,
368         )?
369         .into(),
370       ),
371       verify_email_sent: false,
372       registration_created: false,
373     })
374   }
375 }
376
377 #[async_trait::async_trait(?Send)]
378 impl Perform for AddAdmin {
379   type Response = AddAdminResponse;
380
381   #[tracing::instrument(skip(context, websocket_id))]
382   async fn perform(
383     &self,
384     context: &Data<LemmyContext>,
385     websocket_id: Option<ConnectionId>,
386   ) -> Result<AddAdminResponse, LemmyError> {
387     let data: &AddAdmin = self;
388     let local_user_view =
389       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
390
391     // Make sure user is an admin
392     is_admin(&local_user_view)?;
393
394     let added = data.added;
395     let added_person_id = data.person_id;
396     let added_admin = blocking(context.pool(), move |conn| {
397       Person::add_admin(conn, added_person_id, added)
398     })
399     .await?
400     .map_err(LemmyError::from)
401     .map_err(|e| e.with_message("couldnt_update_user"))?;
402
403     // Mod tables
404     let form = ModAddForm {
405       mod_person_id: local_user_view.person.id,
406       other_person_id: added_admin.id,
407       removed: Some(!data.added),
408     };
409
410     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
411
412     let site_creator_id = blocking(context.pool(), move |conn| {
413       Site::read(conn, 1).map(|s| s.creator_id)
414     })
415     .await??;
416
417     let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
418     let creator_index = admins
419       .iter()
420       .position(|r| r.person.id == site_creator_id)
421       .context(location_info!())?;
422     let creator_person = admins.remove(creator_index);
423     admins.insert(0, creator_person);
424
425     let res = AddAdminResponse { admins };
426
427     context.chat_server().do_send(SendAllMessage {
428       op: UserOperation::AddAdmin,
429       response: res.clone(),
430       websocket_id,
431     });
432
433     Ok(res)
434   }
435 }
436
437 #[async_trait::async_trait(?Send)]
438 impl Perform for BanPerson {
439   type Response = BanPersonResponse;
440
441   #[tracing::instrument(skip(context, websocket_id))]
442   async fn perform(
443     &self,
444     context: &Data<LemmyContext>,
445     websocket_id: Option<ConnectionId>,
446   ) -> Result<BanPersonResponse, LemmyError> {
447     let data: &BanPerson = self;
448     let local_user_view =
449       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
450
451     // Make sure user is an admin
452     is_admin(&local_user_view)?;
453
454     let ban = data.ban;
455     let banned_person_id = data.person_id;
456     let expires = data.expires.map(naive_from_unix);
457
458     let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban, expires);
459     blocking(context.pool(), ban_person)
460       .await?
461       .map_err(LemmyError::from)
462       .map_err(|e| e.with_message("couldnt_update_user"))?;
463
464     // Remove their data if that's desired
465     if data.remove_data.unwrap_or(false) {
466       // Posts
467       blocking(context.pool(), move |conn: &'_ _| {
468         Post::update_removed_for_creator(conn, banned_person_id, None, true)
469       })
470       .await??;
471
472       // Communities
473       // Remove all communities where they're the top mod
474       // for now, remove the communities manually
475       let first_mod_communities = blocking(context.pool(), move |conn: &'_ _| {
476         CommunityModeratorView::get_community_first_mods(conn)
477       })
478       .await??;
479
480       // Filter to only this banned users top communities
481       let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
482         .into_iter()
483         .filter(|fmc| fmc.moderator.id == banned_person_id)
484         .collect();
485
486       for first_mod_community in banned_user_first_communities {
487         blocking(context.pool(), move |conn: &'_ _| {
488           Community::update_removed(conn, first_mod_community.community.id, true)
489         })
490         .await??;
491       }
492
493       // Comments
494       blocking(context.pool(), move |conn: &'_ _| {
495         Comment::update_removed_for_creator(conn, banned_person_id, true)
496       })
497       .await??;
498     }
499
500     // Mod tables
501     let form = ModBanForm {
502       mod_person_id: local_user_view.person.id,
503       other_person_id: data.person_id,
504       reason: data.reason.to_owned(),
505       banned: Some(data.ban),
506       expires,
507     };
508
509     blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
510
511     let person_id = data.person_id;
512     let person_view = blocking(context.pool(), move |conn| {
513       PersonViewSafe::read(conn, person_id)
514     })
515     .await??;
516
517     let res = BanPersonResponse {
518       person_view,
519       banned: data.ban,
520     };
521
522     context.chat_server().do_send(SendAllMessage {
523       op: UserOperation::BanPerson,
524       response: res.clone(),
525       websocket_id,
526     });
527
528     Ok(res)
529   }
530 }
531
532 #[async_trait::async_trait(?Send)]
533 impl Perform for GetBannedPersons {
534   type Response = BannedPersonsResponse;
535
536   async fn perform(
537     &self,
538     context: &Data<LemmyContext>,
539     _websocket_id: Option<ConnectionId>,
540   ) -> Result<Self::Response, LemmyError> {
541     let data: &GetBannedPersons = self;
542     let local_user_view =
543       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
544
545     // Make sure user is an admin
546     is_admin(&local_user_view)?;
547
548     let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
549
550     let res = Self::Response { banned };
551
552     Ok(res)
553   }
554 }
555
556 #[async_trait::async_trait(?Send)]
557 impl Perform for BlockPerson {
558   type Response = BlockPersonResponse;
559
560   #[tracing::instrument(skip(context, _websocket_id))]
561   async fn perform(
562     &self,
563     context: &Data<LemmyContext>,
564     _websocket_id: Option<ConnectionId>,
565   ) -> Result<BlockPersonResponse, LemmyError> {
566     let data: &BlockPerson = self;
567     let local_user_view =
568       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
569
570     let target_id = data.person_id;
571     let person_id = local_user_view.person.id;
572
573     // Don't let a person block themselves
574     if target_id == person_id {
575       return Err(LemmyError::from_message("cant_block_yourself"));
576     }
577
578     let person_block_form = PersonBlockForm {
579       person_id,
580       target_id,
581     };
582
583     if data.block {
584       let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
585       blocking(context.pool(), block)
586         .await?
587         .map_err(LemmyError::from)
588         .map_err(|e| e.with_message("person_block_already_exists"))?;
589     } else {
590       let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
591       blocking(context.pool(), unblock)
592         .await?
593         .map_err(LemmyError::from)
594         .map_err(|e| e.with_message("person_block_already_exists"))?;
595     }
596
597     // TODO does any federated stuff need to be done here?
598
599     let person_view = blocking(context.pool(), move |conn| {
600       PersonViewSafe::read(conn, target_id)
601     })
602     .await??;
603
604     let res = BlockPersonResponse {
605       person_view,
606       blocked: data.block,
607     };
608
609     Ok(res)
610   }
611 }
612
613 #[async_trait::async_trait(?Send)]
614 impl Perform for GetReplies {
615   type Response = GetRepliesResponse;
616
617   #[tracing::instrument(skip(context, _websocket_id))]
618   async fn perform(
619     &self,
620     context: &Data<LemmyContext>,
621     _websocket_id: Option<ConnectionId>,
622   ) -> Result<GetRepliesResponse, LemmyError> {
623     let data: &GetReplies = self;
624     let local_user_view =
625       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
626
627     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
628
629     let page = data.page;
630     let limit = data.limit;
631     let unread_only = data.unread_only;
632     let person_id = local_user_view.person.id;
633     let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
634
635     let replies = blocking(context.pool(), move |conn| {
636       CommentQueryBuilder::create(conn)
637         .sort(sort)
638         .unread_only(unread_only)
639         .recipient_id(person_id)
640         .show_bot_accounts(show_bot_accounts)
641         .my_person_id(person_id)
642         .page(page)
643         .limit(limit)
644         .list()
645     })
646     .await??;
647
648     Ok(GetRepliesResponse { replies })
649   }
650 }
651
652 #[async_trait::async_trait(?Send)]
653 impl Perform for GetPersonMentions {
654   type Response = GetPersonMentionsResponse;
655
656   #[tracing::instrument(skip(context, _websocket_id))]
657   async fn perform(
658     &self,
659     context: &Data<LemmyContext>,
660     _websocket_id: Option<ConnectionId>,
661   ) -> Result<GetPersonMentionsResponse, LemmyError> {
662     let data: &GetPersonMentions = self;
663     let local_user_view =
664       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
665
666     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
667
668     let page = data.page;
669     let limit = data.limit;
670     let unread_only = data.unread_only;
671     let person_id = local_user_view.person.id;
672     let mentions = blocking(context.pool(), move |conn| {
673       PersonMentionQueryBuilder::create(conn)
674         .recipient_id(person_id)
675         .my_person_id(person_id)
676         .sort(sort)
677         .unread_only(unread_only)
678         .page(page)
679         .limit(limit)
680         .list()
681     })
682     .await??;
683
684     Ok(GetPersonMentionsResponse { mentions })
685   }
686 }
687
688 #[async_trait::async_trait(?Send)]
689 impl Perform for MarkPersonMentionAsRead {
690   type Response = PersonMentionResponse;
691
692   #[tracing::instrument(skip(context, _websocket_id))]
693   async fn perform(
694     &self,
695     context: &Data<LemmyContext>,
696     _websocket_id: Option<ConnectionId>,
697   ) -> Result<PersonMentionResponse, LemmyError> {
698     let data: &MarkPersonMentionAsRead = self;
699     let local_user_view =
700       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
701
702     let person_mention_id = data.person_mention_id;
703     let read_person_mention = blocking(context.pool(), move |conn| {
704       PersonMention::read(conn, person_mention_id)
705     })
706     .await??;
707
708     if local_user_view.person.id != read_person_mention.recipient_id {
709       return Err(LemmyError::from_message("couldnt_update_comment"));
710     }
711
712     let person_mention_id = read_person_mention.id;
713     let read = data.read;
714     let update_mention =
715       move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
716     blocking(context.pool(), update_mention)
717       .await?
718       .map_err(LemmyError::from)
719       .map_err(|e| e.with_message("couldnt_update_comment"))?;
720
721     let person_mention_id = read_person_mention.id;
722     let person_id = local_user_view.person.id;
723     let person_mention_view = blocking(context.pool(), move |conn| {
724       PersonMentionView::read(conn, person_mention_id, Some(person_id))
725     })
726     .await??;
727
728     Ok(PersonMentionResponse {
729       person_mention_view,
730     })
731   }
732 }
733
734 #[async_trait::async_trait(?Send)]
735 impl Perform for MarkAllAsRead {
736   type Response = GetRepliesResponse;
737
738   #[tracing::instrument(skip(context, _websocket_id))]
739   async fn perform(
740     &self,
741     context: &Data<LemmyContext>,
742     _websocket_id: Option<ConnectionId>,
743   ) -> Result<GetRepliesResponse, LemmyError> {
744     let data: &MarkAllAsRead = self;
745     let local_user_view =
746       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
747
748     let person_id = local_user_view.person.id;
749     let replies = blocking(context.pool(), move |conn| {
750       CommentQueryBuilder::create(conn)
751         .my_person_id(person_id)
752         .recipient_id(person_id)
753         .unread_only(true)
754         .page(1)
755         .limit(999)
756         .list()
757     })
758     .await??;
759
760     // TODO: this should probably be a bulk operation
761     // Not easy to do as a bulk operation,
762     // because recipient_id isn't in the comment table
763     for comment_view in &replies {
764       let reply_id = comment_view.comment.id;
765       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
766       blocking(context.pool(), mark_as_read)
767         .await?
768         .map_err(LemmyError::from)
769         .map_err(|e| e.with_message("couldnt_update_comment"))?;
770     }
771
772     // Mark all user mentions as read
773     let update_person_mentions =
774       move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
775     blocking(context.pool(), update_person_mentions)
776       .await?
777       .map_err(LemmyError::from)
778       .map_err(|e| e.with_message("couldnt_update_comment"))?;
779
780     // Mark all private_messages as read
781     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
782     blocking(context.pool(), update_pm)
783       .await?
784       .map_err(LemmyError::from)
785       .map_err(|e| e.with_message("couldnt_update_private_message"))?;
786
787     Ok(GetRepliesResponse { replies: vec![] })
788   }
789 }
790
791 #[async_trait::async_trait(?Send)]
792 impl Perform for PasswordReset {
793   type Response = PasswordResetResponse;
794
795   #[tracing::instrument(skip(self, context, _websocket_id))]
796   async fn perform(
797     &self,
798     context: &Data<LemmyContext>,
799     _websocket_id: Option<ConnectionId>,
800   ) -> Result<PasswordResetResponse, LemmyError> {
801     let data: &PasswordReset = self;
802
803     // Fetch that email
804     let email = data.email.clone();
805     let local_user_view = blocking(context.pool(), move |conn| {
806       LocalUserView::find_by_email(conn, &email)
807     })
808     .await?
809     .map_err(LemmyError::from)
810     .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
811
812     // Email the pure token to the user.
813     send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
814     Ok(PasswordResetResponse {})
815   }
816 }
817
818 #[async_trait::async_trait(?Send)]
819 impl Perform for PasswordChange {
820   type Response = LoginResponse;
821
822   #[tracing::instrument(skip(self, context, _websocket_id))]
823   async fn perform(
824     &self,
825     context: &Data<LemmyContext>,
826     _websocket_id: Option<ConnectionId>,
827   ) -> Result<LoginResponse, LemmyError> {
828     let data: &PasswordChange = self;
829
830     // Fetch the user_id from the token
831     let token = data.token.clone();
832     let local_user_id = blocking(context.pool(), move |conn| {
833       PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
834     })
835     .await??;
836
837     password_length_check(&data.password)?;
838
839     // Make sure passwords match
840     if data.password != data.password_verify {
841       return Err(LemmyError::from_message("passwords_dont_match"));
842     }
843
844     // Update the user with the new password
845     let password = data.password.clone();
846     let updated_local_user = blocking(context.pool(), move |conn| {
847       LocalUser::update_password(conn, local_user_id, &password)
848     })
849     .await?
850     .map_err(LemmyError::from)
851     .map_err(|e| e.with_message("couldnt_update_user"))?;
852
853     // Return the jwt
854     Ok(LoginResponse {
855       jwt: Some(
856         Claims::jwt(
857           updated_local_user.id.0,
858           &context.secret().jwt_secret,
859           &context.settings().hostname,
860         )?
861         .into(),
862       ),
863       verify_email_sent: false,
864       registration_created: false,
865     })
866   }
867 }
868
869 #[async_trait::async_trait(?Send)]
870 impl Perform for GetReportCount {
871   type Response = GetReportCountResponse;
872
873   #[tracing::instrument(skip(context, _websocket_id))]
874   async fn perform(
875     &self,
876     context: &Data<LemmyContext>,
877     _websocket_id: Option<ConnectionId>,
878   ) -> Result<GetReportCountResponse, LemmyError> {
879     let data: &GetReportCount = self;
880     let local_user_view =
881       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
882
883     let person_id = local_user_view.person.id;
884     let admin = local_user_view.person.admin;
885     let community_id = data.community_id;
886
887     let comment_reports = blocking(context.pool(), move |conn| {
888       CommentReportView::get_report_count(conn, person_id, admin, community_id)
889     })
890     .await??;
891
892     let post_reports = blocking(context.pool(), move |conn| {
893       PostReportView::get_report_count(conn, person_id, admin, community_id)
894     })
895     .await??;
896
897     let res = GetReportCountResponse {
898       community_id,
899       comment_reports,
900       post_reports,
901     };
902
903     Ok(res)
904   }
905 }
906
907 #[async_trait::async_trait(?Send)]
908 impl Perform for GetUnreadCount {
909   type Response = GetUnreadCountResponse;
910
911   #[tracing::instrument(skip(context, _websocket_id))]
912   async fn perform(
913     &self,
914     context: &Data<LemmyContext>,
915     _websocket_id: Option<ConnectionId>,
916   ) -> Result<Self::Response, LemmyError> {
917     let data = self;
918     let local_user_view =
919       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
920
921     let person_id = local_user_view.person.id;
922
923     let replies = blocking(context.pool(), move |conn| {
924       CommentView::get_unread_replies(conn, person_id)
925     })
926     .await??;
927
928     let mentions = blocking(context.pool(), move |conn| {
929       PersonMentionView::get_unread_mentions(conn, person_id)
930     })
931     .await??;
932
933     let private_messages = blocking(context.pool(), move |conn| {
934       PrivateMessageView::get_unread_messages(conn, person_id)
935     })
936     .await??;
937
938     let res = Self::Response {
939       replies,
940       mentions,
941       private_messages,
942     };
943
944     Ok(res)
945   }
946 }
947
948 #[async_trait::async_trait(?Send)]
949 impl Perform for VerifyEmail {
950   type Response = VerifyEmailResponse;
951
952   async fn perform(
953     &self,
954     context: &Data<LemmyContext>,
955     _websocket_id: Option<usize>,
956   ) -> Result<Self::Response, LemmyError> {
957     let token = self.token.clone();
958     let verification = blocking(context.pool(), move |conn| {
959       EmailVerification::read_for_token(conn, &token)
960     })
961     .await?
962     .map_err(LemmyError::from)
963     .map_err(|e| e.with_message("token_not_found"))?;
964
965     let form = LocalUserForm {
966       // necessary in case this is a new signup
967       email_verified: Some(true),
968       // necessary in case email of an existing user was changed
969       email: Some(Some(verification.email)),
970       ..LocalUserForm::default()
971     };
972     let local_user_id = verification.local_user_id;
973     blocking(context.pool(), move |conn| {
974       LocalUser::update(conn, local_user_id, &form)
975     })
976     .await??;
977
978     let local_user_view = blocking(context.pool(), move |conn| {
979       LocalUserView::read(conn, local_user_id)
980     })
981     .await??;
982
983     send_email_verification_success(&local_user_view, &context.settings())?;
984
985     blocking(context.pool(), move |conn| {
986       EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
987     })
988     .await??;
989
990     Ok(VerifyEmailResponse {})
991   }
992 }