From 76cd6ac6bc7e286e6b15363b5c3904523bc326a9 Mon Sep 17 00:00:00 2001 From: nutomic Date: Tue, 4 Aug 2020 14:39:55 +0000 Subject: [PATCH] Add more checks in inbox, plus some refactoring (#76) Merge branch 'main' into more-inbox-permissions Move check_community_ban() into helper function Move slur check into helper functions Move Claims::decode and site ban check into helper function Note: this changes behaviour in that site ban is checked in more places now. we could easily add a boolean parameter check_for_site_ban to get the previous behaviour back Rewrite user_inbox and community_inbox in the same way as shared_inbox Add check against instance allowlist etc in shared_inbox Co-authored-by: dessalines Co-authored-by: Felix Ableitner Reviewed-on: https://yerbamate.dev/LemmyNet/lemmy/pulls/76 --- server/src/api/comment.rs | 194 ++++----------- server/src/api/community.rs | 220 ++++------------- server/src/api/mod.rs | 56 ++++- server/src/api/post.rs | 268 +++++---------------- server/src/api/site.rs | 121 +++------- server/src/api/user.rs | 209 ++++------------ server/src/apub/activities.rs | 7 +- server/src/apub/fetcher.rs | 6 +- server/src/apub/inbox/activities/create.rs | 1 - server/src/apub/inbox/community_inbox.rs | 60 +++-- server/src/apub/inbox/shared_inbox.rs | 21 +- server/src/apub/inbox/user_inbox.rs | 116 ++++----- server/src/apub/mod.rs | 23 +- 13 files changed, 398 insertions(+), 904 deletions(-) diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 7dfce473f..e3189d434 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -1,5 +1,13 @@ use crate::{ - api::{claims::Claims, is_mod_or_admin, APIError, Oper, Perform}, + api::{ + check_community_ban, + get_user_from_jwt, + get_user_from_jwt_opt, + is_mod_or_admin, + APIError, + Oper, + Perform, + }, apub::{ApubLikeableType, ApubObjectType}, blocking, websocket::{ @@ -13,7 +21,6 @@ use crate::{ use lemmy_db::{ comment::*, comment_view::*, - community_view::*, moderator::*, post::*, site_view::*, @@ -123,13 +130,7 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &CreateComment = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let content_slurs_removed = remove_slurs(&data.content.to_owned()); @@ -137,7 +138,7 @@ impl Perform for Oper { content: content_slurs_removed, parent_id: data.parent_id.to_owned(), post_id: data.post_id, - creator_id: user_id, + creator_id: user.id, removed: None, deleted: None, read: None, @@ -151,18 +152,7 @@ impl Perform for Oper { let post_id = data.post_id; let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; - let community_id = post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(&conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + check_community_ban(user.id, post.community_id, pool).await?; // Check if post is locked, no new comments if post.locked { @@ -203,7 +193,7 @@ impl Perform for Oper { let like_form = CommentLikeForm { comment_id: inserted_comment.id, post_id: data.post_id, - user_id, + user_id: user.id, score: 1, }; @@ -214,6 +204,7 @@ impl Perform for Oper { updated_comment.send_like(&user, &self.client, pool).await?; + let user_id = user.id; let comment_view = blocking(pool, move |conn| { CommentView::read(&conn, inserted_comment.id, Some(user_id)) }) @@ -251,34 +242,16 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &EditComment = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_comment = blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_comment.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_comment.community_id, pool).await?; // Verify that only the creator can edit - if user_id != orig_comment.creator_id { + if user.id != orig_comment.creator_id { return Err(APIError::err("no_comment_edit_allowed").into()); } @@ -309,6 +282,7 @@ impl Perform for Oper { send_local_notifs(mentions, updated_comment, &user, post, pool, false).await?; let edit_id = data.edit_id; + let user_id = user.id; let comment_view = blocking(pool, move |conn| { CommentView::read(conn, edit_id, Some(user_id)) }) @@ -346,34 +320,16 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &DeleteComment = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_comment = blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_comment.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_comment.community_id, pool).await?; // Verify that only the creator can delete - if user_id != orig_comment.creator_id { + if user.id != orig_comment.creator_id { return Err(APIError::err("no_comment_edit_allowed").into()); } @@ -401,6 +357,7 @@ impl Perform for Oper { // Refetch it let edit_id = data.edit_id; + let user_id = user.id; let comment_view = blocking(pool, move |conn| { CommentView::read(conn, edit_id, Some(user_id)) }) @@ -445,34 +402,16 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &RemoveComment = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_comment = blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_comment.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_comment.community_id, pool).await?; // Verify that only a mod or admin can remove - is_mod_or_admin(pool, user_id, community_id).await?; + is_mod_or_admin(pool, user.id, orig_comment.community_id).await?; // Do the remove let removed = data.removed; @@ -487,7 +426,7 @@ impl Perform for Oper { // Mod tables let form = ModRemoveCommentForm { - mod_user_id: user_id, + mod_user_id: user.id, comment_id: data.edit_id, removed: Some(removed), reason: data.reason.to_owned(), @@ -507,6 +446,7 @@ impl Perform for Oper { // Refetch it let edit_id = data.edit_id; + let user_id = user.id; let comment_view = blocking(pool, move |conn| { CommentView::read(conn, edit_id, Some(user_id)) }) @@ -551,31 +491,13 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &MarkCommentAsRead = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_comment = blocking(pool, move |conn| CommentView::read(&conn, edit_id, None)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_comment.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_comment.community_id, pool).await?; // Verify that only the recipient can mark as read // Needs to fetch the parent comment / post to get the recipient @@ -584,14 +506,14 @@ impl Perform for Oper { Some(pid) => { let parent_comment = blocking(pool, move |conn| CommentView::read(&conn, pid, None)).await??; - if user_id != parent_comment.creator_id { + if user.id != parent_comment.creator_id { return Err(APIError::err("no_comment_edit_allowed").into()); } } None => { let parent_post_id = orig_comment.post_id; let parent_post = blocking(pool, move |conn| Post::read(conn, parent_post_id)).await??; - if user_id != parent_post.creator_id { + if user.id != parent_post.creator_id { return Err(APIError::err("no_comment_edit_allowed").into()); } } @@ -606,6 +528,7 @@ impl Perform for Oper { // Refetch it let edit_id = data.edit_id; + let user_id = user.id; let comment_view = blocking(pool, move |conn| { CommentView::read(conn, edit_id, Some(user_id)) }) @@ -631,17 +554,11 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &SaveComment = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let comment_saved_form = CommentSavedForm { comment_id: data.comment_id, - user_id, + user_id: user.id, }; if data.save { @@ -657,6 +574,7 @@ impl Perform for Oper { } let comment_id = data.comment_id; + let user_id = user.id; let comment_view = blocking(pool, move |conn| { CommentView::read(conn, comment_id, Some(user_id)) }) @@ -680,13 +598,7 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &CreateCommentLike = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let mut recipient_ids = Vec::new(); @@ -702,21 +614,9 @@ impl Perform for Oper { let orig_comment = blocking(pool, move |conn| CommentView::read(&conn, comment_id, None)).await??; - // Check for a community ban let post_id = orig_comment.post_id; let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; - let community_id = post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + check_community_ban(user.id, post.community_id, pool).await?; let comment_id = data.comment_id; let comment = blocking(pool, move |conn| Comment::read(conn, comment_id)).await??; @@ -725,7 +625,7 @@ impl Perform for Oper { match comment.parent_id { Some(parent_id) => { let parent_comment = blocking(pool, move |conn| Comment::read(conn, parent_id)).await??; - if parent_comment.creator_id != user_id { + if parent_comment.creator_id != user.id { let parent_user = blocking(pool, move |conn| { User_::read(conn, parent_comment.creator_id) }) @@ -741,7 +641,7 @@ impl Perform for Oper { let like_form = CommentLikeForm { comment_id: data.comment_id, post_id, - user_id, + user_id: user.id, score: data.score, }; @@ -769,6 +669,7 @@ impl Perform for Oper { // Have to refetch the comment to get the current state let comment_id = data.comment_id; + let user_id = user.id; let liked_comment = blocking(pool, move |conn| { CommentView::read(conn, comment_id, Some(user_id)) }) @@ -806,19 +707,8 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &GetComments = &self.data; - - let user_claims: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => Some(claims.claims), - Err(_e) => None, - }, - None => None, - }; - - let user_id = match &user_claims { - Some(claims) => Some(claims.id), - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; + let user_id = user.map(|u| u.id); let type_ = ListingType::from_str(&data.type_)?; let sort = SortType::from_str(&data.sort)?; diff --git a/server/src/api/community.rs b/server/src/api/community.rs index e4a8b6e8e..904dfe53c 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -1,6 +1,6 @@ use super::*; use crate::{ - api::{claims::Claims, is_admin, is_mod_or_admin, APIError, Oper, Perform}, + api::{is_admin, is_mod_or_admin, APIError, Oper, Perform}, apub::ActorType, blocking, websocket::{ @@ -16,8 +16,6 @@ use lemmy_utils::{ is_valid_community_name, make_apub_endpoint, naive_from_unix, - slur_check, - slurs_vec_to_str, EndpointType, }; use serde::{Deserialize, Serialize}; @@ -154,17 +152,8 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &GetCommunity = &self.data; - - let user_id: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None, - }, - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; + let user_id = user.map(|u| u.id); let name = data.name.to_owned().unwrap_or_else(|| "main".to_string()); let community = match data.id { @@ -234,38 +223,16 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &CreateCommunity = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Err(slurs) = slur_check(&data.title) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } + check_slurs(&data.name)?; + check_slurs(&data.title)?; + check_slurs_opt(&data.description)?; if !is_valid_community_name(&data.name) { return Err(APIError::err("invalid_community_name").into()); } - let user_id = claims.id; - - // Check for a site ban - let user_view = blocking(pool, move |conn| UserView::read(conn, user_id)).await??; - if user_view.banned { - return Err(APIError::err("site_ban").into()); - } - // Double check for duplicate community actor_ids let actor_id = make_apub_endpoint(EndpointType::Community, &data.name).to_string(); let actor_id_cloned = actor_id.to_owned(); @@ -285,7 +252,7 @@ impl Perform for Oper { title: data.title.to_owned(), description: data.description.to_owned(), category_id: data.category_id, - creator_id: user_id, + creator_id: user.id, removed: None, deleted: None, nsfw: data.nsfw, @@ -306,7 +273,7 @@ impl Perform for Oper { let community_moderator_form = CommunityModeratorForm { community_id: inserted_community.id, - user_id, + user_id: user.id, }; let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); @@ -316,7 +283,7 @@ impl Perform for Oper { let community_follower_form = CommunityFollowerForm { community_id: inserted_community.id, - user_id, + user_id: user.id, }; let follow = move |conn: &'_ _| CommunityFollower::follow(conn, &community_follower_form); @@ -324,6 +291,7 @@ impl Perform for Oper { return Err(APIError::err("community_follower_already_exists").into()); } + let user_id = user.id; let community_view = blocking(pool, move |conn| { CommunityView::read(conn, inserted_community.id, Some(user_id)) }) @@ -345,29 +313,10 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &EditCommunity = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - if let Err(slurs) = slur_check(&data.title) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + check_slurs(&data.title)?; + check_slurs_opt(&data.description)?; // Verify its a mod (only mods can edit it) let edit_id = data.edit_id; @@ -376,7 +325,7 @@ impl Perform for Oper { .map(|v| v.into_iter().map(|m| m.user_id).collect()) }) .await??; - if !mods.contains(&user_id) { + if !mods.contains(&user.id) { return Err(APIError::err("not_a_moderator").into()); } @@ -415,6 +364,7 @@ impl Perform for Oper { // process for communities and users let edit_id = data.edit_id; + let user_id = user.id; let community_view = blocking(pool, move |conn| { CommunityView::read(conn, edit_id, Some(user_id)) }) @@ -440,24 +390,12 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &DeleteCommunity = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + let user = get_user_from_jwt(&data.auth, pool).await?; // Verify its the creator (only a creator can delete the community) let edit_id = data.edit_id; let read_community = blocking(pool, move |conn| Community::read(conn, edit_id)).await??; - if read_community.creator_id != user_id { + if read_community.creator_id != user.id { return Err(APIError::err("no_community_edit_allowed").into()); } @@ -485,6 +423,7 @@ impl Perform for Oper { } let edit_id = data.edit_id; + let user_id = user.id; let community_view = blocking(pool, move |conn| { CommunityView::read(conn, edit_id, Some(user_id)) }) @@ -510,22 +449,10 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &RemoveCommunity = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + let user = get_user_from_jwt(&data.auth, pool).await?; // Verify its an admin (only an admin can remove a community) - is_admin(pool, user_id).await?; + is_admin(pool, user.id).await?; // Do the remove let edit_id = data.edit_id; @@ -545,7 +472,7 @@ impl Perform for Oper { None => None, }; let form = ModRemoveCommunityForm { - mod_user_id: user_id, + mod_user_id: user.id, community_id: data.edit_id, removed: Some(removed), reason: data.reason.to_owned(), @@ -565,6 +492,7 @@ impl Perform for Oper { } let edit_id = data.edit_id; + let user_id = user.id; let community_view = blocking(pool, move |conn| { CommunityView::read(conn, edit_id, Some(user_id)) }) @@ -590,19 +518,7 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &ListCommunities = &self.data; - - // For logged in users, you need to get back subscribed, and settings - let user: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - Some(user) - } - Err(_e) => None, - }, - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; let user_id = match &user { Some(user) => Some(user.id), @@ -644,19 +560,13 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &FollowCommunity = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let community_id = data.community_id; let community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; let community_follower_form = CommunityFollowerForm { community_id: data.community_id, - user_id, + user_id: user.id, }; if community.local { @@ -672,29 +582,25 @@ impl Perform for Oper { return Err(APIError::err("community_follower_already_exists").into()); } } + } else if data.follow { + // Dont actually add to the community followers here, because you need + // to wait for the accept + user + .send_follow(&community.actor_id, &self.client, pool) + .await?; } else { - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - - if data.follow { - // Dont actually add to the community followers here, because you need - // to wait for the accept - user - .send_follow(&community.actor_id, &self.client, pool) - .await?; - } else { - user - .send_unfollow(&community.actor_id, &self.client, pool) - .await?; - let unfollow = - move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); - if blocking(pool, unfollow).await?.is_err() { - return Err(APIError::err("community_follower_already_exists").into()); - } + user + .send_unfollow(&community.actor_id, &self.client, pool) + .await?; + let unfollow = move |conn: &'_ _| CommunityFollower::unfollow(conn, &community_follower_form); + if blocking(pool, unfollow).await?.is_err() { + return Err(APIError::err("community_follower_already_exists").into()); } - // TODO: this needs to return a "pending" state, until Accept is received from the remote server } + // TODO: this needs to return a "pending" state, until Accept is received from the remote server let community_id = data.community_id; + let user_id = user.id; let community_view = blocking(pool, move |conn| { CommunityView::read(conn, community_id, Some(user_id)) }) @@ -716,14 +622,9 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &GetFollowedCommunities = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - + let user_id = user.id; let communities = match blocking(pool, move |conn| { CommunityFollowerView::for_user(conn, user_id) }) @@ -748,18 +649,12 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &BanFromCommunity = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let community_id = data.community_id; // Verify that only mods or admins can ban - is_mod_or_admin(pool, user_id, community_id).await?; + is_mod_or_admin(pool, user.id, community_id).await?; let community_user_ban_form = CommunityUserBanForm { community_id: data.community_id, @@ -786,7 +681,7 @@ impl Perform for Oper { }; let form = ModBanFromCommunityForm { - mod_user_id: user_id, + mod_user_id: user.id, other_user_id: data.user_id, community_id: data.community_id, reason: data.reason.to_owned(), @@ -826,13 +721,7 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &AddModToCommunity = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let community_moderator_form = CommunityModeratorForm { community_id: data.community_id, @@ -842,7 +731,7 @@ impl Perform for Oper { let community_id = data.community_id; // Verify that only mods or admins can add mod - is_mod_or_admin(pool, user_id, community_id).await?; + is_mod_or_admin(pool, user.id, community_id).await?; if data.added { let join = move |conn: &'_ _| CommunityModerator::join(conn, &community_moderator_form); @@ -858,7 +747,7 @@ impl Perform for Oper { // Mod tables let form = ModAddCommunityForm { - mod_user_id: user_id, + mod_user_id: user.id, other_user_id: data.user_id, community_id: data.community_id, removed: Some(!data.added), @@ -896,13 +785,7 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &TransferCommunity = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let community_id = data.community_id; let read_community = blocking(pool, move |conn| Community::read(conn, community_id)).await??; @@ -917,7 +800,7 @@ impl Perform for Oper { admins.insert(0, creator_user); // Make sure user is the creator, or an admin - if user_id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user_id) { + if user.id != read_community.creator_id && !admins.iter().map(|a| a.id).any(|x| x == user.id) { return Err(APIError::err("not_an_admin").into()); } @@ -962,7 +845,7 @@ impl Perform for Oper { // Mod tables let form = ModAddCommunityForm { - mod_user_id: user_id, + mod_user_id: user.id, other_user_id: data.user_id, community_id: data.community_id, removed: Some(false), @@ -970,6 +853,7 @@ impl Perform for Oper { blocking(pool, move |conn| ModAddCommunity::create(conn, &form)).await??; let community_id = data.community_id; + let user_id = user.id; let community_view = match blocking(pool, move |conn| { CommunityView::read(conn, community_id, Some(user_id)) }) diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 11f958f08..a9aae823a 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -1,4 +1,4 @@ -use crate::{blocking, websocket::WebsocketInfo, DbPool, LemmyError}; +use crate::{api::claims::Claims, blocking, websocket::WebsocketInfo, DbPool, LemmyError}; use actix_web::client::Client; use lemmy_db::{ community::*, @@ -9,6 +9,7 @@ use lemmy_db::{ user_view::*, Crud, }; +use lemmy_utils::{slur_check, slurs_vec_to_str}; use thiserror::Error; pub mod claims; @@ -75,3 +76,56 @@ pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> { } Ok(()) } + +pub(in crate::api) async fn get_user_from_jwt( + jwt: &str, + pool: &DbPool, +) -> Result { + 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(in crate::api) async fn get_user_from_jwt_opt( + jwt: &Option, + pool: &DbPool, +) -> Result, LemmyError> { + match jwt { + Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)), + None => Ok(None), + } +} + +pub(in crate::api) fn check_slurs(text: &str) -> Result<(), APIError> { + if let Err(slurs) = slur_check(text) { + Err(APIError::err(&slurs_vec_to_str(slurs))) + } else { + Ok(()) + } +} +pub(in crate::api) fn check_slurs_opt(text: &Option) -> Result<(), APIError> { + match text { + Some(t) => check_slurs(t), + None => Ok(()), + } +} +pub(in crate::api) 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(()) + } +} diff --git a/server/src/api/post.rs b/server/src/api/post.rs index e346a6c89..b43e4e55c 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -1,5 +1,15 @@ use crate::{ - api::{claims::Claims, is_mod_or_admin, APIError, Oper, Perform}, + api::{ + check_community_ban, + check_slurs, + check_slurs_opt, + get_user_from_jwt, + get_user_from_jwt_opt, + is_mod_or_admin, + APIError, + Oper, + Perform, + }, apub::{ApubLikeableType, ApubObjectType}, blocking, fetch_iframely_and_pictrs_data, @@ -19,20 +29,13 @@ use lemmy_db::{ post::*, post_view::*, site_view::*, - user::*, Crud, Likeable, ListingType, Saveable, SortType, }; -use lemmy_utils::{ - is_valid_post_title, - make_apub_endpoint, - slur_check, - slurs_vec_to_str, - EndpointType, -}; +use lemmy_utils::{is_valid_post_title, make_apub_endpoint, EndpointType}; use serde::{Deserialize, Serialize}; use std::str::FromStr; use url::Url; @@ -146,41 +149,16 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &CreatePost = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(body) = &data.body { - if let Err(slurs) = slur_check(body) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } + check_slurs(&data.name)?; + check_slurs_opt(&data.body)?; if !is_valid_post_title(&data.name) { return Err(APIError::err("invalid_post_title").into()); } - let user_id = claims.id; - - // Check for a community ban - let community_id = data.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + check_community_ban(user.id, data.community_id, pool).await?; if let Some(url) = data.url.as_ref() { match Url::parse(url) { @@ -198,7 +176,7 @@ impl Perform for Oper { url: data.url.to_owned(), body: data.body.to_owned(), community_id: data.community_id, - creator_id: user_id, + creator_id: user.id, removed: None, deleted: None, nsfw: data.nsfw, @@ -244,7 +222,7 @@ impl Perform for Oper { // They like their own post by default let like_form = PostLikeForm { post_id: inserted_post.id, - user_id, + user_id: user.id, score: 1, }; @@ -258,7 +236,7 @@ impl Perform for Oper { // Refetch the view let inserted_post_id = inserted_post.id; let post_view = match blocking(pool, move |conn| { - PostView::read(conn, inserted_post_id, Some(user_id)) + PostView::read(conn, inserted_post_id, Some(user.id)) }) .await? { @@ -290,17 +268,8 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &GetPost = &self.data; - - let user_id: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None, - }, - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; + let user_id = user.map(|u| u.id); let id = data.id; let post_view = match blocking(pool, move |conn| PostView::read(conn, id, user_id)).await? { @@ -369,19 +338,7 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &GetPosts = &self.data; - - // For logged in users, you need to get back subscribed, and settings - let user: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - Some(user) - } - Err(_e) => None, - }, - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; let user_id = match &user { Some(user) => Some(user.id), @@ -446,13 +403,7 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &CreatePostLike = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; // Don't do a downvote if site has downvotes disabled if data.score == -1 { @@ -466,22 +417,11 @@ impl Perform for Oper { let post_id = data.post_id; let post = blocking(pool, move |conn| Post::read(conn, post_id)).await??; - let community_id = post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + check_community_ban(user.id, post.community_id, pool).await?; let like_form = PostLikeForm { post_id: data.post_id, - user_id, + user_id: user.id, score: data.score, }; @@ -508,6 +448,7 @@ impl Perform for Oper { } let post_id = data.post_id; + let user_id = user.id; let post_view = match blocking(pool, move |conn| { PostView::read(conn, post_id, Some(user_id)) }) @@ -541,47 +482,22 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &EditPost = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(body) = &data.body { - if let Err(slurs) = slur_check(body) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } + check_slurs(&data.name)?; + check_slurs_opt(&data.body)?; if !is_valid_post_title(&data.name) { return Err(APIError::err("invalid_post_title").into()); } - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - let edit_id = data.edit_id; let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; - // Check for a community ban - let community_id = orig_post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + check_community_ban(user.id, orig_post.community_id, pool).await?; // Verify that only the creator can edit - if !Post::is_post_creator(user_id, orig_post.creator_id) { + if !Post::is_post_creator(user.id, orig_post.creator_id) { return Err(APIError::err("no_post_edit_allowed").into()); } @@ -630,7 +546,7 @@ impl Perform for Oper { let edit_id = data.edit_id; let post_view = blocking(pool, move |conn| { - PostView::read(conn, edit_id, Some(user_id)) + PostView::read(conn, edit_id, Some(user.id)) }) .await??; @@ -658,33 +574,15 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &DeletePost = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_post.community_id, pool).await?; // Verify that only the creator can delete - if !Post::is_post_creator(user_id, orig_post.creator_id) { + if !Post::is_post_creator(user.id, orig_post.creator_id) { return Err(APIError::err("no_post_edit_allowed").into()); } @@ -708,7 +606,7 @@ impl Perform for Oper { // Refetch the post let edit_id = data.edit_id; let post_view = blocking(pool, move |conn| { - PostView::read(conn, edit_id, Some(user_id)) + PostView::read(conn, edit_id, Some(user.id)) }) .await??; @@ -736,33 +634,15 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &RemovePost = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_post.community_id, pool).await?; // Verify that only the mods can remove - is_mod_or_admin(pool, user_id, community_id).await?; + is_mod_or_admin(pool, user.id, orig_post.community_id).await?; // Update the post let edit_id = data.edit_id; @@ -774,7 +654,7 @@ impl Perform for Oper { // Mod tables let form = ModRemovePostForm { - mod_user_id: user_id, + mod_user_id: user.id, post_id: data.edit_id, removed: Some(removed), reason: data.reason.to_owned(), @@ -792,6 +672,7 @@ impl Perform for Oper { // Refetch the post let edit_id = data.edit_id; + let user_id = user.id; let post_view = blocking(pool, move |conn| { PostView::read(conn, edit_id, Some(user_id)) }) @@ -821,33 +702,15 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &LockPost = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_post.community_id, pool).await?; // Verify that only the mods can lock - is_mod_or_admin(pool, user_id, community_id).await?; + is_mod_or_admin(pool, user.id, orig_post.community_id).await?; // Update the post let edit_id = data.edit_id; @@ -857,7 +720,7 @@ impl Perform for Oper { // Mod tables let form = ModLockPostForm { - mod_user_id: user_id, + mod_user_id: user.id, post_id: data.edit_id, locked: Some(locked), }; @@ -869,7 +732,7 @@ impl Perform for Oper { // Refetch the post let edit_id = data.edit_id; let post_view = blocking(pool, move |conn| { - PostView::read(conn, edit_id, Some(user_id)) + PostView::read(conn, edit_id, Some(user.id)) }) .await??; @@ -897,33 +760,15 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &StickyPost = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let edit_id = data.edit_id; let orig_post = blocking(pool, move |conn| Post::read(conn, edit_id)).await??; - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - - // Check for a community ban - let community_id = orig_post.community_id; - let is_banned = - move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok(); - if blocking(pool, is_banned).await? { - return Err(APIError::err("community_ban").into()); - } + check_community_ban(user.id, orig_post.community_id, pool).await?; // Verify that only the mods can sticky - is_mod_or_admin(pool, user_id, community_id).await?; + is_mod_or_admin(pool, user.id, orig_post.community_id).await?; // Update the post let edit_id = data.edit_id; @@ -935,7 +780,7 @@ impl Perform for Oper { // Mod tables let form = ModStickyPostForm { - mod_user_id: user_id, + mod_user_id: user.id, post_id: data.edit_id, stickied: Some(stickied), }; @@ -948,7 +793,7 @@ impl Perform for Oper { // Refetch the post let edit_id = data.edit_id; let post_view = blocking(pool, move |conn| { - PostView::read(conn, edit_id, Some(user_id)) + PostView::read(conn, edit_id, Some(user.id)) }) .await??; @@ -976,17 +821,11 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &SavePost = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let post_saved_form = PostSavedForm { post_id: data.post_id, - user_id, + user_id: user.id, }; if data.save { @@ -1002,6 +841,7 @@ impl Perform for Oper { } let post_id = data.post_id; + let user_id = user.id; let post_view = blocking(pool, move |conn| { PostView::read(conn, post_id, Some(user_id)) }) diff --git a/server/src/api/site.rs b/server/src/api/site.rs index adade080e..82cad9610 100644 --- a/server/src/api/site.rs +++ b/server/src/api/site.rs @@ -1,6 +1,15 @@ use super::user::Register; use crate::{ - api::{claims::Claims, is_admin, APIError, Oper, Perform}, + api::{ + check_slurs, + check_slurs_opt, + get_user_from_jwt, + get_user_from_jwt_opt, + is_admin, + APIError, + Oper, + Perform, + }, apub::fetcher::search_by_apub_id, blocking, version, @@ -24,7 +33,7 @@ use lemmy_db::{ SearchType, SortType, }; -use lemmy_utils::{settings::Settings, slur_check, slurs_vec_to_str}; +use lemmy_utils::settings::Settings; use log::{debug, info}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -243,30 +252,18 @@ impl Perform for Oper { ) -> Result { let data: &CreateSite = &self.data; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; + let user = get_user_from_jwt(&data.auth, pool).await?; - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } - - let user_id = claims.id; + check_slurs(&data.name)?; + check_slurs_opt(&data.description)?; // Make sure user is an admin - is_admin(pool, user_id).await?; + is_admin(pool, user.id).await?; let site_form = SiteForm { name: data.name.to_owned(), description: data.description.to_owned(), - creator_id: user_id, + creator_id: user.id, enable_downvotes: data.enable_downvotes, open_registration: data.open_registration, enable_nsfw: data.enable_nsfw, @@ -293,26 +290,13 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &EditSite = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - if let Err(slurs) = slur_check(&data.name) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - - if let Some(description) = &data.description { - if let Err(slurs) = slur_check(description) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } - } - - let user_id = claims.id; + check_slurs(&data.name)?; + check_slurs_opt(&data.description)?; // Make sure user is an admin - is_admin(pool, user_id).await?; + is_admin(pool, user.id).await?; let found_site = blocking(pool, move |conn| Site::read(conn, 1)).await??; @@ -421,21 +405,12 @@ impl Perform for Oper { 0 }; - // Giving back your user, if you're logged in - let my_user: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - let mut user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - user.password_encrypted = "".to_string(); - user.private_key = None; - user.public_key = None; - Some(user) - } - Err(_e) => None, - }, - None => None, - }; + let my_user = get_user_from_jwt_opt(&data.auth, pool).await?.map(|mut u| { + u.password_encrypted = "".to_string(); + u.private_key = None; + u.public_key = None; + u + }); Ok(GetSiteResponse { site: site_view, @@ -466,16 +441,8 @@ impl Perform for Oper { Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e), } - let user_id: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - Some(user_id) - } - Err(_e) => None, - }, - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; + let user_id = user.map(|u| u.id); let type_ = SearchType::from_str(&data.type_)?; @@ -630,14 +597,8 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &TransferSite = &self.data; + let mut user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - let mut user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; // TODO add a User_::read_safe() for this. user.password_encrypted = "".to_string(); user.private_key = None; @@ -646,7 +607,7 @@ impl Perform for Oper { let read_site = blocking(pool, move |conn| Site::read(conn, 1)).await??; // Make sure user is the creator - if read_site.creator_id != user_id { + if read_site.creator_id != user.id { return Err(APIError::err("not_an_admin").into()); } @@ -667,7 +628,7 @@ impl Perform for Oper { // Mod tables let form = ModAddForm { - mod_user_id: user_id, + mod_user_id: user.id, other_user_id: data.user_id, removed: Some(false), }; @@ -707,16 +668,10 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &GetSiteConfig = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; // Only let admins read this - is_admin(pool, user_id).await?; + is_admin(pool, user.id).await?; let config_hjson = Settings::read_config_file()?; @@ -734,19 +689,13 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &SaveSiteConfig = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; // Only let admins read this let admins = blocking(pool, move |conn| UserView::admins(conn)).await??; let admin_ids: Vec = admins.into_iter().map(|m| m.id).collect(); - if !admin_ids.contains(&user_id) { + if !admin_ids.contains(&user.id) { return Err(APIError::err("not_an_admin").into()); } diff --git a/server/src/api/user.rs b/server/src/api/user.rs index f9a92cd39..d6746c1a8 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -1,5 +1,14 @@ use crate::{ - api::{claims::Claims, is_admin, APIError, Oper, Perform}, + api::{ + check_slurs, + claims::Claims, + get_user_from_jwt, + get_user_from_jwt_opt, + is_admin, + APIError, + Oper, + Perform, + }, apub::ApubObjectType, blocking, captcha_espeak_wav_base64, @@ -47,8 +56,6 @@ use lemmy_utils::{ remove_slurs, send_email, settings::Settings, - slur_check, - slurs_vec_to_str, EndpointType, }; use log::error; @@ -366,9 +373,7 @@ impl Perform for Oper { }; } - if let Err(slurs) = slur_check(&data.username) { - return Err(APIError::err(&slurs_vec_to_str(slurs)).into()); - } + check_slurs(&data.username)?; // Make sure there are no admins let any_admins = blocking(pool, move |conn| { @@ -543,14 +548,9 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &SaveUserSettings = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - + let user_id = user.id; let read_user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; let email = match &data.email { @@ -665,24 +665,7 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &GetUserDetails = &self.data; - - // For logged in users, you need to get back subscribed, and settings - let user: Option = match &data.auth { - Some(auth) => match Claims::decode(&auth) { - Ok(claims) => { - let user_id = claims.claims.id; - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - Some(user) - } - Err(_e) => None, - }, - None => None, - }; - - let user_id = match &user { - Some(user) => Some(user.id), - None => None, - }; + let user = get_user_from_jwt_opt(&data.auth, pool).await?; let show_nsfw = match &user { Some(user) => user.show_nsfw, @@ -712,6 +695,7 @@ impl Perform for Oper { let limit = data.limit; let saved_only = data.saved_only; let community_id = data.community_id; + let user_id = user.map(|u| u.id); let (posts, comments) = blocking(pool, move |conn| { let mut posts_query = PostQueryBuilder::create(conn) .sort(&sort) @@ -780,16 +764,10 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &AddAdmin = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; // Make sure user is an admin - is_admin(pool, user_id).await?; + is_admin(pool, user.id).await?; let added = data.added; let added_user_id = data.user_id; @@ -800,7 +778,7 @@ impl Perform for Oper { // Mod tables let form = ModAddForm { - mod_user_id: user_id, + mod_user_id: user.id, other_user_id: data.user_id, removed: Some(!data.added), }; @@ -839,16 +817,10 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &BanUser = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; // Make sure user is an admin - is_admin(pool, user_id).await?; + is_admin(pool, user.id).await?; let ban = data.ban; let banned_user_id = data.user_id; @@ -864,7 +836,7 @@ impl Perform for Oper { }; let form = ModBanForm { - mod_user_id: user_id, + mod_user_id: user.id, other_user_id: data.user_id, reason: data.reason.to_owned(), banned: Some(data.ban), @@ -903,19 +875,14 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &GetReplies = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let sort = SortType::from_str(&data.sort)?; let page = data.page; let limit = data.limit; let unread_only = data.unread_only; + let user_id = user.id; let replies = blocking(pool, move |conn| { ReplyQueryBuilder::create(conn, user_id) .sort(&sort) @@ -940,19 +907,14 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &GetUserMentions = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let sort = SortType::from_str(&data.sort)?; let page = data.page; let limit = data.limit; let unread_only = data.unread_only; + let user_id = user.id; let mentions = blocking(pool, move |conn| { UserMentionQueryBuilder::create(conn, user_id) .sort(&sort) @@ -977,19 +939,13 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &MarkUserMentionAsRead = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let user_mention_id = data.user_mention_id; let read_user_mention = blocking(pool, move |conn| UserMention::read(conn, user_mention_id)).await??; - if user_id != read_user_mention.recipient_id { + if user.id != read_user_mention.recipient_id { return Err(APIError::err("couldnt_update_comment").into()); } @@ -1001,6 +957,7 @@ impl Perform for Oper { }; let user_mention_id = read_user_mention.id; + let user_id = user.id; let user_mention_view = blocking(pool, move |conn| { UserMentionView::read(conn, user_mention_id, user_id) }) @@ -1022,14 +979,9 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &MarkAllAsRead = &self.data; + let user = get_user_from_jwt(&data.auth, pool).await?; - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - + let user_id = user.id; let replies = blocking(pool, move |conn| { ReplyQueryBuilder::create(conn, user_id) .unread_only(true) @@ -1076,15 +1028,7 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &DeleteAccount = &self.data; - - let claims = match Claims::decode(&data.auth) { - 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??; + let user = get_user_from_jwt(&data.auth, pool).await?; // Verify the password let valid: bool = verify(&data.password, &user.password_encrypted).unwrap_or(false); @@ -1093,6 +1037,7 @@ impl Perform for Oper { } // Comments + let user_id = user.id; let comments = blocking(pool, move |conn| { CommentQueryBuilder::create(conn) .for_creator_id(user_id) @@ -1230,27 +1175,15 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &CreatePrivateMessage = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; let hostname = &format!("https://{}", Settings::get().hostname); - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } - let content_slurs_removed = remove_slurs(&data.content.to_owned()); let private_message_form = PrivateMessageForm { content: content_slurs_removed.to_owned(), - creator_id: user_id, + creator_id: user.id, recipient_id: data.recipient_id, deleted: None, read: None, @@ -1341,25 +1274,13 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &EditPrivateMessage = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + let user = get_user_from_jwt(&data.auth, pool).await?; // Checking permissions let edit_id = data.edit_id; let orig_private_message = blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??; - if user_id != orig_private_message.creator_id { + if user.id != orig_private_message.creator_id { return Err(APIError::err("no_private_message_edit_allowed").into()); } @@ -1409,25 +1330,13 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &DeletePrivateMessage = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + let user = get_user_from_jwt(&data.auth, pool).await?; // Checking permissions let edit_id = data.edit_id; let orig_private_message = blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??; - if user_id != orig_private_message.creator_id { + if user.id != orig_private_message.creator_id { return Err(APIError::err("no_private_message_edit_allowed").into()); } @@ -1483,25 +1392,13 @@ impl Perform for Oper { websocket_info: Option, ) -> Result { let data: &MarkPrivateMessageAsRead = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; - - // Check for a site ban - let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??; - if user.banned { - return Err(APIError::err("site_ban").into()); - } + let user = get_user_from_jwt(&data.auth, pool).await?; // Checking permissions let edit_id = data.edit_id; let orig_private_message = blocking(pool, move |conn| PrivateMessage::read(conn, edit_id)).await??; - if user_id != orig_private_message.recipient_id { + if user.id != orig_private_message.recipient_id { return Err(APIError::err("couldnt_update_private_message").into()); } @@ -1548,13 +1445,8 @@ impl Perform for Oper { _websocket_info: Option, ) -> Result { let data: &GetPrivateMessages = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; + let user_id = user.id; let page = data.page; let limit = data.limit; @@ -1578,24 +1470,21 @@ impl Perform for Oper { async fn perform( &self, - _pool: &DbPool, + pool: &DbPool, websocket_info: Option, ) -> Result { let data: &UserJoin = &self.data; - - let claims = match Claims::decode(&data.auth) { - Ok(claims) => claims.claims, - Err(_e) => return Err(APIError::err("not_logged_in").into()), - }; - - let user_id = claims.id; + let user = get_user_from_jwt(&data.auth, pool).await?; if let Some(ws) = websocket_info { if let Some(id) = ws.id { - ws.chatserver.do_send(JoinUserRoom { user_id, id }); + ws.chatserver.do_send(JoinUserRoom { + user_id: user.id, + id, + }); } } - Ok(UserJoinResponse { user_id }) + Ok(UserJoinResponse { user_id: user.id }) } } diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index 9fdfe37fb..b5d6ce464 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -1,9 +1,9 @@ use crate::{ apub::{ + check_is_apub_id_valid, community::do_announce, extensions::signatures::sign, insert_activity, - is_apub_id_valid, ActorType, }, request::retry_custom, @@ -50,10 +50,7 @@ pub async fn send_activity( for t in to { let to_url = Url::parse(&t)?; - if !is_apub_id_valid(&to_url) { - debug!("Not sending activity to {} (invalid or blocklisted)", t); - continue; - } + check_is_apub_id_valid(&to_url)?; let res = retry_custom(|| async { let request = client.post(&t).header("Content-Type", "application/json"); diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs index 4425757de..919b0e884 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -1,7 +1,7 @@ use crate::{ api::site::SearchResponse, apub::{ - is_apub_id_valid, + check_is_apub_id_valid, ActorType, FromApub, GroupExt, @@ -66,9 +66,7 @@ pub async fn fetch_remote_object( where Response: for<'de> Deserialize<'de>, { - if !is_apub_id_valid(&url) { - return Err(anyhow!("Activitypub uri invalid or blocked: {}", url).into()); - } + check_is_apub_id_valid(&url)?; let timeout = Duration::from_secs(60); diff --git a/server/src/apub/inbox/activities/create.rs b/server/src/apub/inbox/activities/create.rs index f8a92c1c6..ceeef0ef7 100644 --- a/server/src/apub/inbox/activities/create.rs +++ b/server/src/apub/inbox/activities/create.rs @@ -39,7 +39,6 @@ pub async fn receive_create( chat_server: ChatServerParam, ) -> Result { let create = Create::from_any_base(activity)?.unwrap(); - dbg!(create.object().as_single_kind_str()); match create.object().as_single_kind_str() { Some("Page") => receive_create_post(create, client, pool, chat_server).await, Some("Note") => receive_create_comment(create, client, pool, chat_server).await, diff --git a/server/src/apub/inbox/community_inbox.rs b/server/src/apub/inbox/community_inbox.rs index 69dd2cdf7..37e7c2895 100644 --- a/server/src/apub/inbox/community_inbox.rs +++ b/server/src/apub/inbox/community_inbox.rs @@ -1,7 +1,8 @@ use crate::{ apub::{ + check_is_apub_id_valid, extensions::signatures::verify, - fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, + fetcher::get_or_fetch_and_upsert_user, insert_activity, ActorType, }, @@ -10,7 +11,8 @@ use crate::{ LemmyError, }; use activitystreams::{ - activity::{Follow, Undo}, + activity::{ActorAndObject, Follow, Undo}, + base::AnyBase, prelude::*, }; use actix_web::{client::Client, web, HttpRequest, HttpResponse}; @@ -21,37 +23,28 @@ use lemmy_db::{ Followable, }; use log::debug; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; -#[serde(untagged)] -#[derive(Deserialize, Debug)] -pub enum CommunityAcceptedObjects { - Follow(Follow), - Undo(Undo), +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub enum ValidTypes { + Follow, + Undo, } -impl CommunityAcceptedObjects { - fn follow(&self) -> Result { - match self { - CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()), - CommunityAcceptedObjects::Undo(u) => { - Ok(Follow::from_any_base(u.object().as_one().unwrap().to_owned())?.unwrap()) - } - } - } -} +pub type AcceptedActivities = ActorAndObject; /// Handler for all incoming activities to community inboxes. pub async fn community_inbox( request: HttpRequest, - input: web::Json, + input: web::Json, path: web::Path, db: DbPoolParam, client: web::Data, _chat_server: ChatServerParam, ) -> Result { - let input = input.into_inner(); + let activity = input.into_inner(); let path = path.into_inner(); let community = blocking(&db, move |conn| Community::read_from_name(&conn, &path)).await??; @@ -67,34 +60,35 @@ pub async fn community_inbox( } debug!( "Community {} received activity {:?}", - &community.name, &input + &community.name, &activity ); - let follow = input.follow()?; - let user_uri = follow.actor()?.as_single_xsd_any_uri().unwrap(); - let community_uri = follow.object().as_single_xsd_any_uri().unwrap(); + let user_uri = activity.actor()?.as_single_xsd_any_uri().unwrap(); + check_is_apub_id_valid(user_uri)?; let user = get_or_fetch_and_upsert_user(&user_uri, &client, &db).await?; - let community = get_or_fetch_and_upsert_community(community_uri, &client, &db).await?; verify(&request, &user)?; - match input { - CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await, - CommunityAcceptedObjects::Undo(u) => handle_undo_follow(u, user, community, db).await, + insert_activity(user.id, activity.clone(), false, &db).await?; + + let any_base = activity.clone().into_any_base()?; + let kind = activity.kind().unwrap(); + match kind { + ValidTypes::Follow => handle_follow(any_base, user, community, &client, db).await, + ValidTypes::Undo => handle_undo_follow(any_base, user, community, db).await, } } /// Handle a follow request from a remote user, adding it to the local database and returning an /// Accept activity. async fn handle_follow( - follow: Follow, + activity: AnyBase, user: User_, community: Community, client: &Client, db: DbPoolParam, ) -> Result { - insert_activity(user.id, follow.clone(), false, &db).await?; - + let follow = Follow::from_any_base(activity)?.unwrap(); let community_follower_form = CommunityFollowerForm { community_id: community.id, user_id: user.id, @@ -112,12 +106,12 @@ async fn handle_follow( } async fn handle_undo_follow( - undo: Undo, + activity: AnyBase, user: User_, community: Community, db: DbPoolParam, ) -> Result { - insert_activity(user.id, undo, false, &db).await?; + let _undo = Undo::from_any_base(activity)?.unwrap(); let community_follower_form = CommunityFollowerForm { community_id: community.id, diff --git a/server/src/apub/inbox/shared_inbox.rs b/server/src/apub/inbox/shared_inbox.rs index 9e0cdb3d8..db44a99da 100644 --- a/server/src/apub/inbox/shared_inbox.rs +++ b/server/src/apub/inbox/shared_inbox.rs @@ -1,5 +1,6 @@ use crate::{ apub::{ + check_is_apub_id_valid, community::do_announce, extensions::signatures::verify, fetcher::{ @@ -32,11 +33,11 @@ use activitystreams::{ use actix_web::{client::Client, web, HttpRequest, HttpResponse}; use lemmy_db::user::User_; use log::debug; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; use url::Url; -#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub enum ValidTypes { Create, @@ -67,16 +68,19 @@ pub async fn shared_inbox( debug!("Shared inbox received activity: {}", json); let sender = &activity.actor()?.to_owned().single_xsd_any_uri().unwrap(); - // TODO: pass this actor in instead of using get_user_from_activity() let actor = get_or_fetch_and_upsert_actor(sender, &client, &pool).await?; + + let community = get_community_id_from_activity(&activity).await; + + check_is_apub_id_valid(sender)?; + check_is_apub_id_valid(&community)?; verify(&request, actor.as_ref())?; insert_activity(actor.user_id(), activity.clone(), false, &pool).await?; let any_base = activity.clone().into_any_base()?; let kind = activity.kind().unwrap(); - dbg!(kind); match kind { ValidTypes::Announce => receive_announce(any_base, &client, &pool, chat_server).await, ValidTypes::Create => receive_create(any_base, &client, &pool, chat_server).await, @@ -112,6 +116,15 @@ where get_or_fetch_and_upsert_user(&user_uri, client, pool).await } +pub(in crate::apub::inbox) async fn get_community_id_from_activity(activity: &T) -> Url +where + T: AsBase + ActorAndObjectRef + AsObject, +{ + let cc = activity.cc().unwrap(); + let cc = cc.as_many().unwrap(); + cc.first().unwrap().as_xsd_any_uri().unwrap().to_owned() +} + pub(in crate::apub::inbox) async fn announce_if_community_is_local( activity: T, user: &User_, diff --git a/server/src/apub/inbox/user_inbox.rs b/server/src/apub/inbox/user_inbox.rs index b46f67027..ea018f569 100644 --- a/server/src/apub/inbox/user_inbox.rs +++ b/server/src/apub/inbox/user_inbox.rs @@ -1,8 +1,9 @@ use crate::{ api::user::PrivateMessageResponse, apub::{ + check_is_apub_id_valid, extensions::signatures::verify, - fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, + fetcher::{get_or_fetch_and_upsert_actor, get_or_fetch_and_upsert_community}, insert_activity, FromApub, }, @@ -13,7 +14,8 @@ use crate::{ LemmyError, }; use activitystreams::{ - activity::{Accept, Create, Delete, Undo, Update}, + activity::{Accept, ActorAndObject, Create, Delete, Undo, Update}, + base::AnyBase, object::Note, prelude::*, }; @@ -28,68 +30,76 @@ use lemmy_db::{ Followable, }; use log::debug; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; -#[serde(untagged)] -#[derive(Deserialize, Debug)] -pub enum UserAcceptedObjects { - Accept(Box), - Create(Box), - Update(Box), - Delete(Box), - Undo(Box), +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub enum ValidTypes { + Accept, + Create, + Update, + Delete, + Undo, } +pub type AcceptedActivities = ActorAndObject; + /// Handler for all incoming activities to user inboxes. pub async fn user_inbox( request: HttpRequest, - input: web::Json, + input: web::Json, path: web::Path, client: web::Data, - db: DbPoolParam, + pool: DbPoolParam, chat_server: ChatServerParam, ) -> Result { - // TODO: would be nice if we could do the signature check here, but we cant access the actor property - let input = input.into_inner(); + let activity = input.into_inner(); let username = path.into_inner(); - debug!("User {} received activity: {:?}", &username, &input); + debug!("User {} received activity: {:?}", &username, &activity); - match input { - UserAcceptedObjects::Accept(a) => receive_accept(*a, &request, &username, &client, &db).await, - UserAcceptedObjects::Create(c) => { - receive_create_private_message(*c, &request, &client, &db, chat_server).await + let actor_uri = activity.actor()?.as_single_xsd_any_uri().unwrap(); + + check_is_apub_id_valid(actor_uri)?; + + let actor = get_or_fetch_and_upsert_actor(actor_uri, &client, &pool).await?; + verify(&request, actor.as_ref())?; + + insert_activity(actor.user_id(), activity.clone(), false, &pool).await?; + + let any_base = activity.clone().into_any_base()?; + let kind = activity.kind().unwrap(); + match kind { + ValidTypes::Accept => receive_accept(any_base, username, &client, &pool).await, + ValidTypes::Create => { + receive_create_private_message(any_base, &client, &pool, chat_server).await } - UserAcceptedObjects::Update(u) => { - receive_update_private_message(*u, &request, &client, &db, chat_server).await + ValidTypes::Update => { + receive_update_private_message(any_base, &client, &pool, chat_server).await } - UserAcceptedObjects::Delete(d) => { - receive_delete_private_message(*d, &request, &client, &db, chat_server).await + ValidTypes::Delete => { + receive_delete_private_message(any_base, &client, &pool, chat_server).await } - UserAcceptedObjects::Undo(u) => { - receive_undo_delete_private_message(*u, &request, &client, &db, chat_server).await + ValidTypes::Undo => { + receive_undo_delete_private_message(any_base, &client, &pool, chat_server).await } } } /// Handle accepted follows. async fn receive_accept( - accept: Accept, - request: &HttpRequest, - username: &str, + activity: AnyBase, + username: String, client: &Client, pool: &DbPool, ) -> Result { + let accept = Accept::from_any_base(activity)?.unwrap(); let community_uri = accept.actor()?.to_owned().single_xsd_any_uri().unwrap(); let community = get_or_fetch_and_upsert_community(&community_uri, client, pool).await?; - verify(request, &community)?; - let username = username.to_owned(); let user = blocking(pool, move |conn| User_::read_from_name(conn, &username)).await??; - insert_activity(community.creator_id, accept, false, pool).await?; - // Now you need to add this to the community follower let community_follower_form = CommunityFollowerForm { community_id: community.id, @@ -107,20 +117,14 @@ async fn receive_accept( } async fn receive_create_private_message( - create: Create, - request: &HttpRequest, + activity: AnyBase, client: &Client, pool: &DbPool, chat_server: ChatServerParam, ) -> Result { - let user_uri = &create.actor()?.to_owned().single_xsd_any_uri().unwrap(); + let create = Create::from_any_base(activity)?.unwrap(); let note = Note::from_any_base(create.object().as_one().unwrap().to_owned())?.unwrap(); - let user = get_or_fetch_and_upsert_user(user_uri, client, pool).await?; - verify(request, &user)?; - - insert_activity(user.id, create, false, pool).await?; - let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?; let inserted_private_message = blocking(pool, move |conn| { @@ -148,20 +152,14 @@ async fn receive_create_private_message( } async fn receive_update_private_message( - update: Update, - request: &HttpRequest, + activity: AnyBase, client: &Client, pool: &DbPool, chat_server: ChatServerParam, ) -> Result { - let user_uri = &update.actor()?.to_owned().single_xsd_any_uri().unwrap(); + let update = Update::from_any_base(activity)?.unwrap(); let note = Note::from_any_base(update.object().as_one().unwrap().to_owned())?.unwrap(); - let user = get_or_fetch_and_upsert_user(&user_uri, client, pool).await?; - verify(request, &user)?; - - insert_activity(user.id, update, false, pool).await?; - let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?; let private_message_ap_id = private_message_form.ap_id.clone(); @@ -197,20 +195,14 @@ async fn receive_update_private_message( } async fn receive_delete_private_message( - delete: Delete, - request: &HttpRequest, + activity: AnyBase, client: &Client, pool: &DbPool, chat_server: ChatServerParam, ) -> Result { - let user_uri = &delete.actor()?.to_owned().single_xsd_any_uri().unwrap(); + let delete = Delete::from_any_base(activity)?.unwrap(); let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.unwrap(); - let user = get_or_fetch_and_upsert_user(&user_uri, client, pool).await?; - verify(request, &user)?; - - insert_activity(user.id, delete, false, pool).await?; - let private_message_form = PrivateMessageForm::from_apub(¬e, client, pool).await?; let private_message_ap_id = private_message_form.ap_id; @@ -258,20 +250,14 @@ async fn receive_delete_private_message( } async fn receive_undo_delete_private_message( - undo: Undo, - request: &HttpRequest, + activity: AnyBase, client: &Client, pool: &DbPool, chat_server: ChatServerParam, ) -> Result { + let undo = Undo::from_any_base(activity)?.unwrap(); let delete = Delete::from_any_base(undo.object().as_one().unwrap().to_owned())?.unwrap(); let note = Note::from_any_base(delete.object().as_one().unwrap().to_owned())?.unwrap(); - let user_uri = &delete.actor()?.to_owned().single_xsd_any_uri().unwrap(); - - let user = get_or_fetch_and_upsert_user(&user_uri, client, pool).await?; - verify(request, &user)?; - - insert_activity(user.id, delete, false, pool).await?; let private_message = PrivateMessageForm::from_apub(¬e, client, pool).await?; diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index e86032f61..7f39afc75 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -63,33 +63,34 @@ where } // Checks if the ID has a valid format, correct scheme, and is in the allowed instance list. -fn is_apub_id_valid(apub_id: &Url) -> bool { - debug!("Checking {}", apub_id); +fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> { if apub_id.scheme() != get_apub_protocol_string() { - debug!("invalid scheme: {:?}", apub_id.scheme()); - return false; + return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into()); } - let allowed_instances: Vec = Settings::get() + let mut allowed_instances: Vec = Settings::get() .federation .allowed_instances .split(',') .map(|d| d.to_string()) .collect(); + // need to allow this explicitly because apub activities might contain objects from our local + // instance. replace is needed to remove the port in our federation test setup. + let settings = Settings::get(); + let local_instance = settings.hostname.split(':').collect::>(); + allowed_instances.push(local_instance.first().unwrap().to_string()); + match apub_id.domain() { Some(d) => { let contains = allowed_instances.contains(&d.to_owned()); if !contains { - debug!("{} not in {:?}", d, allowed_instances); + return Err(anyhow!("{} not in federation allowlist", d).into()); } - contains - } - None => { - debug!("missing domain"); - false + Ok(()) } + None => Err(anyhow!("federation allowlist is empty").into()), } }