]> Untitled Git - lemmy.git/blobdiff - crates/api/src/lib.rs
Merge branch 'main' into change_password_api
[lemmy.git] / crates / api / src / lib.rs
index 5642c4b9c7a31f0a9a744af2ac70e8a974a2062d..0ba3fbea21370913f7b533ddb1afaec718fb1576 100644 (file)
@@ -1,37 +1,19 @@
 use actix_web::{web, web::Data};
-use lemmy_db_queries::{
-  source::{
-    community::{CommunityModerator_, Community_},
-    site::Site_,
-    user::UserSafeSettings_,
-  },
-  Crud,
-  DbPool,
-};
-use lemmy_db_schema::source::{
-  community::{Community, CommunityModerator},
-  post::Post,
-  site::Site,
-  user::{UserSafeSettings, User_},
-};
-use lemmy_db_views_actor::{
-  community_user_ban_view::CommunityUserBanView,
-  community_view::CommunityView,
-};
-use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*, websocket::*};
-use lemmy_utils::{claims::Claims, settings::Settings, ApiError, ConnectionId, LemmyError};
+use captcha::Captcha;
+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::process::Command;
-use url::Url;
 
-pub mod comment;
-pub mod community;
-pub mod post;
-pub mod routes;
-pub mod site;
-pub mod user;
-pub mod websocket;
+mod comment;
+mod comment_report;
+mod community;
+mod local_user;
+mod post;
+mod post_report;
+mod private_message;
+mod site;
+mod websocket;
 
 #[async_trait::async_trait(?Send)]
 pub trait Perform {
@@ -44,177 +26,6 @@ pub trait Perform {
   ) -> Result<Self::Response, LemmyError>;
 }
 
