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