use actix_web::{web, web::Data}; use captcha::Captcha; use lemmy_api_common::{ comment::*, community::*, person::*, post::*, private_message::*, site::*, utils::local_site_to_slur_regex, websocket::*, }; use lemmy_db_schema::source::local_site::LocalSite; use lemmy_utils::{error::LemmyError, utils::check_slurs, ConnectionId}; use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation}; use serde::Deserialize; mod comment; mod comment_report; mod community; mod local_user; mod post; mod post_report; mod private_message; mod private_message_report; mod site; mod websocket; #[async_trait::async_trait(?Send)] pub trait Perform { type Response: serde::ser::Serialize + Send; async fn perform( &self, context: &Data, websocket_id: Option, ) -> Result; } pub async fn match_websocket_operation( context: LemmyContext, id: ConnectionId, op: UserOperation, data: &str, ) -> Result { match op { // User ops UserOperation::Login => do_websocket_operation::(context, id, op, data).await, UserOperation::GetCaptcha => do_websocket_operation::(context, id, op, data).await, UserOperation::GetReplies => do_websocket_operation::(context, id, op, data).await, UserOperation::AddAdmin => do_websocket_operation::(context, id, op, data).await, UserOperation::GetUnreadRegistrationApplicationCount => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListRegistrationApplications => { do_websocket_operation::(context, id, op, data).await } UserOperation::ApproveRegistrationApplication => { do_websocket_operation::(context, id, op, data).await } UserOperation::BanPerson => do_websocket_operation::(context, id, op, data).await, UserOperation::GetBannedPersons => { do_websocket_operation::(context, id, op, data).await } UserOperation::BlockPerson => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetPersonMentions => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkPersonMentionAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkCommentReplyAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkAllAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::PasswordReset => { do_websocket_operation::(context, id, op, data).await } UserOperation::PasswordChange => { do_websocket_operation::(context, id, op, data).await } UserOperation::UserJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::PostJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::CommunityJoin => { do_websocket_operation::(context, id, op, data).await } UserOperation::ModJoin => do_websocket_operation::(context, id, op, data).await, UserOperation::SaveUserSettings => { do_websocket_operation::(context, id, op, data).await } UserOperation::ChangePassword => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetReportCount => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetUnreadCount => { do_websocket_operation::(context, id, op, data).await } UserOperation::VerifyEmail => { do_websocket_operation::(context, id, op, data).await } // Private Message ops UserOperation::MarkPrivateMessageAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::CreatePrivateMessageReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ResolvePrivateMessageReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListPrivateMessageReports => { do_websocket_operation::(context, id, op, data).await } // Site ops UserOperation::GetModlog => do_websocket_operation::(context, id, op, data).await, UserOperation::PurgePerson => { do_websocket_operation::(context, id, op, data).await } UserOperation::PurgeCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::PurgePost => do_websocket_operation::(context, id, op, data).await, UserOperation::PurgeComment => { do_websocket_operation::(context, id, op, data).await } UserOperation::Search => do_websocket_operation::(context, id, op, data).await, UserOperation::ResolveObject => { do_websocket_operation::(context, id, op, data).await } UserOperation::TransferCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::LeaveAdmin => do_websocket_operation::(context, id, op, data).await, // Community ops UserOperation::FollowCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::BlockCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::BanFromCommunity => { do_websocket_operation::(context, id, op, data).await } UserOperation::AddModToCommunity => { do_websocket_operation::(context, id, op, data).await } // Post ops UserOperation::LockPost => do_websocket_operation::(context, id, op, data).await, UserOperation::StickyPost => do_websocket_operation::(context, id, op, data).await, UserOperation::CreatePostLike => { do_websocket_operation::(context, id, op, data).await } UserOperation::MarkPostAsRead => { do_websocket_operation::(context, id, op, data).await } UserOperation::SavePost => do_websocket_operation::(context, id, op, data).await, UserOperation::CreatePostReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListPostReports => { do_websocket_operation::(context, id, op, data).await } UserOperation::ResolvePostReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::GetSiteMetadata => { do_websocket_operation::(context, id, op, data).await } // Comment ops UserOperation::SaveComment => { do_websocket_operation::(context, id, op, data).await } UserOperation::CreateCommentLike => { do_websocket_operation::(context, id, op, data).await } UserOperation::CreateCommentReport => { do_websocket_operation::(context, id, op, data).await } UserOperation::ListCommentReports => { do_websocket_operation::(context, id, op, data).await } UserOperation::ResolveCommentReport => { do_websocket_operation::(context, id, op, data).await } } } async fn do_websocket_operation<'a, 'b, Data>( context: LemmyContext, id: ConnectionId, op: UserOperation, data: &str, ) -> Result where for<'de> Data: Deserialize<'de> + 'a, Data: Perform, { let parsed_data: Data = serde_json::from_str(data)?; let res = parsed_data .perform(&web::Data::new(context), Some(id)) .await?; serialize_websocket_message(&op, &res) } /// Converts the captcha to a base64 encoded wav audio file pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> String { let letters = captcha.as_wav(); let mut concat_letters: Vec = Vec::new(); for letter in letters { let bytes = letter.unwrap_or_default(); concat_letters.extend(bytes); } // Convert to base64 base64::encode(concat_letters) } /// Check size of report and remove whitespace pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> { let slur_regex = &local_site_to_slur_regex(local_site); check_slurs(reason, slur_regex)?; if reason.is_empty() { return Err(LemmyError::from_message("report_reason_required")); } if reason.chars().count() > 1000 { return Err(LemmyError::from_message("report_too_long")); } Ok(()) } #[cfg(test)] mod tests { use lemmy_api_common::utils::check_validator_time; use lemmy_db_schema::{ source::{ instance::Instance, local_user::{LocalUser, LocalUserInsertForm}, person::{Person, PersonInsertForm}, secret::Secret, }, traits::Crud, utils::build_db_pool_for_tests, }; use lemmy_utils::{claims::Claims, settings::SETTINGS}; use serial_test::serial; #[tokio::test] #[serial] async fn test_should_not_validate_user_token_after_password_change() { let pool = &build_db_pool_for_tests().await; let secret = Secret::init(pool).await.unwrap(); let settings = &SETTINGS.to_owned(); let inserted_instance = Instance::create(pool, "my_domain.tld").await.unwrap(); let new_person = PersonInsertForm::builder() .name("Gerry9812".into()) .public_key("pubkey".to_string()) .instance_id(inserted_instance.id) .build(); let inserted_person = Person::create(pool, &new_person).await.unwrap(); let local_user_form = LocalUserInsertForm::builder() .person_id(inserted_person.id) .password_encrypted("123456".to_string()) .build(); let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap(); let jwt = Claims::jwt( inserted_local_user.id.0, &secret.jwt_secret, &settings.hostname, ) .unwrap(); let claims = Claims::decode(&jwt, &secret.jwt_secret).unwrap().claims; let check = check_validator_time(&inserted_local_user.validator_time, &claims); assert!(check.is_ok()); // The check should fail, since the validator time is now newer than the jwt issue time let updated_local_user = LocalUser::update_password(pool, inserted_local_user.id, "password111") .await .unwrap(); let check_after = check_validator_time(&updated_local_user.validator_time, &claims); assert!(check_after.is_err()); let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap(); assert_eq!(1, num_deleted); } }