1 use actix_web::{web, web::Data};
2 use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
3 use lemmy_utils::{ConnectionId, LemmyError};
4 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
5 use serde::Deserialize;
6 use std::{env, process::Command};
18 #[async_trait::async_trait(?Send)]
20 type Response: serde::ser::Serialize + Send;
24 context: &Data<LemmyContext>,
25 websocket_id: Option<ConnectionId>,
26 ) -> Result<Self::Response, LemmyError>;
29 pub async fn match_websocket_operation(
30 context: LemmyContext,
34 ) -> Result<String, LemmyError> {
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::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
42 UserOperation::GetPersonMentions => {
43 do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
45 UserOperation::MarkPersonMentionAsRead => {
46 do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
48 UserOperation::MarkAllAsRead => {
49 do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
51 UserOperation::PasswordReset => {
52 do_websocket_operation::<PasswordReset>(context, id, op, data).await
54 UserOperation::PasswordChange => {
55 do_websocket_operation::<PasswordChange>(context, id, op, data).await
57 UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
58 UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
59 UserOperation::CommunityJoin => {
60 do_websocket_operation::<CommunityJoin>(context, id, op, data).await
62 UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
63 UserOperation::SaveUserSettings => {
64 do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
66 UserOperation::GetReportCount => {
67 do_websocket_operation::<GetReportCount>(context, id, op, data).await
70 // Private Message ops
71 UserOperation::MarkPrivateMessageAsRead => {
72 do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
76 UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
77 UserOperation::GetSiteConfig => {
78 do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
80 UserOperation::SaveSiteConfig => {
81 do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
83 UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
84 UserOperation::TransferCommunity => {
85 do_websocket_operation::<TransferCommunity>(context, id, op, data).await
87 UserOperation::TransferSite => {
88 do_websocket_operation::<TransferSite>(context, id, op, data).await
92 UserOperation::FollowCommunity => {
93 do_websocket_operation::<FollowCommunity>(context, id, op, data).await
95 UserOperation::GetFollowedCommunities => {
96 do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
98 UserOperation::BanFromCommunity => {
99 do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
101 UserOperation::AddModToCommunity => {
102 do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
106 UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
107 UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
108 UserOperation::CreatePostLike => {
109 do_websocket_operation::<CreatePostLike>(context, id, op, data).await
111 UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
112 UserOperation::CreatePostReport => {
113 do_websocket_operation::<CreatePostReport>(context, id, op, data).await
115 UserOperation::ListPostReports => {
116 do_websocket_operation::<ListPostReports>(context, id, op, data).await
118 UserOperation::ResolvePostReport => {
119 do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
123 UserOperation::MarkCommentAsRead => {
124 do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
126 UserOperation::SaveComment => {
127 do_websocket_operation::<SaveComment>(context, id, op, data).await
129 UserOperation::CreateCommentLike => {
130 do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
132 UserOperation::CreateCommentReport => {
133 do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
135 UserOperation::ListCommentReports => {
136 do_websocket_operation::<ListCommentReports>(context, id, op, data).await
138 UserOperation::ResolveCommentReport => {
139 do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
144 async fn do_websocket_operation<'a, 'b, Data>(
145 context: LemmyContext,
149 ) -> Result<String, LemmyError>
151 for<'de> Data: Deserialize<'de> + 'a,
154 let parsed_data: Data = serde_json::from_str(&data)?;
155 let res = parsed_data
156 .perform(&web::Data::new(context), Some(id))
158 serialize_websocket_message(&op, &res)
161 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
162 let mut built_text = String::new();
164 // Building proper speech text for espeak
165 for mut c in captcha.chars() {
166 let new_str = if c.is_alphabetic() {
167 if c.is_lowercase() {
168 c.make_ascii_uppercase();
169 format!("lower case {} ... ", c)
171 c.make_ascii_uppercase();
172 format!("capital {} ... ", c)
178 built_text.push_str(&new_str);
181 espeak_wav_base64(&built_text)
184 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
185 // Make a temp file path
186 let uuid = uuid::Uuid::new_v4().to_string();
187 let file_path = format!(
188 "{}/lemmy_espeak_{}.wav",
189 env::temp_dir().to_string_lossy(),
193 // Write the wav file
194 Command::new("espeak")
200 // Read the wav file bytes
201 let bytes = std::fs::read(&file_path)?;
204 std::fs::remove_file(file_path)?;
207 let base64 = base64::encode(bytes);
214 use crate::captcha_espeak_wav_base64;
215 use lemmy_api_common::check_validator_time;
216 use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
217 use lemmy_db_schema::source::{
218 local_user::{LocalUser, LocalUserForm},
219 person::{Person, PersonForm},
221 use lemmy_utils::claims::Claims;
224 fn test_should_not_validate_user_token_after_password_change() {
225 let conn = establish_unpooled_connection();
227 let new_person = PersonForm {
228 name: "Gerry9812".into(),
229 preferred_username: None,
241 last_refreshed_at: None,
243 shared_inbox_url: None,
246 let inserted_person = Person::create(&conn, &new_person).unwrap();
248 let local_user_form = LocalUserForm {
249 person_id: inserted_person.id,
251 matrix_user_id: None,
252 password_encrypted: "123456".to_string(),
256 default_sort_type: None,
257 default_listing_type: None,
260 send_notifications_to_email: None,
263 let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
265 let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
266 let claims = Claims::decode(&jwt).unwrap().claims;
267 let check = check_validator_time(&inserted_local_user.validator_time, &claims);
268 assert!(check.is_ok());
270 // The check should fail, since the validator time is now newer than the jwt issue time
271 let updated_local_user =
272 LocalUser::update_password(&conn, inserted_local_user.id, &"password111").unwrap();
273 let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
274 assert!(check_after.is_err());
276 let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
277 assert_eq!(1, num_deleted);
282 assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())