]> Untitled Git - lemmy.git/blob - crates/api/src/local_user.rs
8a2d2a9224225b25aac3735798c60760e7a94452
[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     };
260
261     blocking(context.pool(), move |conn| {
262       Person::update(conn, person_id, &person_form)
263     })
264     .await?
265     .map_err(LemmyError::from)
266     .map_err(|e| e.with_message("user_already_exists"))?;
267
268     let local_user_form = LocalUserForm {
269       person_id: Some(person_id),
270       email,
271       password_encrypted: Some(password_encrypted),
272       show_nsfw: data.show_nsfw,
273       show_bot_accounts: data.show_bot_accounts,
274       show_scores: data.show_scores,
275       theme: data.theme.to_owned(),
276       default_sort_type,
277       default_listing_type,
278       lang: data.lang.to_owned(),
279       show_avatars: data.show_avatars,
280       show_read_posts: data.show_read_posts,
281       show_new_post_notifs: data.show_new_post_notifs,
282       send_notifications_to_email: data.send_notifications_to_email,
283       email_verified: None,
284       accepted_application: None,
285     };
286
287     let local_user_res = blocking(context.pool(), move |conn| {
288       LocalUser::update(conn, local_user_id, &local_user_form)
289     })
290     .await?;
291     let updated_local_user = match local_user_res {
292       Ok(u) => u,
293       Err(e) => {
294         let err_type = if e.to_string()
295           == "duplicate key value violates unique constraint \"local_user_email_key\""
296         {
297           "email_already_exists"
298         } else {
299           "user_already_exists"
300         };
301
302         return Err(LemmyError::from(e).with_message(err_type));
303       }
304     };
305
306     // Return the jwt
307     Ok(LoginResponse {
308       jwt: Some(
309         Claims::jwt(
310           updated_local_user.id.0,
311           &context.secret().jwt_secret,
312           &context.settings().hostname,
313         )?
314         .into(),
315       ),
316       verify_email_sent: false,
317       registration_created: false,
318     })
319   }
320 }
321
322 #[async_trait::async_trait(?Send)]
323 impl Perform for ChangePassword {
324   type Response = LoginResponse;
325
326   #[tracing::instrument(skip(self, context, _websocket_id))]
327   async fn perform(
328     &self,
329     context: &Data<LemmyContext>,
330     _websocket_id: Option<ConnectionId>,
331   ) -> Result<LoginResponse, LemmyError> {
332     let data: &ChangePassword = self;
333     let local_user_view =
334       get_local_user_view_from_jwt(data.auth.as_ref(), context.pool(), context.secret()).await?;
335
336     password_length_check(&data.new_password)?;
337
338     // Make sure passwords match
339     if data.new_password != data.new_password_verify {
340       return Err(LemmyError::from_message("passwords_dont_match"));
341     }
342
343     // Check the old password
344     let valid: bool = verify(
345       &data.old_password,
346       &local_user_view.local_user.password_encrypted,
347     )
348     .unwrap_or(false);
349     if !valid {
350       return Err(LemmyError::from_message("password_incorrect"));
351     }
352
353     let local_user_id = local_user_view.local_user.id;
354     let new_password = data.new_password.to_owned();
355     let updated_local_user = blocking(context.pool(), move |conn| {
356       LocalUser::update_password(conn, local_user_id, &new_password)
357     })
358     .await??;
359
360     // Return the jwt
361     Ok(LoginResponse {
362       jwt: Some(
363         Claims::jwt(
364           updated_local_user.id.0,
365           &context.secret().jwt_secret,
366           &context.settings().hostname,
367         )?
368         .into(),
369       ),
370       verify_email_sent: false,
371       registration_created: false,
372     })
373   }
374 }
375
376 #[async_trait::async_trait(?Send)]
377 impl Perform for AddAdmin {
378   type Response = AddAdminResponse;
379
380   #[tracing::instrument(skip(context, websocket_id))]
381   async fn perform(
382     &self,
383     context: &Data<LemmyContext>,
384     websocket_id: Option<ConnectionId>,
385   ) -> Result<AddAdminResponse, LemmyError> {
386     let data: &AddAdmin = self;
387     let local_user_view =
388       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
389
390     // Make sure user is an admin
391     is_admin(&local_user_view)?;
392
393     let added = data.added;
394     let added_person_id = data.person_id;
395     let added_admin = blocking(context.pool(), move |conn| {
396       Person::add_admin(conn, added_person_id, added)
397     })
398     .await?
399     .map_err(LemmyError::from)
400     .map_err(|e| e.with_message("couldnt_update_user"))?;
401
402     // Mod tables
403     let form = ModAddForm {
404       mod_person_id: local_user_view.person.id,
405       other_person_id: added_admin.id,
406       removed: Some(!data.added),
407     };
408
409     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
410
411     let site_creator_id = blocking(context.pool(), move |conn| {
412       Site::read(conn, 1).map(|s| s.creator_id)
413     })
414     .await??;
415
416     let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
417     let creator_index = admins
418       .iter()
419       .position(|r| r.person.id == site_creator_id)
420       .context(location_info!())?;
421     let creator_person = admins.remove(creator_index);
422     admins.insert(0, creator_person);
423
424     let res = AddAdminResponse { admins };
425
426     context.chat_server().do_send(SendAllMessage {
427       op: UserOperation::AddAdmin,
428       response: res.clone(),
429       websocket_id,
430     });
431
432     Ok(res)
433   }
434 }
435
436 #[async_trait::async_trait(?Send)]
437 impl Perform for BanPerson {
438   type Response = BanPersonResponse;
439
440   #[tracing::instrument(skip(context, websocket_id))]
441   async fn perform(
442     &self,
443     context: &Data<LemmyContext>,
444     websocket_id: Option<ConnectionId>,
445   ) -> Result<BanPersonResponse, LemmyError> {
446     let data: &BanPerson = self;
447     let local_user_view =
448       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
449
450     // Make sure user is an admin
451     is_admin(&local_user_view)?;
452
453     let ban = data.ban;
454     let banned_person_id = data.person_id;
455     let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban);
456     blocking(context.pool(), ban_person)
457       .await?
458       .map_err(LemmyError::from)
459       .map_err(|e| e.with_message("couldnt_update_user"))?;
460
461     // Remove their data if that's desired
462     if data.remove_data.unwrap_or(false) {
463       // Posts
464       blocking(context.pool(), move |conn: &'_ _| {
465         Post::update_removed_for_creator(conn, banned_person_id, None, true)
466       })
467       .await??;
468
469       // Communities
470       // Remove all communities where they're the top mod
471       // for now, remove the communities manually
472       let first_mod_communities = blocking(context.pool(), move |conn: &'_ _| {
473         CommunityModeratorView::get_community_first_mods(conn)
474       })
475       .await??;
476
477       // Filter to only this banned users top communities
478       let banned_user_first_communities: Vec<CommunityModeratorView> = first_mod_communities
479         .into_iter()
480         .filter(|fmc| fmc.moderator.id == banned_person_id)
481         .collect();
482
483       for first_mod_community in banned_user_first_communities {
484         blocking(context.pool(), move |conn: &'_ _| {
485           Community::update_removed(conn, first_mod_community.community.id, true)
486         })
487         .await??;
488       }
489
490       // Comments
491       blocking(context.pool(), move |conn: &'_ _| {
492         Comment::update_removed_for_creator(conn, banned_person_id, true)
493       })
494       .await??;
495     }
496
497     // Mod tables
498     let expires = data.expires.map(naive_from_unix);
499
500     let form = ModBanForm {
501       mod_person_id: local_user_view.person.id,
502       other_person_id: data.person_id,
503       reason: data.reason.to_owned(),
504       banned: Some(data.ban),
505       expires,
506     };
507
508     blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
509
510     let person_id = data.person_id;
511     let person_view = blocking(context.pool(), move |conn| {
512       PersonViewSafe::read(conn, person_id)
513     })
514     .await??;
515
516     let res = BanPersonResponse {
517       person_view,
518       banned: data.ban,
519     };
520
521     context.chat_server().do_send(SendAllMessage {
522       op: UserOperation::BanPerson,
523       response: res.clone(),
524       websocket_id,
525     });
526
527     Ok(res)
528   }
529 }
530
531 #[async_trait::async_trait(?Send)]
532 impl Perform for GetBannedPersons {
533   type Response = BannedPersonsResponse;
534
535   async fn perform(
536     &self,
537     context: &Data<LemmyContext>,
538     _websocket_id: Option<ConnectionId>,
539   ) -> Result<Self::Response, LemmyError> {
540     let data: &GetBannedPersons = self;
541     let local_user_view =
542       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
543
544     // Make sure user is an admin
545     is_admin(&local_user_view)?;
546
547     let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
548
549     let res = Self::Response { banned };
550
551     Ok(res)
552   }
553 }
554
555 #[async_trait::async_trait(?Send)]
556 impl Perform for BlockPerson {
557   type Response = BlockPersonResponse;
558
559   #[tracing::instrument(skip(context, _websocket_id))]
560   async fn perform(
561     &self,
562     context: &Data<LemmyContext>,
563     _websocket_id: Option<ConnectionId>,
564   ) -> Result<BlockPersonResponse, LemmyError> {
565     let data: &BlockPerson = self;
566     let local_user_view =
567       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
568
569     let target_id = data.person_id;
570     let person_id = local_user_view.person.id;
571
572     // Don't let a person block themselves
573     if target_id == person_id {
574       return Err(LemmyError::from_message("cant_block_yourself"));
575     }
576
577     let person_block_form = PersonBlockForm {
578       person_id,
579       target_id,
580     };
581
582     if data.block {
583       let block = move |conn: &'_ _| PersonBlock::block(conn, &person_block_form);
584       blocking(context.pool(), block)
585         .await?
586         .map_err(LemmyError::from)
587         .map_err(|e| e.with_message("person_block_already_exists"))?;
588     } else {
589       let unblock = move |conn: &'_ _| PersonBlock::unblock(conn, &person_block_form);
590       blocking(context.pool(), unblock)
591         .await?
592         .map_err(LemmyError::from)
593         .map_err(|e| e.with_message("person_block_already_exists"))?;
594     }
595
596     // TODO does any federated stuff need to be done here?
597
598     let person_view = blocking(context.pool(), move |conn| {
599       PersonViewSafe::read(conn, target_id)
600     })
601     .await??;
602
603     let res = BlockPersonResponse {
604       person_view,
605       blocked: data.block,
606     };
607
608     Ok(res)
609   }
610 }
611
612 #[async_trait::async_trait(?Send)]
613 impl Perform for GetReplies {
614   type Response = GetRepliesResponse;
615
616   #[tracing::instrument(skip(context, _websocket_id))]
617   async fn perform(
618     &self,
619     context: &Data<LemmyContext>,
620     _websocket_id: Option<ConnectionId>,
621   ) -> Result<GetRepliesResponse, LemmyError> {
622     let data: &GetReplies = self;
623     let local_user_view =
624       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
625
626     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
627
628     let page = data.page;
629     let limit = data.limit;
630     let unread_only = data.unread_only;
631     let person_id = local_user_view.person.id;
632     let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
633
634     let replies = blocking(context.pool(), move |conn| {
635       CommentQueryBuilder::create(conn)
636         .sort(sort)
637         .unread_only(unread_only)
638         .recipient_id(person_id)
639         .show_bot_accounts(show_bot_accounts)
640         .my_person_id(person_id)
641         .page(page)
642         .limit(limit)
643         .list()
644     })
645     .await??;
646
647     Ok(GetRepliesResponse { replies })
648   }
649 }
650
651 #[async_trait::async_trait(?Send)]
652 impl Perform for GetPersonMentions {
653   type Response = GetPersonMentionsResponse;
654
655   #[tracing::instrument(skip(context, _websocket_id))]
656   async fn perform(
657     &self,
658     context: &Data<LemmyContext>,
659     _websocket_id: Option<ConnectionId>,
660   ) -> Result<GetPersonMentionsResponse, LemmyError> {
661     let data: &GetPersonMentions = self;
662     let local_user_view =
663       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
664
665     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
666
667     let page = data.page;
668     let limit = data.limit;
669     let unread_only = data.unread_only;
670     let person_id = local_user_view.person.id;
671     let mentions = blocking(context.pool(), move |conn| {
672       PersonMentionQueryBuilder::create(conn)
673         .recipient_id(person_id)
674         .my_person_id(person_id)
675         .sort(sort)
676         .unread_only(unread_only)
677         .page(page)
678         .limit(limit)
679         .list()
680     })
681     .await??;
682
683     Ok(GetPersonMentionsResponse { mentions })
684   }
685 }
686
687 #[async_trait::async_trait(?Send)]
688 impl Perform for MarkPersonMentionAsRead {
689   type Response = PersonMentionResponse;
690
691   #[tracing::instrument(skip(context, _websocket_id))]
692   async fn perform(
693     &self,
694     context: &Data<LemmyContext>,
695     _websocket_id: Option<ConnectionId>,
696   ) -> Result<PersonMentionResponse, LemmyError> {
697     let data: &MarkPersonMentionAsRead = self;
698     let local_user_view =
699       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
700
701     let person_mention_id = data.person_mention_id;
702     let read_person_mention = blocking(context.pool(), move |conn| {
703       PersonMention::read(conn, person_mention_id)
704     })
705     .await??;
706
707     if local_user_view.person.id != read_person_mention.recipient_id {
708       return Err(LemmyError::from_message("couldnt_update_comment"));
709     }
710
711     let person_mention_id = read_person_mention.id;
712     let read = data.read;
713     let update_mention =
714       move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
715     blocking(context.pool(), update_mention)
716       .await?
717       .map_err(LemmyError::from)
718       .map_err(|e| e.with_message("couldnt_update_comment"))?;
719
720     let person_mention_id = read_person_mention.id;
721     let person_id = local_user_view.person.id;
722     let person_mention_view = blocking(context.pool(), move |conn| {
723       PersonMentionView::read(conn, person_mention_id, Some(person_id))
724     })
725     .await??;
726
727     Ok(PersonMentionResponse {
728       person_mention_view,
729     })
730   }
731 }
732
733 #[async_trait::async_trait(?Send)]
734 impl Perform for MarkAllAsRead {
735   type Response = GetRepliesResponse;
736
737   #[tracing::instrument(skip(context, _websocket_id))]
738   async fn perform(
739     &self,
740     context: &Data<LemmyContext>,
741     _websocket_id: Option<ConnectionId>,
742   ) -> Result<GetRepliesResponse, LemmyError> {
743     let data: &MarkAllAsRead = self;
744     let local_user_view =
745       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
746
747     let person_id = local_user_view.person.id;
748     let replies = blocking(context.pool(), move |conn| {
749       CommentQueryBuilder::create(conn)
750         .my_person_id(person_id)
751         .recipient_id(person_id)
752         .unread_only(true)
753         .page(1)
754         .limit(999)
755         .list()
756     })
757     .await??;
758
759     // TODO: this should probably be a bulk operation
760     // Not easy to do as a bulk operation,
761     // because recipient_id isn't in the comment table
762     for comment_view in &replies {
763       let reply_id = comment_view.comment.id;
764       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
765       blocking(context.pool(), mark_as_read)
766         .await?
767         .map_err(LemmyError::from)
768         .map_err(|e| e.with_message("couldnt_update_comment"))?;
769     }
770
771     // Mark all user mentions as read
772     let update_person_mentions =
773       move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
774     blocking(context.pool(), update_person_mentions)
775       .await?
776       .map_err(LemmyError::from)
777       .map_err(|e| e.with_message("couldnt_update_comment"))?;
778
779     // Mark all private_messages as read
780     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
781     blocking(context.pool(), update_pm)
782       .await?
783       .map_err(LemmyError::from)
784       .map_err(|e| e.with_message("couldnt_update_private_message"))?;
785
786     Ok(GetRepliesResponse { replies: vec![] })
787   }
788 }
789
790 #[async_trait::async_trait(?Send)]
791 impl Perform for PasswordReset {
792   type Response = PasswordResetResponse;
793
794   #[tracing::instrument(skip(self, context, _websocket_id))]
795   async fn perform(
796     &self,
797     context: &Data<LemmyContext>,
798     _websocket_id: Option<ConnectionId>,
799   ) -> Result<PasswordResetResponse, LemmyError> {
800     let data: &PasswordReset = self;
801
802     // Fetch that email
803     let email = data.email.clone();
804     let local_user_view = blocking(context.pool(), move |conn| {
805       LocalUserView::find_by_email(conn, &email)
806     })
807     .await?
808     .map_err(LemmyError::from)
809     .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))?;
810
811     // Email the pure token to the user.
812     send_password_reset_email(&local_user_view, context.pool(), &context.settings()).await?;
813     Ok(PasswordResetResponse {})
814   }
815 }
816
817 #[async_trait::async_trait(?Send)]
818 impl Perform for PasswordChange {
819   type Response = LoginResponse;
820
821   #[tracing::instrument(skip(self, context, _websocket_id))]
822   async fn perform(
823     &self,
824     context: &Data<LemmyContext>,
825     _websocket_id: Option<ConnectionId>,
826   ) -> Result<LoginResponse, LemmyError> {
827     let data: &PasswordChange = self;
828
829     // Fetch the user_id from the token
830     let token = data.token.clone();
831     let local_user_id = blocking(context.pool(), move |conn| {
832       PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
833     })
834     .await??;
835
836     password_length_check(&data.password)?;
837
838     // Make sure passwords match
839     if data.password != data.password_verify {
840       return Err(LemmyError::from_message("passwords_dont_match"));
841     }
842
843     // Update the user with the new password
844     let password = data.password.clone();
845     let updated_local_user = blocking(context.pool(), move |conn| {
846       LocalUser::update_password(conn, local_user_id, &password)
847     })
848     .await?
849     .map_err(LemmyError::from)
850     .map_err(|e| e.with_message("couldnt_update_user"))?;
851
852     // Return the jwt
853     Ok(LoginResponse {
854       jwt: Some(
855         Claims::jwt(
856           updated_local_user.id.0,
857           &context.secret().jwt_secret,
858           &context.settings().hostname,
859         )?
860         .into(),
861       ),
862       verify_email_sent: false,
863       registration_created: false,
864     })
865   }
866 }
867
868 #[async_trait::async_trait(?Send)]
869 impl Perform for GetReportCount {
870   type Response = GetReportCountResponse;
871
872   #[tracing::instrument(skip(context, _websocket_id))]
873   async fn perform(
874     &self,
875     context: &Data<LemmyContext>,
876     _websocket_id: Option<ConnectionId>,
877   ) -> Result<GetReportCountResponse, LemmyError> {
878     let data: &GetReportCount = self;
879     let local_user_view =
880       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
881
882     let person_id = local_user_view.person.id;
883     let admin = local_user_view.person.admin;
884     let community_id = data.community_id;
885
886     let comment_reports = blocking(context.pool(), move |conn| {
887       CommentReportView::get_report_count(conn, person_id, admin, community_id)
888     })
889     .await??;
890
891     let post_reports = blocking(context.pool(), move |conn| {
892       PostReportView::get_report_count(conn, person_id, admin, community_id)
893     })
894     .await??;
895
896     let res = GetReportCountResponse {
897       community_id,
898       comment_reports,
899       post_reports,
900     };
901
902     Ok(res)
903   }
904 }
905
906 #[async_trait::async_trait(?Send)]
907 impl Perform for GetUnreadCount {
908   type Response = GetUnreadCountResponse;
909
910   #[tracing::instrument(skip(context, _websocket_id))]
911   async fn perform(
912     &self,
913     context: &Data<LemmyContext>,
914     _websocket_id: Option<ConnectionId>,
915   ) -> Result<Self::Response, LemmyError> {
916     let data = self;
917     let local_user_view =
918       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
919
920     let person_id = local_user_view.person.id;
921
922     let replies = blocking(context.pool(), move |conn| {
923       CommentView::get_unread_replies(conn, person_id)
924     })
925     .await??;
926
927     let mentions = blocking(context.pool(), move |conn| {
928       PersonMentionView::get_unread_mentions(conn, person_id)
929     })
930     .await??;
931
932     let private_messages = blocking(context.pool(), move |conn| {
933       PrivateMessageView::get_unread_messages(conn, person_id)
934     })
935     .await??;
936
937     let res = Self::Response {
938       replies,
939       mentions,
940       private_messages,
941     };
942
943     Ok(res)
944   }
945 }
946
947 #[async_trait::async_trait(?Send)]
948 impl Perform for VerifyEmail {
949   type Response = VerifyEmailResponse;
950
951   async fn perform(
952     &self,
953     context: &Data<LemmyContext>,
954     _websocket_id: Option<usize>,
955   ) -> Result<Self::Response, LemmyError> {
956     let token = self.token.clone();
957     let verification = blocking(context.pool(), move |conn| {
958       EmailVerification::read_for_token(conn, &token)
959     })
960     .await?
961     .map_err(LemmyError::from)
962     .map_err(|e| e.with_message("token_not_found"))?;
963
964     let form = LocalUserForm {
965       // necessary in case this is a new signup
966       email_verified: Some(true),
967       // necessary in case email of an existing user was changed
968       email: Some(Some(verification.email)),
969       ..LocalUserForm::default()
970     };
971     let local_user_id = verification.local_user_id;
972     blocking(context.pool(), move |conn| {
973       LocalUser::update(conn, local_user_id, &form)
974     })
975     .await??;
976
977     let local_user_view = blocking(context.pool(), move |conn| {
978       LocalUserView::read(conn, local_user_id)
979     })
980     .await??;
981
982     send_email_verification_success(&local_user_view, &context.settings())?;
983
984     blocking(context.pool(), move |conn| {
985       EmailVerification::delete_old_tokens_for_local_user(conn, local_user_id)
986     })
987     .await??;
988
989     Ok(VerifyEmailResponse {})
990   }
991 }