]> Untitled Git - lemmy.git/blob - crates/api/src/lib.rs
Various pedantic clippy fixes (#2568)
[lemmy.git] / crates / api / src / lib.rs
1 use actix_web::{web, web::Data};
2 use captcha::Captcha;
3 use lemmy_api_common::{
4   comment::{
5     CreateCommentLike,
6     CreateCommentReport,
7     ListCommentReports,
8     ResolveCommentReport,
9     SaveComment,
10   },
11   community::{
12     AddModToCommunity,
13     BanFromCommunity,
14     BlockCommunity,
15     FollowCommunity,
16     TransferCommunity,
17   },
18   person::{
19     AddAdmin,
20     BanPerson,
21     BlockPerson,
22     ChangePassword,
23     GetBannedPersons,
24     GetCaptcha,
25     GetPersonMentions,
26     GetReplies,
27     GetReportCount,
28     GetUnreadCount,
29     Login,
30     MarkAllAsRead,
31     MarkCommentReplyAsRead,
32     MarkPersonMentionAsRead,
33     PasswordChangeAfterReset,
34     PasswordReset,
35     SaveUserSettings,
36     VerifyEmail,
37   },
38   post::{
39     CreatePostLike,
40     CreatePostReport,
41     GetSiteMetadata,
42     ListPostReports,
43     LockPost,
44     MarkPostAsRead,
45     ResolvePostReport,
46     SavePost,
47     StickyPost,
48   },
49   private_message::{
50     CreatePrivateMessageReport,
51     ListPrivateMessageReports,
52     MarkPrivateMessageAsRead,
53     ResolvePrivateMessageReport,
54   },
55   site::{
56     ApproveRegistrationApplication,
57     GetModlog,
58     GetUnreadRegistrationApplicationCount,
59     LeaveAdmin,
60     ListRegistrationApplications,
61     PurgeComment,
62     PurgeCommunity,
63     PurgePerson,
64     PurgePost,
65     ResolveObject,
66     Search,
67   },
68   utils::local_site_to_slur_regex,
69   websocket::{CommunityJoin, ModJoin, PostJoin, UserJoin},
70 };
71 use lemmy_db_schema::source::local_site::LocalSite;
72 use lemmy_utils::{error::LemmyError, utils::check_slurs, ConnectionId};
73 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
74 use serde::Deserialize;
75
76 mod comment;
77 mod comment_report;
78 mod community;
79 mod local_user;
80 mod post;
81 mod post_report;
82 mod private_message;
83 mod private_message_report;
84 mod site;
85 mod websocket;
86
87 #[async_trait::async_trait(?Send)]
88 pub trait Perform {
89   type Response: serde::ser::Serialize + Send;
90
91   async fn perform(
92     &self,
93     context: &Data<LemmyContext>,
94     websocket_id: Option<ConnectionId>,
95   ) -> Result<Self::Response, LemmyError>;
96 }
97
98 pub async fn match_websocket_operation(
99   context: LemmyContext,
100   id: ConnectionId,
101   op: UserOperation,
102   data: &str,
103 ) -> Result<String, LemmyError> {
104   match op {
105     // User ops
106     UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
107     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
108     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
109     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
110     UserOperation::GetUnreadRegistrationApplicationCount => {
111       do_websocket_operation::<GetUnreadRegistrationApplicationCount>(context, id, op, data).await
112     }
113     UserOperation::ListRegistrationApplications => {
114       do_websocket_operation::<ListRegistrationApplications>(context, id, op, data).await
115     }
116     UserOperation::ApproveRegistrationApplication => {
117       do_websocket_operation::<ApproveRegistrationApplication>(context, id, op, data).await
118     }
119     UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
120     UserOperation::GetBannedPersons => {
121       do_websocket_operation::<GetBannedPersons>(context, id, op, data).await
122     }
123     UserOperation::BlockPerson => {
124       do_websocket_operation::<BlockPerson>(context, id, op, data).await
125     }
126     UserOperation::GetPersonMentions => {
127       do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
128     }
129     UserOperation::MarkPersonMentionAsRead => {
130       do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
131     }
132     UserOperation::MarkCommentReplyAsRead => {
133       do_websocket_operation::<MarkCommentReplyAsRead>(context, id, op, data).await
134     }
135     UserOperation::MarkAllAsRead => {
136       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
137     }
138     UserOperation::PasswordReset => {
139       do_websocket_operation::<PasswordReset>(context, id, op, data).await
140     }
141     UserOperation::PasswordChange => {
142       do_websocket_operation::<PasswordChangeAfterReset>(context, id, op, data).await
143     }
144     UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
145     UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
146     UserOperation::CommunityJoin => {
147       do_websocket_operation::<CommunityJoin>(context, id, op, data).await
148     }
149     UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
150     UserOperation::SaveUserSettings => {
151       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
152     }
153     UserOperation::ChangePassword => {
154       do_websocket_operation::<ChangePassword>(context, id, op, data).await
155     }
156     UserOperation::GetReportCount => {
157       do_websocket_operation::<GetReportCount>(context, id, op, data).await
158     }
159     UserOperation::GetUnreadCount => {
160       do_websocket_operation::<GetUnreadCount>(context, id, op, data).await
161     }
162     UserOperation::VerifyEmail => {
163       do_websocket_operation::<VerifyEmail>(context, id, op, data).await
164     }
165
166     // Private Message ops
167     UserOperation::MarkPrivateMessageAsRead => {
168       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
169     }
170     UserOperation::CreatePrivateMessageReport => {
171       do_websocket_operation::<CreatePrivateMessageReport>(context, id, op, data).await
172     }
173     UserOperation::ResolvePrivateMessageReport => {
174       do_websocket_operation::<ResolvePrivateMessageReport>(context, id, op, data).await
175     }
176     UserOperation::ListPrivateMessageReports => {
177       do_websocket_operation::<ListPrivateMessageReports>(context, id, op, data).await
178     }
179
180     // Site ops
181     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
182     UserOperation::PurgePerson => {
183       do_websocket_operation::<PurgePerson>(context, id, op, data).await
184     }
185     UserOperation::PurgeCommunity => {
186       do_websocket_operation::<PurgeCommunity>(context, id, op, data).await
187     }
188     UserOperation::PurgePost => do_websocket_operation::<PurgePost>(context, id, op, data).await,
189     UserOperation::PurgeComment => {
190       do_websocket_operation::<PurgeComment>(context, id, op, data).await
191     }
192     UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
193     UserOperation::ResolveObject => {
194       do_websocket_operation::<ResolveObject>(context, id, op, data).await
195     }
196     UserOperation::TransferCommunity => {
197       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
198     }
199     UserOperation::LeaveAdmin => do_websocket_operation::<LeaveAdmin>(context, id, op, data).await,
200
201     // Community ops
202     UserOperation::FollowCommunity => {
203       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
204     }
205     UserOperation::BlockCommunity => {
206       do_websocket_operation::<BlockCommunity>(context, id, op, data).await
207     }
208     UserOperation::BanFromCommunity => {
209       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
210     }
211     UserOperation::AddModToCommunity => {
212       do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
213     }
214
215     // Post ops
216     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
217     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
218     UserOperation::CreatePostLike => {
219       do_websocket_operation::<CreatePostLike>(context, id, op, data).await
220     }
221     UserOperation::MarkPostAsRead => {
222       do_websocket_operation::<MarkPostAsRead>(context, id, op, data).await
223     }
224     UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
225     UserOperation::CreatePostReport => {
226       do_websocket_operation::<CreatePostReport>(context, id, op, data).await
227     }
228     UserOperation::ListPostReports => {
229       do_websocket_operation::<ListPostReports>(context, id, op, data).await
230     }
231     UserOperation::ResolvePostReport => {
232       do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
233     }
234     UserOperation::GetSiteMetadata => {
235       do_websocket_operation::<GetSiteMetadata>(context, id, op, data).await
236     }
237
238     // Comment ops
239     UserOperation::SaveComment => {
240       do_websocket_operation::<SaveComment>(context, id, op, data).await
241     }
242     UserOperation::CreateCommentLike => {
243       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
244     }
245     UserOperation::CreateCommentReport => {
246       do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
247     }
248     UserOperation::ListCommentReports => {
249       do_websocket_operation::<ListCommentReports>(context, id, op, data).await
250     }
251     UserOperation::ResolveCommentReport => {
252       do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
253     }
254   }
255 }
256
257 async fn do_websocket_operation<'a, 'b, Data>(
258   context: LemmyContext,
259   id: ConnectionId,
260   op: UserOperation,
261   data: &str,
262 ) -> Result<String, LemmyError>
263 where
264   for<'de> Data: Deserialize<'de> + 'a,
265   Data: Perform,
266 {
267   let parsed_data: Data = serde_json::from_str(data)?;
268   let res = parsed_data
269     .perform(&web::Data::new(context), Some(id))
270     .await?;
271   serialize_websocket_message(&op, &res)
272 }
273
274 /// Converts the captcha to a base64 encoded wav audio file
275 pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String {
276   let letters = captcha.as_wav();
277
278   let mut concat_letters: Vec<u8> = Vec::new();
279
280   for letter in letters {
281     let bytes = letter.unwrap_or_default();
282     concat_letters.extend(bytes);
283   }
284
285   // Convert to base64
286   base64::encode(concat_letters)
287 }
288
289 /// Check size of report and remove whitespace
290 pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
291   let slur_regex = &local_site_to_slur_regex(local_site);
292
293   check_slurs(reason, slur_regex)?;
294   if reason.is_empty() {
295     return Err(LemmyError::from_message("report_reason_required"));
296   }
297   if reason.chars().count() > 1000 {
298     return Err(LemmyError::from_message("report_too_long"));
299   }
300   Ok(())
301 }
302
303 #[cfg(test)]
304 mod tests {
305   use lemmy_api_common::utils::check_validator_time;
306   use lemmy_db_schema::{
307     source::{
308       instance::Instance,
309       local_user::{LocalUser, LocalUserInsertForm},
310       person::{Person, PersonInsertForm},
311       secret::Secret,
312     },
313     traits::Crud,
314     utils::build_db_pool_for_tests,
315   };
316   use lemmy_utils::{claims::Claims, settings::SETTINGS};
317   use serial_test::serial;
318
319   #[tokio::test]
320   #[serial]
321   async fn test_should_not_validate_user_token_after_password_change() {
322     let pool = &build_db_pool_for_tests().await;
323     let secret = Secret::init(pool).await.unwrap();
324     let settings = &SETTINGS.to_owned();
325
326     let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap();
327
328     let new_person = PersonInsertForm::builder()
329       .name("Gerry9812".into())
330       .public_key("pubkey".to_string())
331       .instance_id(inserted_instance.id)
332       .build();
333
334     let inserted_person = Person::create(pool, &new_person).await.unwrap();
335
336     let local_user_form = LocalUserInsertForm::builder()
337       .person_id(inserted_person.id)
338       .password_encrypted("123456".to_string())
339       .build();
340
341     let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
342
343     let jwt = Claims::jwt(
344       inserted_local_user.id.0,
345       &secret.jwt_secret,
346       &settings.hostname,
347     )
348     .unwrap();
349     let claims = Claims::decode(&jwt, &secret.jwt_secret).unwrap().claims;
350     let check = check_validator_time(&inserted_local_user.validator_time, &claims);
351     assert!(check.is_ok());
352
353     // The check should fail, since the validator time is now newer than the jwt issue time
354     let updated_local_user =
355       LocalUser::update_password(pool, inserted_local_user.id, "password111")
356         .await
357         .unwrap();
358     let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
359     assert!(check_after.is_err());
360
361     let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
362     assert_eq!(1, num_deleted);
363   }
364 }