]> Untitled Git - lemmy.git/blob - crates/api/src/local_user.rs
Merge pull request #1500 from LemmyNet/jwt_revocation_dess
[lemmy.git] / crates / api / src / local_user.rs
1 use crate::{
2   captcha_espeak_wav_base64,
3   collect_moderated_communities,
4   get_local_user_view_from_jwt,
5   get_local_user_view_from_jwt_opt,
6   is_admin,
7   password_length_check,
8   Perform,
9 };
10 use actix_web::web::Data;
11 use anyhow::Context;
12 use bcrypt::verify;
13 use captcha::{gen, Difficulty};
14 use chrono::Duration;
15 use lemmy_api_structs::{blocking, person::*, send_email_to_user};
16 use lemmy_apub::{
17   generate_apub_endpoint,
18   generate_followers_url,
19   generate_inbox_url,
20   generate_shared_inbox_url,
21   ApubObjectType,
22   EndpointType,
23 };
24 use lemmy_db_queries::{
25   diesel_option_overwrite,
26   diesel_option_overwrite_to_url,
27   source::{
28     comment::Comment_,
29     community::Community_,
30     local_user::LocalUser_,
31     password_reset_request::PasswordResetRequest_,
32     person::Person_,
33     person_mention::PersonMention_,
34     post::Post_,
35     private_message::PrivateMessage_,
36     site::Site_,
37   },
38   Crud,
39   Followable,
40   Joinable,
41   ListingType,
42   SortType,
43 };
44 use lemmy_db_schema::{
45   naive_now,
46   source::{
47     comment::Comment,
48     community::*,
49     local_user::{LocalUser, LocalUserForm},
50     moderator::*,
51     password_reset_request::*,
52     person::*,
53     person_mention::*,
54     post::Post,
55     private_message::*,
56     site::*,
57   },
58   CommunityId,
59 };
60 use lemmy_db_views::{
61   comment_report_view::CommentReportView,
62   comment_view::CommentQueryBuilder,
63   local_user_view::LocalUserView,
64   post_report_view::PostReportView,
65   post_view::PostQueryBuilder,
66   private_message_view::{PrivateMessageQueryBuilder, PrivateMessageView},
67 };
68 use lemmy_db_views_actor::{
69   community_follower_view::CommunityFollowerView,
70   community_moderator_view::CommunityModeratorView,
71   person_mention_view::{PersonMentionQueryBuilder, PersonMentionView},
72   person_view::PersonViewSafe,
73 };
74 use lemmy_utils::{
75   apub::generate_actor_keypair,
76   claims::Claims,
77   email::send_email,
78   location_info,
79   settings::structs::Settings,
80   utils::{
81     check_slurs,
82     generate_random_string,
83     is_valid_preferred_username,
84     is_valid_username,
85     naive_from_unix,
86     remove_slurs,
87   },
88   ApiError,
89   ConnectionId,
90   LemmyError,
91 };
92 use lemmy_websocket::{
93   messages::{CaptchaItem, CheckCaptcha, SendAllMessage, SendUserRoomMessage},
94   LemmyContext,
95   UserOperation,
96 };
97 use std::str::FromStr;
98
99 #[async_trait::async_trait(?Send)]
100 impl Perform for Login {
101   type Response = LoginResponse;
102
103   async fn perform(
104     &self,
105     context: &Data<LemmyContext>,
106     _websocket_id: Option<ConnectionId>,
107   ) -> Result<LoginResponse, LemmyError> {
108     let data: &Login = &self;
109
110     // Fetch that username / email
111     let username_or_email = data.username_or_email.clone();
112     let local_user_view = match blocking(context.pool(), move |conn| {
113       LocalUserView::find_by_email_or_name(conn, &username_or_email)
114     })
115     .await?
116     {
117       Ok(uv) => uv,
118       Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
119     };
120
121     // Verify the password
122     let valid: bool = verify(
123       &data.password,
124       &local_user_view.local_user.password_encrypted,
125     )
126     .unwrap_or(false);
127     if !valid {
128       return Err(ApiError::err("password_incorrect").into());
129     }
130
131     // Return the jwt
132     Ok(LoginResponse {
133       jwt: Claims::jwt(local_user_view.local_user.id.0)?,
134     })
135   }
136 }
137
138 #[async_trait::async_trait(?Send)]
139 impl Perform for Register {
140   type Response = LoginResponse;
141
142   async fn perform(
143     &self,
144     context: &Data<LemmyContext>,
145     _websocket_id: Option<ConnectionId>,
146   ) -> Result<LoginResponse, LemmyError> {
147     let data: &Register = &self;
148
149     // Make sure site has open registration
150     if let Ok(site) = blocking(context.pool(), move |conn| Site::read_simple(conn)).await? {
151       if !site.open_registration {
152         return Err(ApiError::err("registration_closed").into());
153       }
154     }
155
156     password_length_check(&data.password)?;
157
158     // Make sure passwords match
159     if data.password != data.password_verify {
160       return Err(ApiError::err("passwords_dont_match").into());
161     }
162
163     // Check if there are admins. False if admins exist
164     let no_admins = blocking(context.pool(), move |conn| {
165       PersonViewSafe::admins(conn).map(|a| a.is_empty())
166     })
167     .await??;
168
169     // If its not the admin, check the captcha
170     if !no_admins && Settings::get().captcha().enabled {
171       let check = context
172         .chat_server()
173         .send(CheckCaptcha {
174           uuid: data
175             .captcha_uuid
176             .to_owned()
177             .unwrap_or_else(|| "".to_string()),
178           answer: data
179             .captcha_answer
180             .to_owned()
181             .unwrap_or_else(|| "".to_string()),
182         })
183         .await?;
184       if !check {
185         return Err(ApiError::err("captcha_incorrect").into());
186       }
187     }
188
189     check_slurs(&data.username)?;
190
191     let actor_keypair = generate_actor_keypair()?;
192     if !is_valid_username(&data.username) {
193       return Err(ApiError::err("invalid_username").into());
194     }
195     let actor_id = generate_apub_endpoint(EndpointType::Person, &data.username)?;
196
197     // We have to create both a person, and local_user
198
199     // Register the new person
200     let person_form = PersonForm {
201       name: data.username.to_owned(),
202       avatar: None,
203       banner: None,
204       preferred_username: None,
205       published: None,
206       updated: None,
207       banned: None,
208       deleted: None,
209       actor_id: Some(actor_id.clone()),
210       bio: None,
211       local: Some(true),
212       private_key: Some(Some(actor_keypair.private_key)),
213       public_key: Some(Some(actor_keypair.public_key)),
214       last_refreshed_at: None,
215       inbox_url: Some(generate_inbox_url(&actor_id)?),
216       shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
217     };
218
219     // insert the person
220     let inserted_person = match blocking(context.pool(), move |conn| {
221       Person::create(conn, &person_form)
222     })
223     .await?
224     {
225       Ok(u) => u,
226       Err(_) => {
227         return Err(ApiError::err("user_already_exists").into());
228       }
229     };
230
231     // Create the local user
232     let local_user_form = LocalUserForm {
233       person_id: inserted_person.id,
234       email: Some(data.email.to_owned()),
235       matrix_user_id: None,
236       password_encrypted: data.password.to_owned(),
237       admin: Some(no_admins),
238       show_nsfw: Some(data.show_nsfw),
239       theme: Some("browser".into()),
240       default_sort_type: Some(SortType::Active as i16),
241       default_listing_type: Some(ListingType::Subscribed as i16),
242       lang: Some("browser".into()),
243       show_avatars: Some(true),
244       send_notifications_to_email: Some(false),
245     };
246
247     let inserted_local_user = match blocking(context.pool(), move |conn| {
248       LocalUser::register(conn, &local_user_form)
249     })
250     .await?
251     {
252       Ok(lu) => lu,
253       Err(e) => {
254         let err_type = if e.to_string()
255           == "duplicate key value violates unique constraint \"local_user_email_key\""
256         {
257           "email_already_exists"
258         } else {
259           "user_already_exists"
260         };
261
262         // If the local user creation errored, then delete that person
263         blocking(context.pool(), move |conn| {
264           Person::delete(&conn, inserted_person.id)
265         })
266         .await??;
267
268         return Err(ApiError::err(err_type).into());
269       }
270     };
271
272     let main_community_keypair = generate_actor_keypair()?;
273
274     // Create the main community if it doesn't exist
275     let main_community = match blocking(context.pool(), move |conn| {
276       Community::read(conn, CommunityId(2))
277     })
278     .await?
279     {
280       Ok(c) => c,
281       Err(_e) => {
282         let default_community_name = "main";
283         let actor_id = generate_apub_endpoint(EndpointType::Community, default_community_name)?;
284         let community_form = CommunityForm {
285           name: default_community_name.to_string(),
286           title: "The Default Community".to_string(),
287           description: Some("The Default Community".to_string()),
288           nsfw: false,
289           creator_id: inserted_person.id,
290           removed: None,
291           deleted: None,
292           updated: None,
293           actor_id: Some(actor_id.to_owned()),
294           local: true,
295           private_key: Some(main_community_keypair.private_key),
296           public_key: Some(main_community_keypair.public_key),
297           last_refreshed_at: None,
298           published: None,
299           icon: None,
300           banner: None,
301           followers_url: Some(generate_followers_url(&actor_id)?),
302           inbox_url: Some(generate_inbox_url(&actor_id)?),
303           shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
304         };
305         blocking(context.pool(), move |conn| {
306           Community::create(conn, &community_form)
307         })
308         .await??
309       }
310     };
311
312     // Sign them up for main community no matter what
313     let community_follower_form = CommunityFollowerForm {
314       community_id: main_community.id,
315       person_id: inserted_person.id,
316       pending: false,
317     };
318
319     let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form);
320     if blocking(context.pool(), follow).await?.is_err() {
321       return Err(ApiError::err("community_follower_already_exists").into());
322     };
323
324     // If its an admin, add them as a mod and follower to main
325     if no_admins {
326       let community_moderator_form = CommunityModeratorForm {
327         community_id: main_community.id,
328         person_id: inserted_person.id,
329       };
330
331       let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form);
332       if blocking(context.pool(), join).await?.is_err() {
333         return Err(ApiError::err("community_moderator_already_exists").into());
334       }
335     }
336
337     // Return the jwt
338     Ok(LoginResponse {
339       jwt: Claims::jwt(inserted_local_user.id.0)?,
340     })
341   }
342 }
343
344 #[async_trait::async_trait(?Send)]
345 impl Perform for GetCaptcha {
346   type Response = GetCaptchaResponse;
347
348   async fn perform(
349     &self,
350     context: &Data<LemmyContext>,
351     _websocket_id: Option<ConnectionId>,
352   ) -> Result<Self::Response, LemmyError> {
353     let captcha_settings = Settings::get().captcha();
354
355     if !captcha_settings.enabled {
356       return Ok(GetCaptchaResponse { ok: None });
357     }
358
359     let captcha = match captcha_settings.difficulty.as_str() {
360       "easy" => gen(Difficulty::Easy),
361       "medium" => gen(Difficulty::Medium),
362       "hard" => gen(Difficulty::Hard),
363       _ => gen(Difficulty::Medium),
364     };
365
366     let answer = captcha.chars_as_string();
367
368     let png_byte_array = captcha.as_png().expect("failed to generate captcha");
369
370     let png = base64::encode(png_byte_array);
371
372     let uuid = uuid::Uuid::new_v4().to_string();
373
374     let wav = captcha_espeak_wav_base64(&answer).ok();
375
376     let captcha_item = CaptchaItem {
377       answer,
378       uuid: uuid.to_owned(),
379       expires: naive_now() + Duration::minutes(10), // expires in 10 minutes
380     };
381
382     // Stores the captcha item on the queue
383     context.chat_server().do_send(captcha_item);
384
385     Ok(GetCaptchaResponse {
386       ok: Some(CaptchaResponse { png, uuid, wav }),
387     })
388   }
389 }
390
391 #[async_trait::async_trait(?Send)]
392 impl Perform for SaveUserSettings {
393   type Response = LoginResponse;
394
395   async fn perform(
396     &self,
397     context: &Data<LemmyContext>,
398     _websocket_id: Option<ConnectionId>,
399   ) -> Result<LoginResponse, LemmyError> {
400     let data: &SaveUserSettings = &self;
401     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
402
403     let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
404     let banner = diesel_option_overwrite_to_url(&data.banner)?;
405     let email = diesel_option_overwrite(&data.email);
406     let bio = diesel_option_overwrite(&data.bio);
407     let preferred_username = diesel_option_overwrite(&data.preferred_username);
408     let matrix_user_id = diesel_option_overwrite(&data.matrix_user_id);
409
410     if let Some(Some(bio)) = &bio {
411       if bio.chars().count() > 300 {
412         return Err(ApiError::err("bio_length_overflow").into());
413       }
414     }
415
416     if let Some(Some(preferred_username)) = &preferred_username {
417       if !is_valid_preferred_username(preferred_username.trim()) {
418         return Err(ApiError::err("invalid_username").into());
419       }
420     }
421
422     let local_user_id = local_user_view.local_user.id;
423     let person_id = local_user_view.person.id;
424     let password_encrypted = match &data.new_password {
425       Some(new_password) => {
426         match &data.new_password_verify {
427           Some(new_password_verify) => {
428             password_length_check(&new_password)?;
429
430             // Make sure passwords match
431             if new_password != new_password_verify {
432               return Err(ApiError::err("passwords_dont_match").into());
433             }
434
435             // Check the old password
436             match &data.old_password {
437               Some(old_password) => {
438                 let valid: bool =
439                   verify(old_password, &local_user_view.local_user.password_encrypted)
440                     .unwrap_or(false);
441                 if !valid {
442                   return Err(ApiError::err("password_incorrect").into());
443                 }
444                 let new_password = new_password.to_owned();
445                 let user = blocking(context.pool(), move |conn| {
446                   LocalUser::update_password(conn, local_user_id, &new_password)
447                 })
448                 .await??;
449                 user.password_encrypted
450               }
451               None => return Err(ApiError::err("password_incorrect").into()),
452             }
453           }
454           None => return Err(ApiError::err("passwords_dont_match").into()),
455         }
456       }
457       None => local_user_view.local_user.password_encrypted,
458     };
459
460     let default_listing_type = data.default_listing_type;
461     let default_sort_type = data.default_sort_type;
462
463     let person_form = PersonForm {
464       name: local_user_view.person.name,
465       avatar,
466       banner,
467       inbox_url: None,
468       preferred_username,
469       published: None,
470       updated: Some(naive_now()),
471       banned: None,
472       deleted: None,
473       actor_id: None,
474       bio,
475       local: None,
476       private_key: None,
477       public_key: None,
478       last_refreshed_at: None,
479       shared_inbox_url: None,
480     };
481
482     let person_res = blocking(context.pool(), move |conn| {
483       Person::update(conn, person_id, &person_form)
484     })
485     .await?;
486     let _updated_person: Person = match person_res {
487       Ok(p) => p,
488       Err(_) => {
489         return Err(ApiError::err("user_already_exists").into());
490       }
491     };
492
493     let local_user_form = LocalUserForm {
494       person_id,
495       email,
496       matrix_user_id,
497       password_encrypted,
498       admin: None,
499       show_nsfw: data.show_nsfw,
500       theme: data.theme.to_owned(),
501       default_sort_type,
502       default_listing_type,
503       lang: data.lang.to_owned(),
504       show_avatars: data.show_avatars,
505       send_notifications_to_email: data.send_notifications_to_email,
506     };
507
508     let local_user_res = blocking(context.pool(), move |conn| {
509       LocalUser::update(conn, local_user_id, &local_user_form)
510     })
511     .await?;
512     let updated_local_user = match local_user_res {
513       Ok(u) => u,
514       Err(e) => {
515         let err_type = if e.to_string()
516           == "duplicate key value violates unique constraint \"local_user_email_key\""
517         {
518           "email_already_exists"
519         } else {
520           "user_already_exists"
521         };
522
523         return Err(ApiError::err(err_type).into());
524       }
525     };
526
527     // Return the jwt
528     Ok(LoginResponse {
529       jwt: Claims::jwt(updated_local_user.id.0)?,
530     })
531   }
532 }
533
534 #[async_trait::async_trait(?Send)]
535 impl Perform for GetPersonDetails {
536   type Response = GetPersonDetailsResponse;
537
538   async fn perform(
539     &self,
540     context: &Data<LemmyContext>,
541     _websocket_id: Option<ConnectionId>,
542   ) -> Result<GetPersonDetailsResponse, LemmyError> {
543     let data: &GetPersonDetails = &self;
544     let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
545
546     let show_nsfw = match &local_user_view {
547       Some(uv) => uv.local_user.show_nsfw,
548       None => false,
549     };
550
551     let sort = SortType::from_str(&data.sort)?;
552
553     let username = data
554       .username
555       .to_owned()
556       .unwrap_or_else(|| "admin".to_string());
557     let person_details_id = match data.person_id {
558       Some(id) => id,
559       None => {
560         let person = blocking(context.pool(), move |conn| {
561           Person::find_by_name(conn, &username)
562         })
563         .await?;
564         match person {
565           Ok(p) => p.id,
566           Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
567         }
568       }
569     };
570
571     let person_id = local_user_view.map(|uv| uv.person.id);
572
573     // You don't need to return settings for the user, since this comes back with GetSite
574     // `my_user`
575     let person_view = blocking(context.pool(), move |conn| {
576       PersonViewSafe::read(conn, person_details_id)
577     })
578     .await??;
579
580     let page = data.page;
581     let limit = data.limit;
582     let saved_only = data.saved_only;
583     let community_id = data.community_id;
584
585     let (posts, comments) = blocking(context.pool(), move |conn| {
586       let mut posts_query = PostQueryBuilder::create(conn)
587         .sort(&sort)
588         .show_nsfw(show_nsfw)
589         .saved_only(saved_only)
590         .community_id(community_id)
591         .my_person_id(person_id)
592         .page(page)
593         .limit(limit);
594
595       let mut comments_query = CommentQueryBuilder::create(conn)
596         .my_person_id(person_id)
597         .sort(&sort)
598         .saved_only(saved_only)
599         .page(page)
600         .limit(limit);
601
602       // If its saved only, you don't care what creator it was
603       // Or, if its not saved, then you only want it for that specific creator
604       if !saved_only {
605         posts_query = posts_query.creator_id(person_details_id);
606         comments_query = comments_query.creator_id(person_details_id);
607       }
608
609       let posts = posts_query.list()?;
610       let comments = comments_query.list()?;
611
612       Ok((posts, comments)) as Result<_, LemmyError>
613     })
614     .await??;
615
616     let mut follows = vec![];
617     if let Some(pid) = person_id {
618       if pid == person_details_id {
619         follows = blocking(context.pool(), move |conn| {
620           CommunityFollowerView::for_person(conn, person_details_id)
621         })
622         .await??;
623       }
624     };
625     let moderates = blocking(context.pool(), move |conn| {
626       CommunityModeratorView::for_person(conn, person_details_id)
627     })
628     .await??;
629
630     // Return the jwt
631     Ok(GetPersonDetailsResponse {
632       person_view,
633       follows,
634       moderates,
635       comments,
636       posts,
637     })
638   }
639 }
640
641 #[async_trait::async_trait(?Send)]
642 impl Perform for AddAdmin {
643   type Response = AddAdminResponse;
644
645   async fn perform(
646     &self,
647     context: &Data<LemmyContext>,
648     websocket_id: Option<ConnectionId>,
649   ) -> Result<AddAdminResponse, LemmyError> {
650     let data: &AddAdmin = &self;
651     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
652
653     // Make sure user is an admin
654     is_admin(&local_user_view)?;
655
656     let added = data.added;
657     let added_person_id = data.person_id;
658     let added_admin = match blocking(context.pool(), move |conn| {
659       LocalUser::add_admin(conn, added_person_id, added)
660     })
661     .await?
662     {
663       Ok(a) => a,
664       Err(_) => {
665         return Err(ApiError::err("couldnt_update_user").into());
666       }
667     };
668
669     // Mod tables
670     let form = ModAddForm {
671       mod_person_id: local_user_view.person.id,
672       other_person_id: added_admin.person_id,
673       removed: Some(!data.added),
674     };
675
676     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
677
678     let site_creator_id = blocking(context.pool(), move |conn| {
679       Site::read(conn, 1).map(|s| s.creator_id)
680     })
681     .await??;
682
683     let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
684     let creator_index = admins
685       .iter()
686       .position(|r| r.person.id == site_creator_id)
687       .context(location_info!())?;
688     let creator_person = admins.remove(creator_index);
689     admins.insert(0, creator_person);
690
691     let res = AddAdminResponse { admins };
692
693     context.chat_server().do_send(SendAllMessage {
694       op: UserOperation::AddAdmin,
695       response: res.clone(),
696       websocket_id,
697     });
698
699     Ok(res)
700   }
701 }
702
703 #[async_trait::async_trait(?Send)]
704 impl Perform for BanPerson {
705   type Response = BanPersonResponse;
706
707   async fn perform(
708     &self,
709     context: &Data<LemmyContext>,
710     websocket_id: Option<ConnectionId>,
711   ) -> Result<BanPersonResponse, LemmyError> {
712     let data: &BanPerson = &self;
713     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
714
715     // Make sure user is an admin
716     is_admin(&local_user_view)?;
717
718     let ban = data.ban;
719     let banned_person_id = data.person_id;
720     let ban_person = move |conn: &'_ _| Person::ban_person(conn, banned_person_id, ban);
721     if blocking(context.pool(), ban_person).await?.is_err() {
722       return Err(ApiError::err("couldnt_update_user").into());
723     }
724
725     // Remove their data if that's desired
726     if data.remove_data {
727       // Posts
728       blocking(context.pool(), move |conn: &'_ _| {
729         Post::update_removed_for_creator(conn, banned_person_id, None, true)
730       })
731       .await??;
732
733       // Communities
734       blocking(context.pool(), move |conn: &'_ _| {
735         Community::update_removed_for_creator(conn, banned_person_id, true)
736       })
737       .await??;
738
739       // Comments
740       blocking(context.pool(), move |conn: &'_ _| {
741         Comment::update_removed_for_creator(conn, banned_person_id, true)
742       })
743       .await??;
744     }
745
746     // Mod tables
747     let expires = match data.expires {
748       Some(time) => Some(naive_from_unix(time)),
749       None => None,
750     };
751
752     let form = ModBanForm {
753       mod_person_id: local_user_view.person.id,
754       other_person_id: data.person_id,
755       reason: data.reason.to_owned(),
756       banned: Some(data.ban),
757       expires,
758     };
759
760     blocking(context.pool(), move |conn| ModBan::create(conn, &form)).await??;
761
762     let person_id = data.person_id;
763     let person_view = blocking(context.pool(), move |conn| {
764       PersonViewSafe::read(conn, person_id)
765     })
766     .await??;
767
768     let res = BanPersonResponse {
769       person_view,
770       banned: data.ban,
771     };
772
773     context.chat_server().do_send(SendAllMessage {
774       op: UserOperation::BanPerson,
775       response: res.clone(),
776       websocket_id,
777     });
778
779     Ok(res)
780   }
781 }
782
783 #[async_trait::async_trait(?Send)]
784 impl Perform for GetReplies {
785   type Response = GetRepliesResponse;
786
787   async fn perform(
788     &self,
789     context: &Data<LemmyContext>,
790     _websocket_id: Option<ConnectionId>,
791   ) -> Result<GetRepliesResponse, LemmyError> {
792     let data: &GetReplies = &self;
793     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
794
795     let sort = SortType::from_str(&data.sort)?;
796
797     let page = data.page;
798     let limit = data.limit;
799     let unread_only = data.unread_only;
800     let person_id = local_user_view.person.id;
801     let replies = blocking(context.pool(), move |conn| {
802       CommentQueryBuilder::create(conn)
803         .sort(&sort)
804         .unread_only(unread_only)
805         .recipient_id(person_id)
806         .my_person_id(person_id)
807         .page(page)
808         .limit(limit)
809         .list()
810     })
811     .await??;
812
813     Ok(GetRepliesResponse { replies })
814   }
815 }
816
817 #[async_trait::async_trait(?Send)]
818 impl Perform for GetPersonMentions {
819   type Response = GetPersonMentionsResponse;
820
821   async fn perform(
822     &self,
823     context: &Data<LemmyContext>,
824     _websocket_id: Option<ConnectionId>,
825   ) -> Result<GetPersonMentionsResponse, LemmyError> {
826     let data: &GetPersonMentions = &self;
827     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
828
829     let sort = SortType::from_str(&data.sort)?;
830
831     let page = data.page;
832     let limit = data.limit;
833     let unread_only = data.unread_only;
834     let person_id = local_user_view.person.id;
835     let mentions = blocking(context.pool(), move |conn| {
836       PersonMentionQueryBuilder::create(conn)
837         .recipient_id(person_id)
838         .my_person_id(person_id)
839         .sort(&sort)
840         .unread_only(unread_only)
841         .page(page)
842         .limit(limit)
843         .list()
844     })
845     .await??;
846
847     Ok(GetPersonMentionsResponse { mentions })
848   }
849 }
850
851 #[async_trait::async_trait(?Send)]
852 impl Perform for MarkPersonMentionAsRead {
853   type Response = PersonMentionResponse;
854
855   async fn perform(
856     &self,
857     context: &Data<LemmyContext>,
858     _websocket_id: Option<ConnectionId>,
859   ) -> Result<PersonMentionResponse, LemmyError> {
860     let data: &MarkPersonMentionAsRead = &self;
861     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
862
863     let person_mention_id = data.person_mention_id;
864     let read_person_mention = blocking(context.pool(), move |conn| {
865       PersonMention::read(conn, person_mention_id)
866     })
867     .await??;
868
869     if local_user_view.person.id != read_person_mention.recipient_id {
870       return Err(ApiError::err("couldnt_update_comment").into());
871     }
872
873     let person_mention_id = read_person_mention.id;
874     let read = data.read;
875     let update_mention =
876       move |conn: &'_ _| PersonMention::update_read(conn, person_mention_id, read);
877     if blocking(context.pool(), update_mention).await?.is_err() {
878       return Err(ApiError::err("couldnt_update_comment").into());
879     };
880
881     let person_mention_id = read_person_mention.id;
882     let person_id = local_user_view.person.id;
883     let person_mention_view = blocking(context.pool(), move |conn| {
884       PersonMentionView::read(conn, person_mention_id, Some(person_id))
885     })
886     .await??;
887
888     Ok(PersonMentionResponse {
889       person_mention_view,
890     })
891   }
892 }
893
894 #[async_trait::async_trait(?Send)]
895 impl Perform for MarkAllAsRead {
896   type Response = GetRepliesResponse;
897
898   async fn perform(
899     &self,
900     context: &Data<LemmyContext>,
901     _websocket_id: Option<ConnectionId>,
902   ) -> Result<GetRepliesResponse, LemmyError> {
903     let data: &MarkAllAsRead = &self;
904     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
905
906     let person_id = local_user_view.person.id;
907     let replies = blocking(context.pool(), move |conn| {
908       CommentQueryBuilder::create(conn)
909         .my_person_id(person_id)
910         .recipient_id(person_id)
911         .unread_only(true)
912         .page(1)
913         .limit(999)
914         .list()
915     })
916     .await??;
917
918     // TODO: this should probably be a bulk operation
919     // Not easy to do as a bulk operation,
920     // because recipient_id isn't in the comment table
921     for comment_view in &replies {
922       let reply_id = comment_view.comment.id;
923       let mark_as_read = move |conn: &'_ _| Comment::update_read(conn, reply_id, true);
924       if blocking(context.pool(), mark_as_read).await?.is_err() {
925         return Err(ApiError::err("couldnt_update_comment").into());
926       }
927     }
928
929     // Mark all user mentions as read
930     let update_person_mentions =
931       move |conn: &'_ _| PersonMention::mark_all_as_read(conn, person_id);
932     if blocking(context.pool(), update_person_mentions)
933       .await?
934       .is_err()
935     {
936       return Err(ApiError::err("couldnt_update_comment").into());
937     }
938
939     // Mark all private_messages as read
940     let update_pm = move |conn: &'_ _| PrivateMessage::mark_all_as_read(conn, person_id);
941     if blocking(context.pool(), update_pm).await?.is_err() {
942       return Err(ApiError::err("couldnt_update_private_message").into());
943     }
944
945     Ok(GetRepliesResponse { replies: vec![] })
946   }
947 }
948
949 #[async_trait::async_trait(?Send)]
950 impl Perform for DeleteAccount {
951   type Response = LoginResponse;
952
953   async fn perform(
954     &self,
955     context: &Data<LemmyContext>,
956     _websocket_id: Option<ConnectionId>,
957   ) -> Result<LoginResponse, LemmyError> {
958     let data: &DeleteAccount = &self;
959     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
960
961     // Verify the password
962     let valid: bool = verify(
963       &data.password,
964       &local_user_view.local_user.password_encrypted,
965     )
966     .unwrap_or(false);
967     if !valid {
968       return Err(ApiError::err("password_incorrect").into());
969     }
970
971     // Comments
972     let person_id = local_user_view.person.id;
973     let permadelete = move |conn: &'_ _| Comment::permadelete_for_creator(conn, person_id);
974     if blocking(context.pool(), permadelete).await?.is_err() {
975       return Err(ApiError::err("couldnt_update_comment").into());
976     }
977
978     // Posts
979     let permadelete = move |conn: &'_ _| Post::permadelete_for_creator(conn, person_id);
980     if blocking(context.pool(), permadelete).await?.is_err() {
981       return Err(ApiError::err("couldnt_update_post").into());
982     }
983
984     blocking(context.pool(), move |conn| {
985       Person::delete_account(conn, person_id)
986     })
987     .await??;
988
989     Ok(LoginResponse {
990       jwt: data.auth.to_owned(),
991     })
992   }
993 }
994
995 #[async_trait::async_trait(?Send)]
996 impl Perform for PasswordReset {
997   type Response = PasswordResetResponse;
998
999   async fn perform(
1000     &self,
1001     context: &Data<LemmyContext>,
1002     _websocket_id: Option<ConnectionId>,
1003   ) -> Result<PasswordResetResponse, LemmyError> {
1004     let data: &PasswordReset = &self;
1005
1006     // Fetch that email
1007     let email = data.email.clone();
1008     let local_user_view = match blocking(context.pool(), move |conn| {
1009       LocalUserView::find_by_email(conn, &email)
1010     })
1011     .await?
1012     {
1013       Ok(lu) => lu,
1014       Err(_e) => return Err(ApiError::err("couldnt_find_that_username_or_email").into()),
1015     };
1016
1017     // Generate a random token
1018     let token = generate_random_string();
1019
1020     // Insert the row
1021     let token2 = token.clone();
1022     let local_user_id = local_user_view.local_user.id;
1023     blocking(context.pool(), move |conn| {
1024       PasswordResetRequest::create_token(conn, local_user_id, &token2)
1025     })
1026     .await??;
1027
1028     // Email the pure token to the user.
1029     // TODO no i18n support here.
1030     let email = &local_user_view.local_user.email.expect("email");
1031     let subject = &format!("Password reset for {}", local_user_view.person.name);
1032     let hostname = &Settings::get().get_protocol_and_hostname();
1033     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, hostname, &token);
1034     match send_email(subject, email, &local_user_view.person.name, html) {
1035       Ok(_o) => _o,
1036       Err(_e) => return Err(ApiError::err(&_e).into()),
1037     };
1038
1039     Ok(PasswordResetResponse {})
1040   }
1041 }
1042
1043 #[async_trait::async_trait(?Send)]
1044 impl Perform for PasswordChange {
1045   type Response = LoginResponse;
1046
1047   async fn perform(
1048     &self,
1049     context: &Data<LemmyContext>,
1050     _websocket_id: Option<ConnectionId>,
1051   ) -> Result<LoginResponse, LemmyError> {
1052     let data: &PasswordChange = &self;
1053
1054     // Fetch the user_id from the token
1055     let token = data.token.clone();
1056     let local_user_id = blocking(context.pool(), move |conn| {
1057       PasswordResetRequest::read_from_token(conn, &token).map(|p| p.local_user_id)
1058     })
1059     .await??;
1060
1061     password_length_check(&data.password)?;
1062
1063     // Make sure passwords match
1064     if data.password != data.password_verify {
1065       return Err(ApiError::err("passwords_dont_match").into());
1066     }
1067
1068     // Update the user with the new password
1069     let password = data.password.clone();
1070     let updated_local_user = match blocking(context.pool(), move |conn| {
1071       LocalUser::update_password(conn, local_user_id, &password)
1072     })
1073     .await?
1074     {
1075       Ok(u) => u,
1076       Err(_e) => return Err(ApiError::err("couldnt_update_user").into()),
1077     };
1078
1079     // Return the jwt
1080     Ok(LoginResponse {
1081       jwt: Claims::jwt(updated_local_user.id.0)?,
1082     })
1083   }
1084 }
1085
1086 #[async_trait::async_trait(?Send)]
1087 impl Perform for CreatePrivateMessage {
1088   type Response = PrivateMessageResponse;
1089
1090   async fn perform(
1091     &self,
1092     context: &Data<LemmyContext>,
1093     websocket_id: Option<ConnectionId>,
1094   ) -> Result<PrivateMessageResponse, LemmyError> {
1095     let data: &CreatePrivateMessage = &self;
1096     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1097
1098     let content_slurs_removed = remove_slurs(&data.content.to_owned());
1099
1100     let private_message_form = PrivateMessageForm {
1101       content: content_slurs_removed.to_owned(),
1102       creator_id: local_user_view.person.id,
1103       recipient_id: data.recipient_id,
1104       deleted: None,
1105       read: None,
1106       updated: None,
1107       ap_id: None,
1108       local: true,
1109       published: None,
1110     };
1111
1112     let inserted_private_message = match blocking(context.pool(), move |conn| {
1113       PrivateMessage::create(conn, &private_message_form)
1114     })
1115     .await?
1116     {
1117       Ok(private_message) => private_message,
1118       Err(_e) => {
1119         return Err(ApiError::err("couldnt_create_private_message").into());
1120       }
1121     };
1122
1123     let inserted_private_message_id = inserted_private_message.id;
1124     let updated_private_message = match blocking(
1125       context.pool(),
1126       move |conn| -> Result<PrivateMessage, LemmyError> {
1127         let apub_id = generate_apub_endpoint(
1128           EndpointType::PrivateMessage,
1129           &inserted_private_message_id.to_string(),
1130         )?;
1131         Ok(PrivateMessage::update_ap_id(
1132           &conn,
1133           inserted_private_message_id,
1134           apub_id,
1135         )?)
1136       },
1137     )
1138     .await?
1139     {
1140       Ok(private_message) => private_message,
1141       Err(_e) => return Err(ApiError::err("couldnt_create_private_message").into()),
1142     };
1143
1144     updated_private_message
1145       .send_create(&local_user_view.person, context)
1146       .await?;
1147
1148     let private_message_view = blocking(context.pool(), move |conn| {
1149       PrivateMessageView::read(conn, inserted_private_message.id)
1150     })
1151     .await??;
1152
1153     let res = PrivateMessageResponse {
1154       private_message_view,
1155     };
1156
1157     // Send notifications to the local recipient, if one exists
1158     let recipient_id = data.recipient_id;
1159     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1160       LocalUserView::read_person(conn, recipient_id)
1161     })
1162     .await?
1163     {
1164       send_email_to_user(
1165         &local_recipient,
1166         "Private Message from",
1167         "Private Message",
1168         &content_slurs_removed,
1169       );
1170
1171       let local_recipient_id = local_recipient.local_user.id;
1172       context.chat_server().do_send(SendUserRoomMessage {
1173         op: UserOperation::CreatePrivateMessage,
1174         response: res.clone(),
1175         local_recipient_id,
1176         websocket_id,
1177       });
1178     }
1179
1180     Ok(res)
1181   }
1182 }
1183
1184 #[async_trait::async_trait(?Send)]
1185 impl Perform for EditPrivateMessage {
1186   type Response = PrivateMessageResponse;
1187
1188   async fn perform(
1189     &self,
1190     context: &Data<LemmyContext>,
1191     websocket_id: Option<ConnectionId>,
1192   ) -> Result<PrivateMessageResponse, LemmyError> {
1193     let data: &EditPrivateMessage = &self;
1194     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1195
1196     // Checking permissions
1197     let private_message_id = data.private_message_id;
1198     let orig_private_message = blocking(context.pool(), move |conn| {
1199       PrivateMessage::read(conn, private_message_id)
1200     })
1201     .await??;
1202     if local_user_view.person.id != orig_private_message.creator_id {
1203       return Err(ApiError::err("no_private_message_edit_allowed").into());
1204     }
1205
1206     // Doing the update
1207     let content_slurs_removed = remove_slurs(&data.content);
1208     let private_message_id = data.private_message_id;
1209     let updated_private_message = match blocking(context.pool(), move |conn| {
1210       PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed)
1211     })
1212     .await?
1213     {
1214       Ok(private_message) => private_message,
1215       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1216     };
1217
1218     // Send the apub update
1219     updated_private_message
1220       .send_update(&local_user_view.person, context)
1221       .await?;
1222
1223     let private_message_id = data.private_message_id;
1224     let private_message_view = blocking(context.pool(), move |conn| {
1225       PrivateMessageView::read(conn, private_message_id)
1226     })
1227     .await??;
1228
1229     let res = PrivateMessageResponse {
1230       private_message_view,
1231     };
1232
1233     // Send notifications to the local recipient, if one exists
1234     let recipient_id = orig_private_message.recipient_id;
1235     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1236       LocalUserView::read_person(conn, recipient_id)
1237     })
1238     .await?
1239     {
1240       let local_recipient_id = local_recipient.local_user.id;
1241       context.chat_server().do_send(SendUserRoomMessage {
1242         op: UserOperation::EditPrivateMessage,
1243         response: res.clone(),
1244         local_recipient_id,
1245         websocket_id,
1246       });
1247     }
1248
1249     Ok(res)
1250   }
1251 }
1252
1253 #[async_trait::async_trait(?Send)]
1254 impl Perform for DeletePrivateMessage {
1255   type Response = PrivateMessageResponse;
1256
1257   async fn perform(
1258     &self,
1259     context: &Data<LemmyContext>,
1260     websocket_id: Option<ConnectionId>,
1261   ) -> Result<PrivateMessageResponse, LemmyError> {
1262     let data: &DeletePrivateMessage = &self;
1263     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1264
1265     // Checking permissions
1266     let private_message_id = data.private_message_id;
1267     let orig_private_message = blocking(context.pool(), move |conn| {
1268       PrivateMessage::read(conn, private_message_id)
1269     })
1270     .await??;
1271     if local_user_view.person.id != orig_private_message.creator_id {
1272       return Err(ApiError::err("no_private_message_edit_allowed").into());
1273     }
1274
1275     // Doing the update
1276     let private_message_id = data.private_message_id;
1277     let deleted = data.deleted;
1278     let updated_private_message = match blocking(context.pool(), move |conn| {
1279       PrivateMessage::update_deleted(conn, private_message_id, deleted)
1280     })
1281     .await?
1282     {
1283       Ok(private_message) => private_message,
1284       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1285     };
1286
1287     // Send the apub update
1288     if data.deleted {
1289       updated_private_message
1290         .send_delete(&local_user_view.person, context)
1291         .await?;
1292     } else {
1293       updated_private_message
1294         .send_undo_delete(&local_user_view.person, context)
1295         .await?;
1296     }
1297
1298     let private_message_id = data.private_message_id;
1299     let private_message_view = blocking(context.pool(), move |conn| {
1300       PrivateMessageView::read(conn, private_message_id)
1301     })
1302     .await??;
1303
1304     let res = PrivateMessageResponse {
1305       private_message_view,
1306     };
1307
1308     // Send notifications to the local recipient, if one exists
1309     let recipient_id = orig_private_message.recipient_id;
1310     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1311       LocalUserView::read_person(conn, recipient_id)
1312     })
1313     .await?
1314     {
1315       let local_recipient_id = local_recipient.local_user.id;
1316       context.chat_server().do_send(SendUserRoomMessage {
1317         op: UserOperation::DeletePrivateMessage,
1318         response: res.clone(),
1319         local_recipient_id,
1320         websocket_id,
1321       });
1322     }
1323
1324     Ok(res)
1325   }
1326 }
1327
1328 #[async_trait::async_trait(?Send)]
1329 impl Perform for MarkPrivateMessageAsRead {
1330   type Response = PrivateMessageResponse;
1331
1332   async fn perform(
1333     &self,
1334     context: &Data<LemmyContext>,
1335     websocket_id: Option<ConnectionId>,
1336   ) -> Result<PrivateMessageResponse, LemmyError> {
1337     let data: &MarkPrivateMessageAsRead = &self;
1338     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1339
1340     // Checking permissions
1341     let private_message_id = data.private_message_id;
1342     let orig_private_message = blocking(context.pool(), move |conn| {
1343       PrivateMessage::read(conn, private_message_id)
1344     })
1345     .await??;
1346     if local_user_view.person.id != orig_private_message.recipient_id {
1347       return Err(ApiError::err("couldnt_update_private_message").into());
1348     }
1349
1350     // Doing the update
1351     let private_message_id = data.private_message_id;
1352     let read = data.read;
1353     match blocking(context.pool(), move |conn| {
1354       PrivateMessage::update_read(conn, private_message_id, read)
1355     })
1356     .await?
1357     {
1358       Ok(private_message) => private_message,
1359       Err(_e) => return Err(ApiError::err("couldnt_update_private_message").into()),
1360     };
1361
1362     // No need to send an apub update
1363     let private_message_id = data.private_message_id;
1364     let private_message_view = blocking(context.pool(), move |conn| {
1365       PrivateMessageView::read(conn, private_message_id)
1366     })
1367     .await??;
1368
1369     let res = PrivateMessageResponse {
1370       private_message_view,
1371     };
1372
1373     // Send notifications to the local recipient, if one exists
1374     let recipient_id = orig_private_message.recipient_id;
1375     if let Ok(local_recipient) = blocking(context.pool(), move |conn| {
1376       LocalUserView::read_person(conn, recipient_id)
1377     })
1378     .await?
1379     {
1380       let local_recipient_id = local_recipient.local_user.id;
1381       context.chat_server().do_send(SendUserRoomMessage {
1382         op: UserOperation::MarkPrivateMessageAsRead,
1383         response: res.clone(),
1384         local_recipient_id,
1385         websocket_id,
1386       });
1387     }
1388
1389     Ok(res)
1390   }
1391 }
1392
1393 #[async_trait::async_trait(?Send)]
1394 impl Perform for GetPrivateMessages {
1395   type Response = PrivateMessagesResponse;
1396
1397   async fn perform(
1398     &self,
1399     context: &Data<LemmyContext>,
1400     _websocket_id: Option<ConnectionId>,
1401   ) -> Result<PrivateMessagesResponse, LemmyError> {
1402     let data: &GetPrivateMessages = &self;
1403     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1404     let person_id = local_user_view.person.id;
1405
1406     let page = data.page;
1407     let limit = data.limit;
1408     let unread_only = data.unread_only;
1409     let messages = blocking(context.pool(), move |conn| {
1410       PrivateMessageQueryBuilder::create(&conn, person_id)
1411         .page(page)
1412         .limit(limit)
1413         .unread_only(unread_only)
1414         .list()
1415     })
1416     .await??;
1417
1418     Ok(PrivateMessagesResponse {
1419       private_messages: messages,
1420     })
1421   }
1422 }
1423
1424 #[async_trait::async_trait(?Send)]
1425 impl Perform for GetReportCount {
1426   type Response = GetReportCountResponse;
1427
1428   async fn perform(
1429     &self,
1430     context: &Data<LemmyContext>,
1431     websocket_id: Option<ConnectionId>,
1432   ) -> Result<GetReportCountResponse, LemmyError> {
1433     let data: &GetReportCount = &self;
1434     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
1435
1436     let person_id = local_user_view.person.id;
1437     let community_id = data.community;
1438     let community_ids =
1439       collect_moderated_communities(person_id, community_id, context.pool()).await?;
1440
1441     let res = {
1442       if community_ids.is_empty() {
1443         GetReportCountResponse {
1444           community: None,
1445           comment_reports: 0,
1446           post_reports: 0,
1447         }
1448       } else {
1449         let ids = community_ids.clone();
1450         let comment_reports = blocking(context.pool(), move |conn| {
1451           CommentReportView::get_report_count(conn, &ids)
1452         })
1453         .await??;
1454
1455         let ids = community_ids.clone();
1456         let post_reports = blocking(context.pool(), move |conn| {
1457           PostReportView::get_report_count(conn, &ids)
1458         })
1459         .await??;
1460
1461         GetReportCountResponse {
1462           community: data.community,
1463           comment_reports,
1464           post_reports,
1465         }
1466       }
1467     };
1468
1469     context.chat_server().do_send(SendUserRoomMessage {
1470       op: UserOperation::GetReportCount,
1471       response: res.clone(),
1472       local_recipient_id: local_user_view.local_user.id,
1473       websocket_id,
1474     });
1475
1476     Ok(res)
1477   }
1478 }