]> Untitled Git - lemmy.git/blobdiff - crates/api/src/lib.rs
Fixing broken SQL migration formatting. (#3800)
[lemmy.git] / crates / api / src / lib.rs
index 5dc678be66d7ff0ce3aa6f9236fefba23050843a..1b7b3215454ec197aca3ae37be6f8cac6f8a8c76 100644 (file)
-use actix_web::{web, web::Data};
-use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
-use lemmy_utils::{ConnectionId, LemmyError};
-use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
-use serde::Deserialize;
-use std::{env, process::Command};
-
-mod comment;
-mod comment_report;
-mod community;
-mod local_user;
-mod post;
-mod post_report;
-mod private_message;
-mod site;
-mod websocket;
+use actix_web::web::Data;
+use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
+use captcha::Captcha;
+use lemmy_api_common::{context::LemmyContext, utils::local_site_to_slur_regex};
+use lemmy_db_schema::source::local_site::LocalSite;
+use lemmy_utils::{
+  error::{LemmyError, LemmyErrorExt, LemmyErrorType},
+  utils::slurs::check_slurs,
+};
+use std::io::Cursor;
+
+pub mod comment;
+pub mod comment_report;
+pub mod community;
+pub mod local_user;
+pub mod post;
+pub mod post_report;
+pub mod private_message;
+pub mod private_message_report;
+pub mod site;
 
 #[async_trait::async_trait(?Send)]
 pub trait Perform {
-  type Response: serde::ser::Serialize + Send;
+  type Response: serde::ser::Serialize + Send + Clone + Sync;
 
-  async fn perform(
-    &self,
-    context: &Data<LemmyContext>,
-    websocket_id: Option<ConnectionId>,
-  ) -> Result<Self::Response, LemmyError>;
+  async fn perform(&self, context: &Data<LemmyContext>) -> Result<Self::Response, LemmyError>;
 }
 
-pub async fn match_websocket_operation(
-  context: LemmyContext,
-  id: ConnectionId,
-  op: UserOperation,
-  data: &str,
-) -> Result<String, LemmyError> {
-  match op {
-    // User ops
-    UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
-    UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
-    UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
-    UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
-    UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
-    UserOperation::GetPersonMentions => {
-      do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
-    }
-    UserOperation::MarkPersonMentionAsRead => {
-      do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
-    }
-    UserOperation::MarkAllAsRead => {
-      do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
-    }
-    UserOperation::PasswordReset => {
-      do_websocket_operation::<PasswordReset>(context, id, op, data).await
-    }
-    UserOperation::PasswordChange => {
-      do_websocket_operation::<PasswordChange>(context, id, op, data).await
-    }
-    UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
-    UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
-    UserOperation::CommunityJoin => {
-      do_websocket_operation::<CommunityJoin>(context, id, op, data).await
-    }
-    UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
-    UserOperation::SaveUserSettings => {
-      do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
-    }
-    UserOperation::GetReportCount => {
-      do_websocket_operation::<GetReportCount>(context, id, op, data).await
-    }
-
-    // Private Message ops
-    UserOperation::MarkPrivateMessageAsRead => {
-      do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
-    }
-
-    // Site ops
-    UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
-    UserOperation::GetSiteConfig => {
-      do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
-    }
-    UserOperation::SaveSiteConfig => {
-      do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
-    }
-    UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
-    UserOperation::TransferCommunity => {
-      do_websocket_operation::<TransferCommunity>(context, id, op, data).await
-    }
-    UserOperation::TransferSite => {
-      do_websocket_operation::<TransferSite>(context, id, op, data).await
-    }
-
-    // Community ops
-    UserOperation::FollowCommunity => {
-      do_websocket_operation::<FollowCommunity>(context, id, op, data).await
-    }
-    UserOperation::GetFollowedCommunities => {
-      do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
-    }
-    UserOperation::BanFromCommunity => {
-      do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
-    }
-    UserOperation::AddModToCommunity => {
-      do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
-    }
-
-    // Post ops
-    UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
-    UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
-    UserOperation::CreatePostLike => {
-      do_websocket_operation::<CreatePostLike>(context, id, op, data).await
-    }
-    UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
-    UserOperation::CreatePostReport => {
-      do_websocket_operation::<CreatePostReport>(context, id, op, data).await
-    }
-    UserOperation::ListPostReports => {
-      do_websocket_operation::<ListPostReports>(context, id, op, data).await
-    }
-    UserOperation::ResolvePostReport => {
-      do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
-    }
-
-    // Comment ops
-    UserOperation::MarkCommentAsRead => {
-      do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
-    }
-    UserOperation::SaveComment => {
-      do_websocket_operation::<SaveComment>(context, id, op, data).await
-    }
-    UserOperation::CreateCommentLike => {
-      do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
-    }
-    UserOperation::CreateCommentReport => {
-      do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
-    }
-    UserOperation::ListCommentReports => {
-      do_websocket_operation::<ListCommentReports>(context, id, op, data).await
-    }
-    UserOperation::ResolveCommentReport => {
-      do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
+/// Converts the captcha to a base64 encoded wav audio file
+pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyError> {
+  let letters = captcha.as_wav();
+
+  // Decode each wav file, concatenate the samples
+  let mut concat_samples: Vec<i16> = Vec::new();
+  let mut any_header: Option<wav::Header> = None;
+  for letter in letters {
+    let mut cursor = Cursor::new(letter.unwrap_or_default());
+    let (header, samples) = wav::read(&mut cursor)?;
+    any_header = Some(header);
+    if let Some(samples16) = samples.as_sixteen() {
+      concat_samples.extend(samples16);
+    } else {
+      Err(LemmyErrorType::CouldntCreateAudioCaptcha)?;
     }
   }
-}
 
-async fn do_websocket_operation<'a, 'b, Data>(
-  context: LemmyContext,
-  id: ConnectionId,
-  op: UserOperation,
-  data: &str,
-) -> Result<String, LemmyError>
-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)
+  // Encode the concatenated result as a wav file
+  let mut output_buffer = Cursor::new(vec![]);
+  let header = match any_header {
+    Some(header) => header,
+    None => return Err(LemmyErrorType::CouldntCreateAudioCaptcha)?,
+  };
+  wav::write(
+    header,
+    &wav::BitDepth::Sixteen(concat_samples),
+    &mut output_buffer,
+  )
+  .with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
+
+  Ok(base64.encode(output_buffer.into_inner()))
 }
 
-pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
-  let mut built_text = String::new();
-
-  // Building proper speech text for espeak
-  for mut c in captcha.chars() {
-    let new_str = if c.is_alphabetic() {
-      if c.is_lowercase() {
-        c.make_ascii_uppercase();
-        format!("lower case {} ... ", c)
-      } else {
-        c.make_ascii_uppercase();
-        format!("capital {} ... ", c)
-      }
-    } else {
-      format!("{} ...", c)
-    };
+/// Check size of report
+pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
+  let slur_regex = &local_site_to_slur_regex(local_site);
 
-    built_text.push_str(&new_str);
+  check_slurs(reason, slur_regex)?;
+  if reason.is_empty() {
+    Err(LemmyErrorType::ReportReasonRequired)?;
   }
-
-  espeak_wav_base64(&built_text)
-}
-
-pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
-  // Make a temp file path
-  let uuid = uuid::Uuid::new_v4().to_string();
-  let file_path = format!(
-    "{}/lemmy_espeak_{}.wav",
-    env::temp_dir().to_string_lossy(),
-    &uuid
-  );
-
-  // Write the wav file
-  Command::new("espeak")
-    .arg("-w")
-    .arg(&file_path)
-    .arg(text)
-    .status()?;
-
-  // Read the wav file bytes
-  let bytes = std::fs::read(&file_path)?;
-
-  // Delete the file
-  std::fs::remove_file(file_path)?;
-
-  // Convert to base64
-  let base64 = base64::encode(bytes);
-
-  Ok(base64)
+  if reason.chars().count() > 1000 {
+    Err(LemmyErrorType::ReportTooLong)?;
+  }
+  Ok(())
 }
 
 #[cfg(test)]
 mod tests {
-  use crate::captcha_espeak_wav_base64;
-  use lemmy_api_common::check_validator_time;
-  use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
-  use lemmy_db_schema::source::{
-    local_user::{LocalUser, LocalUserForm},
-    person::{Person, PersonForm},
+  #![allow(clippy::unwrap_used)]
+  #![allow(clippy::indexing_slicing)]
+
+  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;
-
-  #[test]
-  fn test_should_not_validate_user_token_after_password_change() {
-    let conn = establish_unpooled_connection();
-
-    let new_person = PersonForm {
-      name: "Gerry9812".into(),
-      ..PersonForm::default()
-    };
-
-    let inserted_person = Person::create(&conn, &new_person).unwrap();
-
-    let local_user_form = LocalUserForm {
-      person_id: inserted_person.id,
-      password_encrypted: "123456".to_string(),
-      ..LocalUserForm::default()
-    };
-
-    let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
-
-    let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
-    let claims = Claims::decode(&jwt).unwrap().claims;
+  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 pool = &mut pool.into();
+    let secret = Secret::init(pool).await.unwrap();
+    let settings = &SETTINGS.to_owned();
+
+    let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
+      .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(&conn, inserted_local_user.id, &"password111").unwrap();
+      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(&conn, inserted_person.id).unwrap();
+    let num_deleted = Person::delete(pool, inserted_person.id).await.unwrap();
     assert_eq!(1, num_deleted);
   }
-
-  #[test]
-  fn test_espeak() {
-    assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
-  }
 }