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