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