]> Untitled Git - lemmy.git/blob - crates/api/src/local_user.rs
Implement instance actor (#1798)
[lemmy.git] / crates / api / src / local_user.rs
1 use crate::{captcha_as_wav_base64, Perform};
2 use actix_web::web::Data;
3 use bcrypt::verify;
4 use captcha::{gen, Difficulty};
5 use chrono::Duration;
6 use lemmy_api_common::{
7   blocking,
8   check_registration_application,
9   get_local_user_view_from_jwt,
10   is_admin,
11   password_length_check,
12   person::*,
13   remove_user_data,
14   send_email_verification_success,
15   send_password_reset_email,
16   send_verification_email,
17 };
18 use lemmy_apub::{
19   activities::block::SiteOrCommunity,
20   protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser},
21 };
22 use lemmy_db_schema::{
23   diesel_option_overwrite,
24   diesel_option_overwrite_to_url,
25   from_opt_str_to_opt_enum,
26   naive_now,
27   source::{
28     comment::Comment,
29     email_verification::EmailVerification,
30     local_user::{LocalUser, LocalUserForm},
31     moderator::*,
32     password_reset_request::*,
33     person::*,
34     person_block::{PersonBlock, PersonBlockForm},
35     person_mention::*,
36     private_message::PrivateMessage,
37     site::*,
38   },
39   traits::{Blockable, Crud},
40   SortType,
41 };
42 use lemmy_db_views::{
43   comment_report_view::CommentReportView,
44   comment_view::{CommentQueryBuilder, CommentView},
45   local_user_view::LocalUserView,
46   post_report_view::PostReportView,
47   private_message_view::PrivateMessageView,
48 };
49 use lemmy_db_views_actor::{
50   person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
51   person_view::PersonViewSafe,
52 };
53 use lemmy_utils::{
54   claims::Claims,
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_local_site).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_local_site);
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 admins = blocking(context.pool(), PersonViewSafe::admins).await??;
413
414     let res = AddAdminResponse { admins };
415
416     context.chat_server().do_send(SendAllMessage {
417       op: UserOperation::AddAdmin,
418       response: res.clone(),
419       websocket_id,
420     });
421
422     Ok(res)
423   }
424 }
425
426 #[async_trait::async_trait(?Send)]
427 impl Perform for BanPerson {
428   type Response = BanPersonResponse;
429
430   #[tracing::instrument(skip(context, websocket_id))]
431   async fn perform(
432     &self,
433     context: &Data<LemmyContext>,
434     websocket_id: Option<ConnectionId>,
435   ) -> Result<BanPersonResponse, LemmyError> {
436     let data: &BanPerson = self;
437     let local_user_view =
438       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
439
440     // Make sure user is an admin
441     is_admin(&local_user_view)?;
442
443     let ban = data.ban;
444     let banned_person_id = data.person_id;
445     let expires = data.expires.map(naive_from_unix);
446
447     let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban, expires);
448     let person = blocking(context.pool(), ban_person)
449       .await?
450       .map_err(LemmyError::from)
451       .map_err(|e| e.with_message("couldnt_update_user"))?;
452
453     // Remove their data if that's desired
454     let remove_data = data.remove_data.unwrap_or(false);
455     if remove_data {
456       remove_user_data(person.id, context.pool()).await?;
457     }
458
459     // Mod tables
460     let form = ModBanForm {
461       mod_person_id: local_user_view.person.id,
462       other_person_id: data.person_id,
463       reason: data.reason.to_owned(),
464       banned: Some(data.ban),
465       expires,
466     };
467
468     blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
469
470     let person_id = data.person_id;
471     let person_view = blocking(context.pool(), move |conn| {
472       PersonViewSafe::read(conn, person_id)
473     })
474     .await??;
475
476     let site = SiteOrCommunity::Site(
477       blocking(context.pool(), Site::read_local_site)
478         .await??
479         .into(),
480     );
481     // if the action affects a local user, federate to other instances
482     if person.local {
483       if ban {
484         BlockUser::send(
485           &site,
486           &person.into(),
487           &local_user_view.person.into(),
488           remove_data,
489           data.reason.clone(),
490           expires,
491           context,
492         )
493         .await?;
494       } else {
495         UndoBlockUser::send(
496           &site,
497           &person.into(),
498           &local_user_view.person.into(),
499           data.reason.clone(),
500           context,
501         )
502         .await?;
503       }
504     }
505
506     let res = BanPersonResponse {
507       person_view,
508       banned: data.ban,
509     };
510
511     context.chat_server().do_send(SendAllMessage {
512       op: UserOperation::BanPerson,
513       response: res.clone(),
514       websocket_id,
515     });
516
517     Ok(res)
518   }
519 }
520
521 #[async_trait::async_trait(?Send)]
522 impl Perform for GetBannedPersons {
523   type Response = BannedPersonsResponse;
524
525   async fn perform(
526     &self,
527     context: &Data<LemmyContext>,
528     _websocket_id: Option<ConnectionId>,
529   ) -> Result<Self::Response, LemmyError> {
530     let data: &GetBannedPersons = self;
531     let local_user_view =
532       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
533
534     // Make sure user is an admin
535     is_admin(&local_user_view)?;
536
537     let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
538
539     let res = Self::Response { banned };
540
541     Ok(res)
542   }
543 }
544
545 #[async_trait::async_trait(?Send)]
546 impl Perform for BlockPerson {
547   type Response = BlockPersonResponse;
548
549   #[tracing::instrument(skip(context, _websocket_id))]
550   async fn perform(
551     &self,
552     context: &Data<LemmyContext>,
553     _websocket_id: Option<ConnectionId>,
554   ) -> Result<BlockPersonResponse, LemmyError> {
555     let data: &BlockPerson = self;
556     let local_user_view =
557       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
558
559     let target_id = data.person_id;
560     let person_id = local_user_view.person.id;
561
562     // Don't let a person block themselves
563     if target_id == person_id {
564       return Err(LemmyError::from_message("cant_block_yourself"));
565     }
566
567     let person_block_form = PersonBlockForm {
568       person_id,
569       target_id,
570     };
571
572     if data.block {
573       let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
574       blocking(context.pool(), block)
575         .await?
576         .map_err(LemmyError::from)
577         .map_err(|e| e.with_message("person_block_already_exists"))?;
578     } else {
579       let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
580       blocking(context.pool(), unblock)
581         .await?
582         .map_err(LemmyError::from)
583         .map_err(|e| e.with_message("person_block_already_exists"))?;
584     }
585
586     // TODO does any federated stuff need to be done here?
587
588     let person_view = blocking(context.pool(), move |conn| {
589       PersonViewSafe::read(conn, target_id)
590     })
591     .await??;
592
593     let res = BlockPersonResponse {
594       person_view,
595       blocked: data.block,
596     };
597
598     Ok(res)
599   }
600 }
601
602 #[async_trait::async_trait(?Send)]
603 impl Perform for GetReplies {
604   type Response = GetRepliesResponse;
605
606   #[tracing::instrument(skip(context, _websocket_id))]
607   async fn perform(
608     &self,
609     context: &Data<LemmyContext>,
610     _websocket_id: Option<ConnectionId>,
611   ) -> Result<GetRepliesResponse, LemmyError> {
612     let data: &GetReplies = self;
613     let local_user_view =
614       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
615
616     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
617
618     let page = data.page;
619     let limit = data.limit;
620     let unread_only = data.unread_only;
621     let person_id = local_user_view.person.id;
622     let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
623
624     let replies = blocking(context.pool(), move |conn| {
625       CommentQueryBuilder::create(conn)
626         .sort(sort)
627         .unread_only(unread_only)
628         .recipient_id(person_id)
629         .show_bot_accounts(show_bot_accounts)
630         .my_person_id(person_id)
631         .page(page)
632         .limit(limit)
633         .list()
634     })
635     .await??;
636
637     Ok(GetRepliesResponse { replies })
638   }
639 }
640
641 #[async_trait::async_trait(?Send)]
642 impl Perform for GetPersonMentions {
643   type Response = GetPersonMentionsResponse;
644
645   #[tracing::instrument(skip(context, _websocket_id))]
646   async fn perform(
647     &self,
648     context: &Data<LemmyContext>,
649     _websocket_id: Option<ConnectionId>,
650   ) -> Result<GetPersonMentionsResponse, LemmyError> {
651     let data: &GetPersonMentions = self;
652     let local_user_view =
653       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
654
655     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
656
657     let page = data.page;
658     let limit = data.limit;
659     let unread_only = data.unread_only;
660     let person_id = local_user_view.person.id;
661     let mentions = blocking(context.pool(), move |conn| {
662       PersonMentionQueryBuilder::create(conn)
663         .recipient_id(person_id)
664         .my_person_id(person_id)
665         .sort(sort)
666         .unread_only(unread_only)
667         .page(page)
668         .limit(limit)
669         .list()
670     })
671     .await??;
672
673     Ok(GetPersonMentionsResponse { mentions })
674   }
675 }
676
677 #[async_trait::async_trait(?Send)]
678 impl Perform for MarkPersonMentionAsRead {
679   type Response = PersonMentionResponse;
680
681   #[tracing::instrument(skip(context, _websocket_id))]
682   async fn perform(
683     &self,
684     context: &Data<LemmyContext>,
685     _websocket_id: Option<ConnectionId>,
686   ) -> Result<PersonMentionResponse, LemmyError> {
687     let data: &MarkPersonMentionAsRead = self;
688     let local_user_view =
689       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
690
691     let person_mention_id = data.person_mention_id;
692     let read_person_mention = blocking(context.pool(), move |conn| {
693       PersonMention::read(conn, person_mention_id)
694     })
695     .await??;
696
697     if local_user_view.person.id != read_person_mention.recipient_id {
698       return Err(LemmyError::from_message("couldnt_update_comment"));
699     }
700
701     let person_mention_id = read_person_mention.id;
702     let read = data.read;
703     let update_mention =
704       move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
705     blocking(context.pool(), update_mention)
706       .await?
707       .map_err(LemmyError::from)
708       .map_err(|e| e.with_message("couldnt_update_comment"))?;
709
710     let person_mention_id = read_person_mention.id;
711     let person_id = local_user_view.person.id;
712     let person_mention_view = blocking(context.pool(), move |conn| {
713       PersonMentionView::read(conn, person_mention_id, Some(person_id))
714     })
715     .await??;
716
717     Ok(PersonMentionResponse {
718       person_mention_view,
719     })
720   }
721 }
722
723 #[async_trait::async_trait(?Send)]
724 impl Perform for MarkAllAsRead {
725   type Response = GetRepliesResponse;
726
727   #[tracing::instrument(skip(context, _websocket_id))]
728   async fn perform(
729     &self,
730     context: &Data<LemmyContext>,
731     _websocket_id: Option<ConnectionId>,
732   ) -> Result<GetRepliesResponse, LemmyError> {
733     let data: &MarkAllAsRead = self;
734     let local_user_view =
735       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
736
737     let person_id = local_user_view.person.id;
738     let replies = blocking(context.pool(), move |conn| {
739       CommentQueryBuilder::create(conn)
740         .my_person_id(person_id)
741         .recipient_id(person_id)
742         .unread_only(true)
743         .page(1)
744         .limit(999)
745         .list()
746     })
747     .await??;
748
749     // TODO: this should probably be a bulk operation
750     // Not easy to do as a bulk operation,
751     // because recipient_id isn't in the comment table
752     for comment_view in &replies {
753       let reply_id = comment_view.comment.id;
754       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
755       blocking(context.pool(), mark_as_read)
756         .await?
757         .map_err(LemmyError::from)
758         .map_err(|e| e.with_message("couldnt_update_comment"))?;
759     }
760
761     // Mark all user mentions as read
762     let update_person_mentions =
763       move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
764     blocking(context.pool(), update_person_mentions)
765       .await?
766       .map_err(LemmyError::from)
767       .map_err(|e| e.with_message("couldnt_update_comment"))?;
768
769     // Mark all private_messages as read
770     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
771     blocking(context.pool(), update_pm)
772       .await?
773       .map_err(LemmyError::from)
774       .map_err(|e| e.with_message("couldnt_update_private_message"))?;
775
776     Ok(GetRepliesResponse { replies: vec![] })
777   }
778 }
779
780 #[async_trait::async_trait(?Send)]
781 impl Perform for PasswordReset {
782   type Response = PasswordResetResponse;
783
784   #[tracing::instrument(skip(self, context, _websocket_id))]
785   async fn perform(
786     &self,
787     context: &Data<LemmyContext>,
788     _websocket_id: Option<ConnectionId>,
789   ) -> Result<PasswordResetResponse, LemmyError> {
790     let data: &PasswordReset = self;
791
792     // Fetch that email
793     let email = data.email.clone();
794     let local_user_view = blocking(context.pool(), move |conn| {
795       LocalUserView::find_by_email(conn, &email)
796     })
797     .await?
798     .map_err(LemmyError::from)
799     .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
800
801     // Email the pure token to the user.
802     send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
803     Ok(PasswordResetResponse {})
804   }
805 }
806
807 #[async_trait::async_trait(?Send)]
808 impl Perform for PasswordChange {
809   type Response = LoginResponse;
810
811   #[tracing::instrument(skip(self, context, _websocket_id))]
812   async fn perform(
813     &self,
814     context: &Data<LemmyContext>,
815     _websocket_id: Option<ConnectionId>,
816   ) -> Result<LoginResponse, LemmyError> {
817     let data: &PasswordChange = self;
818
819     // Fetch the user_id from the token
820     let token = data.token.clone();
821     let local_user_id = blocking(context.pool(), move |conn| {
822       PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
823     })
824     .await??;
825
826     password_length_check(&data.password)?;
827
828     // Make sure passwords match
829     if data.password != data.password_verify {
830       return Err(LemmyError::from_message("passwords_dont_match"));
831     }
832
833     // Update the user with the new password
834     let password = data.password.clone();
835     let updated_local_user = blocking(context.pool(), move |conn| {
836       LocalUser::update_password(conn, local_user_id, &password)
837     })
838     .await?
839     .map_err(LemmyError::from)
840     .map_err(|e| e.with_message("couldnt_update_user"))?;
841
842     // Return the jwt
843     Ok(LoginResponse {
844       jwt: Some(
845         Claims::jwt(
846           updated_local_user.id.0,
847           &context.secret().jwt_secret,
848           &context.settings().hostname,
849         )?
850         .into(),
851       ),
852       verify_email_sent: false,
853       registration_created: false,
854     })
855   }
856 }
857
858 #[async_trait::async_trait(?Send)]
859 impl Perform for GetReportCount {
860   type Response = GetReportCountResponse;
861
862   #[tracing::instrument(skip(context, _websocket_id))]
863   async fn perform(
864     &self,
865     context: &Data<LemmyContext>,
866     _websocket_id: Option<ConnectionId>,
867   ) -> Result<GetReportCountResponse, LemmyError> {
868     let data: &GetReportCount = self;
869     let local_user_view =
870       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
871
872     let person_id = local_user_view.person.id;
873     let admin = local_user_view.person.admin;
874     let community_id = data.community_id;
875
876     let comment_reports = blocking(context.pool(), move |conn| {
877       CommentReportView::get_report_count(conn, person_id, admin, community_id)
878     })
879     .await??;
880
881     let post_reports = blocking(context.pool(), move |conn| {
882       PostReportView::get_report_count(conn, person_id, admin, community_id)
883     })
884     .await??;
885
886     let res = GetReportCountResponse {
887       community_id,
888       comment_reports,
889       post_reports,
890     };
891
892     Ok(res)
893   }
894 }
895
896 #[async_trait::async_trait(?Send)]
897 impl Perform for GetUnreadCount {
898   type Response = GetUnreadCountResponse;
899
900   #[tracing::instrument(skip(context, _websocket_id))]
901   async fn perform(
902     &self,
903     context: &Data<LemmyContext>,
904     _websocket_id: Option<ConnectionId>,
905   ) -> Result<Self::Response, LemmyError> {
906     let data = self;
907     let local_user_view =
908       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
909
910     let person_id = local_user_view.person.id;
911
912     let replies = blocking(context.pool(), move |conn| {
913       CommentView::get_unread_replies(conn, person_id)
914     })
915     .await??;
916
917     let mentions = blocking(context.pool(), move |conn| {
918       PersonMentionView::get_unread_mentions(conn, person_id)
919     })
920     .await??;
921
922     let private_messages = blocking(context.pool(), move |conn| {
923       PrivateMessageView::get_unread_messages(conn, person_id)
924     })
925     .await??;
926
927     let res = Self::Response {
928       replies,
929       mentions,
930       private_messages,
931     };
932
933     Ok(res)
934   }
935 }
936
937 #[async_trait::async_trait(?Send)]
938 impl Perform for VerifyEmail {
939   type Response = VerifyEmailResponse;
940
941   async fn perform(
942     &self,
943     context: &Data<LemmyContext>,
944     _websocket_id: Option<usize>,
945   ) -> Result<Self::Response, LemmyError> {
946     let token = self.token.clone();
947     let verification = blocking(context.pool(), move |conn| {
948       EmailVerification::read_for_token(conn, &token)
949     })
950     .await?
951     .map_err(LemmyError::from)
952     .map_err(|e| e.with_message("token_not_found"))?;
953
954     let form = LocalUserForm {
955       // necessary in case this is a new signup
956       email_verified: Some(true),
957       // necessary in case email of an existing user was changed
958       email: Some(Some(verification.email)),
959       ..LocalUserForm::default()
960     };
961     let local_user_id = verification.local_user_id;
962     blocking(context.pool(), move |conn| {
963       LocalUser::update(conn, local_user_id, &form)
964     })
965     .await??;
966
967     let local_user_view = blocking(context.pool(), move |conn| {
968       LocalUserView::read(conn, local_user_id)
969     })
970     .await??;
971
972     send_email_verification_success(&local_user_view, &context.settings())?;
973
974     blocking(context.pool(), move |conn| {
975       EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
976     })
977     .await??;
978
979     Ok(VerifyEmailResponse {})
980   }
981 }