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