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