-pub(crate) async fn is_mod_or_admin(
-  pool: &DbPool,
-  user_id: i32,
-  community_id: i32,
-) -> Result<(), LemmyError> {
-  let is_mod_or_admin = blocking(pool, move |conn| {
-    CommunityView::is_mod_or_admin(conn, user_id, community_id)
-  })
-  .await?;
-  if !is_mod_or_admin {
-    return Err(ApiError::err("not_a_mod_or_admin").into());
-  }
-  Ok(())
-}
-pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
-  let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
-  if !user.admin {
-    return Err(ApiError::err("not_an_admin").into());
-  }
-  Ok(())
-}
-
-pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
-  match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
-    Ok(post) => Ok(post),
-    Err(_e) => Err(ApiError::err("couldnt_find_post").into()),
-  }
-}
-
-pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
-  let claims = match Claims::decode(&jwt) {
-    Ok(claims) => claims.claims,
-    Err(_e) => return Err(ApiError::err("not_logged_in").into()),
-  };
-  let user_id = claims.id;
-  let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
-  // Check for a site ban
-  if user.banned {
-    return Err(ApiError::err("site_ban").into());
-  }
-  Ok(user)
-}
-
-pub(crate) async fn get_user_from_jwt_opt(
-  jwt: &Option<String>,
-  pool: &DbPool,
-) -> Result<Option<User_>, LemmyError> {
-  match jwt {
-    Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
-    None => Ok(None),
-  }
-}
-
-pub(crate) async fn get_user_safe_settings_from_jwt(
-  jwt: &str,
-  pool: &DbPool,
-) -> Result<UserSafeSettings, LemmyError> {
-  let claims = match Claims::decode(&jwt) {
-    Ok(claims) => claims.claims,
-    Err(_e) => return Err(ApiError::err("not_logged_in").into()),
-  };
-  let user_id = claims.id;
-  let user = blocking(pool, move |conn| UserSafeSettings::read(conn, user_id)).await??;
-  // Check for a site ban
-  if user.banned {
-    return Err(ApiError::err("site_ban").into());
-  }
-  Ok(user)
-}
-
-pub(crate) async fn get_user_safe_settings_from_jwt_opt(
-  jwt: &Option<String>,
-  pool: &DbPool,
-) -> Result<Option<UserSafeSettings>, LemmyError> {
-  match jwt {
-    Some(jwt) => Ok(Some(get_user_safe_settings_from_jwt(jwt, pool).await?)),
-    None => Ok(None),
-  }
-}
-
-pub(crate) async fn check_community_ban(
-  user_id: i32,
-  community_id: i32,
-  pool: &DbPool,
-) -> Result<(), LemmyError> {
-  let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
-  if blocking(pool, is_banned).await? {
-    Err(ApiError::err("community_ban").into())
-  } else {
-    Ok(())
-  }
-}
-
-pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
-  if score == -1 {
-    let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
-    if !site.enable_downvotes {
-      return Err(ApiError::err("downvotes_disabled").into());
-    }
-  }
-  Ok(())
-}
-
-/// Returns a list of communities that the user moderates
-/// or if a community_id is supplied validates the user is a moderator
-/// of that community and returns the community id in a vec
-///
-/// * `user_id` - the user id of the moderator
-/// * `community_id` - optional community id to check for moderator privileges
-/// * `pool` - the diesel db pool
-pub(crate) async fn collect_moderated_communities(
-  user_id: i32,
-  community_id: Option<i32>,
-  pool: &DbPool,
-) -> Result<Vec<i32>, LemmyError> {
-  if let Some(community_id) = community_id {
-    // if the user provides a community_id, just check for mod/admin privileges
-    is_mod_or_admin(pool, user_id, community_id).await?;
-    Ok(vec![community_id])
-  } else {
-    let ids = blocking(pool, move |conn: &'_ _| {
-      CommunityModerator::get_user_moderated_communities(conn, user_id)
-    })
-    .await??;
-    Ok(ids)
-  }
-}
-
-pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
-  if let Some(Some(item)) = &item {
-    if Url::parse(item).is_err() {
-      return Err(ApiError::err("invalid_url").into());
-    }
-  }
-  Ok(())
-}
-
-pub(crate) async fn build_federated_instances(
-  pool: &DbPool,
-) -> Result<Option<FederatedInstances>, LemmyError> {
-  if Settings::get().federation.enabled {
-    let distinct_communities = blocking(pool, move |conn| {
-      Community::distinct_federated_communities(conn)
-    })
-    .await??;
-
-    let allowed = Settings::get().get_allowed_instances();
-    let blocked = Settings::get().get_blocked_instances();
-
-    let mut linked = distinct_communities
-      .iter()
-      .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
-      .collect::<Result<Vec<String>, LemmyError>>()?;
-
-    linked.extend_from_slice(&allowed);
-    linked.retain(|a| !blocked.contains(a) && !a.eq("") && !a.eq(&Settings::get().hostname));
-
-    // Sort and remove dupes
-    linked.sort_unstable();
-    linked.dedup();
-
-    Ok(Some(FederatedInstances {
-      linked,
-      allowed,
-      blocked,
-    }))
-  } else {
-    Ok(None)
-  }
-}
-
 pub async fn match_websocket_operation(
   context: LemmyContext,
   id: ConnectionId,
@@ -224,26 +35,19 @@ pub async fn match_websocket_operation(
   match op {
     // User ops
     UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
-    UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
-    UserOperation::GetUserDetails => {
-      do_websocket_operation::<GetUserDetails>(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::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
-    UserOperation::GetUserMentions => {
-      do_websocket_operation::<GetUserMentions>(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::MarkUserMentionAsRead => {
-      do_websocket_operation::<MarkUserMentionAsRead>(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::DeleteAccount => {
-      do_websocket_operation::<DeleteAccount>(context, id, op, data).await
-    }
     UserOperation::PasswordReset => {
       do_websocket_operation::<PasswordReset>(context, id, op, data).await
     }
@@ -259,32 +63,20 @@ pub async fn match_websocket_operation(
     UserOperation::SaveUserSettings => {
       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
     }
+    UserOperation::ChangePassword => {
+      do_websocket_operation::<ChangePassword>(context, id, op, data).await
+    }
     UserOperation::GetReportCount => {
       do_websocket_operation::<GetReportCount>(context, id, op, data).await
     }
 
     // Private Message ops
-    UserOperation::CreatePrivateMessage => {
-      do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
-    }
-    UserOperation::EditPrivateMessage => {
-      do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
-    }
-    UserOperation::DeletePrivateMessage => {
-      do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
-    }
     UserOperation::MarkPrivateMessageAsRead => {
       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
     }
-    UserOperation::GetPrivateMessages => {
-      do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
-    }
 
     // Site ops
     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
-    UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
-    UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
-    UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
     UserOperation::GetSiteConfig => {
       do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
     }
@@ -300,24 +92,6 @@ pub async fn match_websocket_operation(
     }
 
     // Community ops
-    UserOperation::GetCommunity => {
-      do_websocket_operation::<GetCommunity>(context, id, op, data).await
-    }
-    UserOperation::ListCommunities => {
-      do_websocket_operation::<ListCommunities>(context, id, op, data).await
-    }
-    UserOperation::CreateCommunity => {
-      do_websocket_operation::<CreateCommunity>(context, id, op, data).await
-    }
-    UserOperation::EditCommunity => {
-      do_websocket_operation::<EditCommunity>(context, id, op, data).await
-    }
-    UserOperation::DeleteCommunity => {
-      do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
-    }
-    UserOperation::RemoveCommunity => {
-      do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
-    }
     UserOperation::FollowCommunity => {
       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
     }
@@ -332,12 +106,6 @@ pub async fn match_websocket_operation(
     }
 
     // Post ops
-    UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
-    UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
-    UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
-    UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
-    UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
-    UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
     UserOperation::CreatePostLike => {
@@ -355,27 +123,12 @@ pub async fn match_websocket_operation(
     }
 
     // Comment ops
-    UserOperation::CreateComment => {
-      do_websocket_operation::<CreateComment>(context, id, op, data).await
-    }
-    UserOperation::EditComment => {
-      do_websocket_operation::<EditComment>(context, id, op, data).await
-    }
-    UserOperation::DeleteComment => {
-      do_websocket_operation::<DeleteComment>(context, id, op, data).await
-    }
-    UserOperation::RemoveComment => {
-      do_websocket_operation::<RemoveComment>(context, id, op, data).await
-    }
     UserOperation::MarkCommentAsRead => {
       do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
     }
     UserOperation::SaveComment => {
       do_websocket_operation::<SaveComment>(context, id, op, data).await
     }
-    UserOperation::GetComments => {
-      do_websocket_operation::<GetComments>(context, id, op, data).await
-    }
     UserOperation::CreateCommentLike => {
       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
     }
@@ -408,59 +161,62 @@ where
   serialize_websocket_message(&op, &res)
 }
 
-pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
-  let mut built_text = String::new();
+/// 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();
 
-  // 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)
-    };
+  let mut concat_letters: Vec<u8> = Vec::new();
 
-    built_text.push_str(&new_str);
+  for letter in letters {
+    let bytes = letter.unwrap_or_default();
+    concat_letters.extend(bytes);
   }
 
-  espeak_wav_base64(&built_text)
+  // Convert to base64
+  base64::encode(concat_letters)
 }
 
-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!("/tmp/lemmy_espeak_{}.wav", &uuid);
+#[cfg(test)]
+mod tests {
+  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},
+  };
+  use lemmy_utils::claims::Claims;
 
-  // Write the wav file
-  Command::new("espeak")
-    .arg("-w")
-    .arg(&file_path)
-    .arg(text)
-    .status()?;
+  #[test]
+  fn test_should_not_validate_user_token_after_password_change() {
+    let conn = establish_unpooled_connection();
 
-  // Read the wav file bytes
-  let bytes = std::fs::read(&file_path)?;
+    let new_person = PersonForm {
+      name: "Gerry9812".into(),
+      ..PersonForm::default()
+    };
 
-  // Delete the file
-  std::fs::remove_file(file_path)?;
+    let inserted_person = Person::create(&conn, &new_person).unwrap();
 
-  // Convert to base64
-  let base64 = base64::encode(bytes);
+    let local_user_form = LocalUserForm {
+      person_id: inserted_person.id,
+      password_encrypted: "123456".to_string(),
+      ..LocalUserForm::default()
+    };
 
-  Ok(base64)
-}
+    let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
 
-#[cfg(test)]
-mod tests {
-  use crate::captcha_espeak_wav_base64;
+    let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
+    let claims = Claims::decode(&jwt).unwrap().claims;
+    let check = check_validator_time(&inserted_local_user.validator_time, &claims);
+    assert!(check.is_ok());
 
-  #[test]
-  fn test_espeak() {
-    assert!(captcha_espeak_wav_base64("WxRt2l").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();
+    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();
+    assert_eq!(1, num_deleted);
   }
 }