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