mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-22 19:01:32 +00:00
Merge branch 'main' of https://github.com/LemmyNet/lemmy into magic-filetypes
This commit is contained in:
commit
e33e72729c
35 changed files with 424 additions and 142 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2603,6 +2603,7 @@ dependencies = [
|
||||||
"moka",
|
"moka",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"reqwest 0.12.8",
|
"reqwest 0.12.8",
|
||||||
|
"semver",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_with",
|
"serde_with",
|
||||||
|
|
|
@ -156,7 +156,6 @@ test("Delete a comment", async () => {
|
||||||
commentRes.comment_view.comment.id,
|
commentRes.comment_view.comment.id,
|
||||||
);
|
);
|
||||||
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
|
||||||
expect(deleteCommentRes.comment_view.comment.content).toBe("");
|
|
||||||
|
|
||||||
// Make sure that comment is deleted on beta
|
// Make sure that comment is deleted on beta
|
||||||
await waitUntil(
|
await waitUntil(
|
||||||
|
@ -254,7 +253,6 @@ test("Remove a comment from admin and community on different instance", async ()
|
||||||
betaComment.comment.id,
|
betaComment.comment.id,
|
||||||
);
|
);
|
||||||
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
|
||||||
expect(removeCommentRes.comment_view.comment.content).toBe("");
|
|
||||||
|
|
||||||
// Comment text is also hidden from list
|
// Comment text is also hidden from list
|
||||||
let listComments = await getComments(
|
let listComments = await getComments(
|
||||||
|
@ -263,7 +261,6 @@ test("Remove a comment from admin and community on different instance", async ()
|
||||||
);
|
);
|
||||||
expect(listComments.comments.length).toBe(1);
|
expect(listComments.comments.length).toBe(1);
|
||||||
expect(listComments.comments[0].comment.removed).toBe(true);
|
expect(listComments.comments[0].comment.removed).toBe(true);
|
||||||
expect(listComments.comments[0].comment.content).toBe("");
|
|
||||||
|
|
||||||
// Make sure its not removed on alpha
|
// Make sure its not removed on alpha
|
||||||
let refetchedPostComments = await getComments(
|
let refetchedPostComments = await getComments(
|
||||||
|
|
|
@ -35,7 +35,7 @@ import {
|
||||||
userBlockInstance,
|
userBlockInstance,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import { AdminAllowInstanceParams } from "lemmy-js-client/dist/types/AdminAllowInstanceParams";
|
import { AdminAllowInstanceParams } from "lemmy-js-client/dist/types/AdminAllowInstanceParams";
|
||||||
import { EditCommunity, EditSite } from "lemmy-js-client";
|
import { EditCommunity, EditSite, GetPosts } from "lemmy-js-client";
|
||||||
|
|
||||||
beforeAll(setupLogins);
|
beforeAll(setupLogins);
|
||||||
afterAll(unfollows);
|
afterAll(unfollows);
|
||||||
|
@ -576,3 +576,29 @@ test("Remote mods can edit communities", async () => {
|
||||||
"Example description",
|
"Example description",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Community name with non-ascii chars", async () => {
|
||||||
|
const name = "това_ме_ядосва" + Math.random().toString().slice(2, 6);
|
||||||
|
let communityRes = await createCommunity(alpha, name);
|
||||||
|
|
||||||
|
let betaCommunity1 = await resolveCommunity(
|
||||||
|
beta,
|
||||||
|
communityRes.community_view.community.actor_id,
|
||||||
|
);
|
||||||
|
expect(betaCommunity1.community!.community.name).toBe(name);
|
||||||
|
|
||||||
|
let alphaCommunity2 = await getCommunityByName(alpha, name);
|
||||||
|
expect(alphaCommunity2.community_view.community.name).toBe(name);
|
||||||
|
|
||||||
|
let fediName = `${communityRes.community_view.community.name}@LEMMY-ALPHA:8541`;
|
||||||
|
let betaCommunity2 = await getCommunityByName(beta, fediName);
|
||||||
|
expect(betaCommunity2.community_view.community.name).toBe(name);
|
||||||
|
|
||||||
|
let postRes = await createPost(beta, betaCommunity1.community!.community.id);
|
||||||
|
|
||||||
|
let form: GetPosts = {
|
||||||
|
community_name: fediName,
|
||||||
|
};
|
||||||
|
let posts = await beta.getPosts(form);
|
||||||
|
expect(posts.posts[0].post.name).toBe(postRes.post_view.post.name);
|
||||||
|
});
|
||||||
|
|
|
@ -22,6 +22,7 @@ import {
|
||||||
alphaImage,
|
alphaImage,
|
||||||
unfollows,
|
unfollows,
|
||||||
saveUserSettingsBio,
|
saveUserSettingsBio,
|
||||||
|
getPersonDetails,
|
||||||
} from "./shared";
|
} from "./shared";
|
||||||
import {
|
import {
|
||||||
EditSite,
|
EditSite,
|
||||||
|
@ -136,11 +137,9 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create user with Arabic name", async () => {
|
test("Create user with Arabic name", async () => {
|
||||||
let user = await registerUser(
|
// less than actor_name_max_length
|
||||||
alpha,
|
const name = "تجريب" + Math.random().toString().slice(2, 10);
|
||||||
alphaUrl,
|
let user = await registerUser(alpha, alphaUrl, name);
|
||||||
"تجريب" + Math.random().toString().slice(2, 10), // less than actor_name_max_length
|
|
||||||
);
|
|
||||||
|
|
||||||
let site = await getSite(user);
|
let site = await getSite(user);
|
||||||
expect(site.my_user).toBeDefined();
|
expect(site.my_user).toBeDefined();
|
||||||
|
@ -149,8 +148,11 @@ test("Create user with Arabic name", async () => {
|
||||||
}
|
}
|
||||||
apShortname = `${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
apShortname = `${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
|
||||||
|
|
||||||
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
|
let betaPerson1 = (await resolvePerson(beta, apShortname)).person;
|
||||||
expect(alphaPerson).toBeDefined();
|
expect(betaPerson1!.person.name).toBe(name);
|
||||||
|
|
||||||
|
let betaPerson2 = await getPersonDetails(beta, betaPerson1!.person.id);
|
||||||
|
expect(betaPerson2!.person_view.person.name).toBe(name);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Create user with accept-language", async () => {
|
test("Create user with accept-language", async () => {
|
||||||
|
|
|
@ -25,6 +25,8 @@ pub struct CreateOAuthProvider {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +56,8 @@ pub struct EditOAuthProvider {
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,4 +86,6 @@ pub struct AuthenticateWithOauth {
|
||||||
/// An answer is mandatory if require application is enabled on the server
|
/// An answer is mandatory if require application is enabled on the server
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub answer: Option<String>,
|
pub answer: Option<String>,
|
||||||
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
|
pub pkce_code_verifier: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ anyhow.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
webmention = "0.6.0"
|
webmention = "0.6.0"
|
||||||
accept-language = "3.1.0"
|
accept-language = "3.1.0"
|
||||||
|
regex = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
|
|
|
@ -35,6 +35,7 @@ pub async fn create_oauth_provider(
|
||||||
scopes: data.scopes.to_string(),
|
scopes: data.scopes.to_string(),
|
||||||
auto_verify_email: data.auto_verify_email,
|
auto_verify_email: data.auto_verify_email,
|
||||||
account_linking_enabled: data.account_linking_enabled,
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
|
use_pkce: data.use_pkce,
|
||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
};
|
};
|
||||||
let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
|
let oauth_provider = OAuthProvider::create(&mut context.pool(), &oauth_provider_form).await?;
|
||||||
|
|
|
@ -33,6 +33,7 @@ pub async fn update_oauth_provider(
|
||||||
auto_verify_email: data.auto_verify_email,
|
auto_verify_email: data.auto_verify_email,
|
||||||
account_linking_enabled: data.account_linking_enabled,
|
account_linking_enabled: data.account_linking_enabled,
|
||||||
enabled: data.enabled,
|
enabled: data.enabled,
|
||||||
|
use_pkce: data.use_pkce,
|
||||||
updated: Some(Some(Utc::now())),
|
updated: Some(Some(Utc::now())),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,10 @@ use lemmy_utils::{
|
||||||
validation::is_valid_actor_name,
|
validation::is_valid_actor_name,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use std::collections::HashSet;
|
use std::{collections::HashSet, sync::LazyLock};
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
@ -225,6 +226,11 @@ pub async fn authenticate_with_oauth(
|
||||||
Err(LemmyErrorType::OauthAuthorizationInvalid)?
|
Err(LemmyErrorType::OauthAuthorizationInvalid)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validate the PKCE challenge
|
||||||
|
if let Some(code_verifier) = &data.pkce_code_verifier {
|
||||||
|
check_code_verifier(code_verifier)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the OAUTH provider and make sure it's enabled
|
// Fetch the OAUTH provider and make sure it's enabled
|
||||||
let oauth_provider_id = data.oauth_provider_id;
|
let oauth_provider_id = data.oauth_provider_id;
|
||||||
let oauth_provider = OAuthProvider::read(&mut context.pool(), oauth_provider_id)
|
let oauth_provider = OAuthProvider::read(&mut context.pool(), oauth_provider_id)
|
||||||
|
@ -236,9 +242,14 @@ pub async fn authenticate_with_oauth(
|
||||||
return Err(LemmyErrorType::OauthAuthorizationInvalid)?;
|
return Err(LemmyErrorType::OauthAuthorizationInvalid)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let token_response =
|
let token_response = oauth_request_access_token(
|
||||||
oauth_request_access_token(&context, &oauth_provider, &data.code, redirect_uri.as_str())
|
&context,
|
||||||
.await?;
|
&oauth_provider,
|
||||||
|
&data.code,
|
||||||
|
data.pkce_code_verifier.as_deref(),
|
||||||
|
redirect_uri.as_str(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let user_info = oidc_get_user_info(
|
let user_info = oidc_get_user_info(
|
||||||
&context,
|
&context,
|
||||||
|
@ -533,20 +544,27 @@ async fn oauth_request_access_token(
|
||||||
context: &Data<LemmyContext>,
|
context: &Data<LemmyContext>,
|
||||||
oauth_provider: &OAuthProvider,
|
oauth_provider: &OAuthProvider,
|
||||||
code: &str,
|
code: &str,
|
||||||
|
pkce_code_verifier: Option<&str>,
|
||||||
redirect_uri: &str,
|
redirect_uri: &str,
|
||||||
) -> LemmyResult<TokenResponse> {
|
) -> LemmyResult<TokenResponse> {
|
||||||
|
let mut form = vec![
|
||||||
|
("client_id", &*oauth_provider.client_id),
|
||||||
|
("client_secret", &*oauth_provider.client_secret),
|
||||||
|
("code", code),
|
||||||
|
("grant_type", "authorization_code"),
|
||||||
|
("redirect_uri", redirect_uri),
|
||||||
|
];
|
||||||
|
|
||||||
|
if let Some(code_verifier) = pkce_code_verifier {
|
||||||
|
form.push(("code_verifier", code_verifier));
|
||||||
|
}
|
||||||
|
|
||||||
// Request an Access Token from the OAUTH provider
|
// Request an Access Token from the OAUTH provider
|
||||||
let response = context
|
let response = context
|
||||||
.client()
|
.client()
|
||||||
.post(oauth_provider.token_endpoint.as_str())
|
.post(oauth_provider.token_endpoint.as_str())
|
||||||
.header("Accept", "application/json")
|
.header("Accept", "application/json")
|
||||||
.form(&[
|
.form(&form[..])
|
||||||
("grant_type", "authorization_code"),
|
|
||||||
("code", code),
|
|
||||||
("redirect_uri", redirect_uri),
|
|
||||||
("client_id", &oauth_provider.client_id),
|
|
||||||
("client_secret", &oauth_provider.client_secret),
|
|
||||||
])
|
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::OauthLoginFailed)?
|
.with_lemmy_type(LemmyErrorType::OauthLoginFailed)?
|
||||||
|
@ -596,3 +614,17 @@ fn read_user_info(user_info: &serde_json::Value, key: &str) -> LemmyResult<Strin
|
||||||
}
|
}
|
||||||
Err(LemmyErrorType::OauthLoginFailed)?
|
Err(LemmyErrorType::OauthLoginFailed)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::expect_used)]
|
||||||
|
fn check_code_verifier(code_verifier: &str) -> LemmyResult<()> {
|
||||||
|
static VALID_CODE_VERIFIER_REGEX: LazyLock<Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9\-._~]{43,128}$").expect("compile regex"));
|
||||||
|
|
||||||
|
let check = VALID_CODE_VERIFIER_REGEX.is_match(code_verifier);
|
||||||
|
|
||||||
|
if check {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(LemmyErrorType::InvalidCodeVerifier.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ html2md = "0.2.14"
|
||||||
html2text = "0.12.6"
|
html2text = "0.12.6"
|
||||||
stringreader = "0.1.1"
|
stringreader = "0.1.1"
|
||||||
enum_delegate = "0.2.0"
|
enum_delegate = "0.2.0"
|
||||||
|
semver = "1.0.23"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serial_test = { workspace = true }
|
serial_test = { workspace = true }
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
|
"actor": "http://enterprise.lemmy.ml/u/lemmy_beta",
|
||||||
"to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
|
"to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
|
||||||
"object": {
|
"object": {
|
||||||
"type": "ChatMessage",
|
"type": "Note",
|
||||||
"id": "http://enterprise.lemmy.ml/private_message/1",
|
"id": "http://enterprise.lemmy.ml/private_message/1",
|
||||||
"attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
|
"attributedTo": "http://enterprise.lemmy.ml/u/lemmy_beta",
|
||||||
"to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
|
"to": ["http://ds9.lemmy.ml/u/lemmy_alpha"],
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"id": "https://enterprise.lemmy.ml/private_message/1621",
|
"id": "https://enterprise.lemmy.ml/private_message/1621",
|
||||||
"type": "ChatMessage",
|
"type": "Note",
|
||||||
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
|
"attributedTo": "https://enterprise.lemmy.ml/u/picard",
|
||||||
"to": ["https://queer.hacktivis.me/users/lanodan"],
|
"to": ["https://queer.hacktivis.me/users/lanodan"],
|
||||||
"content": "<p>Hello hello, testing</p>\n",
|
"content": "<p>Hello hello, testing</p>\n",
|
49
crates/apub/assets/mastodon/activities/private_message.json
Normal file
49
crates/apub/assets/mastodon/activities/private_message.json
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"@context": [
|
||||||
|
"https://www.w3.org/ns/activitystreams",
|
||||||
|
{
|
||||||
|
"ostatus": "http://ostatus.org#",
|
||||||
|
"atomUri": "ostatus:atomUri",
|
||||||
|
"inReplyToAtomUri": "ostatus:inReplyToAtomUri",
|
||||||
|
"conversation": "ostatus:conversation",
|
||||||
|
"sensitive": "as:sensitive",
|
||||||
|
"toot": "http://joinmastodon.org/ns#",
|
||||||
|
"votersCount": "toot:votersCount"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "https://mastodon.world/users/nutomic/statuses/110854468010322301",
|
||||||
|
"type": "Note",
|
||||||
|
"summary": null,
|
||||||
|
"inReplyTo": "https://mastodon.world/users/nutomic/statuses/110854464248188528",
|
||||||
|
"published": "2023-08-08T14:29:04Z",
|
||||||
|
"url": "https://mastodon.world/@nutomic/110854468010322301",
|
||||||
|
"attributedTo": "https://mastodon.world/users/nutomic",
|
||||||
|
"to": ["https://ds9.lemmy.ml/u/nutomic"],
|
||||||
|
"cc": [],
|
||||||
|
"sensitive": false,
|
||||||
|
"atomUri": "https://mastodon.world/users/nutomic/statuses/110854468010322301",
|
||||||
|
"inReplyToAtomUri": "https://mastodon.world/users/nutomic/statuses/110854464248188528",
|
||||||
|
"conversation": "tag:mastodon.world,2023-08-08:objectId=121377096:objectType=Conversation",
|
||||||
|
"content": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://ds9.lemmy.ml/u/nutomic\" class=\"u-url mention\">@<span>nutomic@ds9.lemmy.ml</span></a></span> 444</p>",
|
||||||
|
"contentMap": {
|
||||||
|
"es": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://ds9.lemmy.ml/u/nutomic\" class=\"u-url mention\">@<span>nutomic@ds9.lemmy.ml</span></a></span> 444</p>"
|
||||||
|
},
|
||||||
|
"attachment": [],
|
||||||
|
"tag": [
|
||||||
|
{
|
||||||
|
"type": "Mention",
|
||||||
|
"href": "https://ds9.lemmy.ml/u/nutomic",
|
||||||
|
"name": "@nutomic@ds9.lemmy.ml"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replies": {
|
||||||
|
"id": "https://mastodon.world/users/nutomic/statuses/110854468010322301/replies",
|
||||||
|
"type": "Collection",
|
||||||
|
"first": {
|
||||||
|
"type": "CollectionPage",
|
||||||
|
"next": "https://mastodon.world/users/nutomic/statuses/110854468010322301/replies?only_other_accounts=true&page=true",
|
||||||
|
"partOf": "https://mastodon.world/users/nutomic/statuses/110854468010322301/replies",
|
||||||
|
"items": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,16 +64,17 @@ impl ActivityHandler for RawAnnouncableActivities {
|
||||||
|
|
||||||
// verify and receive activity
|
// verify and receive activity
|
||||||
activity.verify(context).await?;
|
activity.verify(context).await?;
|
||||||
activity.clone().receive(context).await?;
|
let actor_id = activity.actor().clone().into();
|
||||||
|
activity.receive(context).await?;
|
||||||
|
|
||||||
// if community is local, send activity to followers
|
// if community is local, send activity to followers
|
||||||
if let Some(community) = community {
|
if let Some(community) = community {
|
||||||
if community.local {
|
if community.local {
|
||||||
let actor_id = activity.actor().clone().into();
|
|
||||||
verify_person_in_community(&actor_id, &community, context).await?;
|
verify_person_in_community(&actor_id, &community, context).await?;
|
||||||
AnnounceActivity::send(self, &community, context).await?;
|
AnnounceActivity::send(self, &community, context).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ use lemmy_utils::{
|
||||||
error::{LemmyError, LemmyResult},
|
error::{LemmyError, LemmyResult},
|
||||||
utils::mention::scrape_text_for_mentions,
|
utils::mention::scrape_text_for_mentions,
|
||||||
};
|
};
|
||||||
|
use serde_json::{from_value, to_value};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
impl CreateOrUpdateNote {
|
impl CreateOrUpdateNote {
|
||||||
|
@ -98,7 +99,11 @@ impl CreateOrUpdateNote {
|
||||||
inboxes.add_inbox(person.shared_inbox_or_inbox());
|
inboxes.add_inbox(person.shared_inbox_or_inbox());
|
||||||
}
|
}
|
||||||
|
|
||||||
let activity = AnnouncableActivities::CreateOrUpdateComment(create_or_update);
|
// AnnouncableActivities doesnt contain Comment activity but only NoteWrapper,
|
||||||
|
// to be able to handle both comment and private message. So to send this out we need
|
||||||
|
// to convert this to NoteWrapper, by serializing and then deserializing again.
|
||||||
|
let converted = from_value(to_value(create_or_update)?)?;
|
||||||
|
let activity = AnnouncableActivities::CreateOrUpdateNoteWrapper(converted);
|
||||||
send_activity_in_community(activity, &person, &community, inboxes, false, &context).await
|
send_activity_in_community(activity, &person, &community, inboxes, false, &context).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
|
pub(crate) mod note_wrapper;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
|
|
74
crates/apub/src/activities/create_or_update/note_wrapper.rs
Normal file
74
crates/apub/src/activities/create_or_update/note_wrapper.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::{
|
||||||
|
objects::community::ApubCommunity,
|
||||||
|
protocol::{
|
||||||
|
activities::create_or_update::{
|
||||||
|
note::CreateOrUpdateNote,
|
||||||
|
note_wrapper::CreateOrUpdateNoteWrapper,
|
||||||
|
private_message::CreateOrUpdatePrivateMessage,
|
||||||
|
},
|
||||||
|
InCommunity,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use activitypub_federation::{config::Data, traits::ActivityHandler};
|
||||||
|
use lemmy_api_common::context::LemmyContext;
|
||||||
|
use lemmy_utils::error::{LemmyError, LemmyResult};
|
||||||
|
use serde_json::{from_value, to_value};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
/// In Activitypub, both private messages and comments are represented by `type: Note` which
|
||||||
|
/// makes it difficult to distinguish them. This wrapper handles receiving of both types, and
|
||||||
|
/// routes them to the correct handler.
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl ActivityHandler for CreateOrUpdateNoteWrapper {
|
||||||
|
type DataType = LemmyContext;
|
||||||
|
type Error = LemmyError;
|
||||||
|
|
||||||
|
fn id(&self) -> &Url {
|
||||||
|
&self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actor(&self) -> &Url {
|
||||||
|
&self.actor
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn verify(&self, _context: &Data<Self::DataType>) -> LemmyResult<()> {
|
||||||
|
// Do everything in receive to avoid extra checks.
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
async fn receive(self, context: &Data<Self::DataType>) -> LemmyResult<()> {
|
||||||
|
// Use serde to convert NoteWrapper either into Comment or PrivateMessage,
|
||||||
|
// depending on conditions below. This works because NoteWrapper keeps all
|
||||||
|
// additional data in field `other: Map<String, Value>`.
|
||||||
|
let val = to_value(self)?;
|
||||||
|
|
||||||
|
// Convert self to a comment and get the community. If the conversion is
|
||||||
|
// successful and a community is returned, this is a comment.
|
||||||
|
let comment = from_value::<CreateOrUpdateNote>(val.clone());
|
||||||
|
if let Ok(comment) = comment {
|
||||||
|
if comment.community(context).await.is_ok() {
|
||||||
|
CreateOrUpdateNote::verify(&comment, context).await?;
|
||||||
|
CreateOrUpdateNote::receive(comment, context).await?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the previous checks failed, we are dealing with a private message.
|
||||||
|
let private_message = from_value(val)?;
|
||||||
|
CreateOrUpdatePrivateMessage::verify(&private_message, context).await?;
|
||||||
|
CreateOrUpdatePrivateMessage::receive(private_message, context).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl InCommunity for CreateOrUpdateNoteWrapper {
|
||||||
|
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
|
||||||
|
// Same logic as in receive. In case this is a private message, an error is returned.
|
||||||
|
let val = to_value(self)?;
|
||||||
|
let comment: CreateOrUpdateNote = from_value(val.clone())?;
|
||||||
|
comment.community(context).await
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use crate::{
|
||||||
insert_received_activity,
|
insert_received_activity,
|
||||||
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
|
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
|
||||||
protocol::activities::{
|
protocol::activities::{
|
||||||
create_or_update::chat_message::CreateOrUpdateChatMessage,
|
create_or_update::private_message::CreateOrUpdatePrivateMessage,
|
||||||
CreateOrUpdateType,
|
CreateOrUpdateType,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -30,7 +30,7 @@ pub(crate) async fn send_create_or_update_pm(
|
||||||
kind.clone(),
|
kind.clone(),
|
||||||
&context.settings().get_protocol_and_hostname(),
|
&context.settings().get_protocol_and_hostname(),
|
||||||
)?;
|
)?;
|
||||||
let create_or_update = CreateOrUpdateChatMessage {
|
let create_or_update = CreateOrUpdatePrivateMessage {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
actor: actor.id().into(),
|
actor: actor.id().into(),
|
||||||
to: [recipient.id().into()],
|
to: [recipient.id().into()],
|
||||||
|
@ -44,7 +44,7 @@ pub(crate) async fn send_create_or_update_pm(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl ActivityHandler for CreateOrUpdateChatMessage {
|
impl ActivityHandler for CreateOrUpdatePrivateMessage {
|
||||||
type DataType = LemmyContext;
|
type DataType = LemmyContext;
|
||||||
type Error = LemmyError;
|
type Error = LemmyError;
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ use lemmy_db_schema::{
|
||||||
community::{Community, CommunityUpdateForm},
|
community::{Community, CommunityUpdateForm},
|
||||||
person::Person,
|
person::Person,
|
||||||
post::{Post, PostUpdateForm},
|
post::{Post, PostUpdateForm},
|
||||||
private_message::{PrivateMessage, PrivateMessageUpdateForm},
|
private_message::{PrivateMessage as DbPrivateMessage, PrivateMessageUpdateForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
@ -82,7 +82,7 @@ pub(crate) async fn send_apub_delete_in_community(
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
pub(crate) async fn send_apub_delete_private_message(
|
pub(crate) async fn send_apub_delete_private_message(
|
||||||
actor: &ApubPerson,
|
actor: &ApubPerson,
|
||||||
pm: PrivateMessage,
|
pm: DbPrivateMessage,
|
||||||
deleted: bool,
|
deleted: bool,
|
||||||
context: Data<LemmyContext>,
|
context: Data<LemmyContext>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
|
@ -298,7 +298,7 @@ async fn receive_delete_action(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DeletableObjects::PrivateMessage(pm) => {
|
DeletableObjects::PrivateMessage(pm) => {
|
||||||
PrivateMessage::update(
|
DbPrivateMessage::update(
|
||||||
&mut context.pool(),
|
&mut context.pool(),
|
||||||
pm.id,
|
pm.id,
|
||||||
&PrivateMessageUpdateForm {
|
&PrivateMessageUpdateForm {
|
||||||
|
|
|
@ -11,11 +11,7 @@ use crate::{
|
||||||
report::Report,
|
report::Report,
|
||||||
update::UpdateCommunity,
|
update::UpdateCommunity,
|
||||||
},
|
},
|
||||||
create_or_update::{
|
create_or_update::{note_wrapper::CreateOrUpdateNoteWrapper, page::CreateOrUpdatePage},
|
||||||
chat_message::CreateOrUpdateChatMessage,
|
|
||||||
note::CreateOrUpdateNote,
|
|
||||||
page::CreateOrUpdatePage,
|
|
||||||
},
|
|
||||||
deletion::{delete::Delete, undo_delete::UndoDelete},
|
deletion::{delete::Delete, undo_delete::UndoDelete},
|
||||||
following::{
|
following::{
|
||||||
accept::AcceptFollow,
|
accept::AcceptFollow,
|
||||||
|
@ -48,47 +44,17 @@ pub enum SharedInboxActivities {
|
||||||
AcceptFollow(AcceptFollow),
|
AcceptFollow(AcceptFollow),
|
||||||
RejectFollow(RejectFollow),
|
RejectFollow(RejectFollow),
|
||||||
UndoFollow(UndoFollow),
|
UndoFollow(UndoFollow),
|
||||||
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
|
||||||
Report(Report),
|
Report(Report),
|
||||||
AnnounceActivity(AnnounceActivity),
|
AnnounceActivity(AnnounceActivity),
|
||||||
/// This is a catch-all and needs to be last
|
/// This is a catch-all and needs to be last
|
||||||
RawAnnouncableActivities(RawAnnouncableActivities),
|
RawAnnouncableActivities(RawAnnouncableActivities),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List of activities which the group inbox can handle.
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
#[enum_delegate::implement(ActivityHandler)]
|
|
||||||
pub enum GroupInboxActivities {
|
|
||||||
Follow(Follow),
|
|
||||||
UndoFollow(UndoFollow),
|
|
||||||
Report(Report),
|
|
||||||
/// This is a catch-all and needs to be last
|
|
||||||
AnnouncableActivities(RawAnnouncableActivities),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of activities which the person inbox can handle.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
#[enum_delegate::implement(ActivityHandler)]
|
|
||||||
pub enum PersonInboxActivities {
|
|
||||||
Follow(Follow),
|
|
||||||
AcceptFollow(AcceptFollow),
|
|
||||||
RejectFollow(RejectFollow),
|
|
||||||
UndoFollow(UndoFollow),
|
|
||||||
CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage),
|
|
||||||
Delete(Delete),
|
|
||||||
UndoDelete(UndoDelete),
|
|
||||||
AnnounceActivity(AnnounceActivity),
|
|
||||||
/// User can also receive some "announcable" activities, eg a comment mention.
|
|
||||||
AnnouncableActivities(AnnouncableActivities),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
#[enum_delegate::implement(ActivityHandler)]
|
#[enum_delegate::implement(ActivityHandler)]
|
||||||
pub enum AnnouncableActivities {
|
pub enum AnnouncableActivities {
|
||||||
CreateOrUpdateComment(CreateOrUpdateNote),
|
CreateOrUpdateNoteWrapper(CreateOrUpdateNoteWrapper),
|
||||||
CreateOrUpdatePost(CreateOrUpdatePage),
|
CreateOrUpdatePost(CreateOrUpdatePage),
|
||||||
Vote(Vote),
|
Vote(Vote),
|
||||||
UndoVote(UndoVote),
|
UndoVote(UndoVote),
|
||||||
|
@ -111,7 +77,7 @@ impl InCommunity for AnnouncableActivities {
|
||||||
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
|
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
|
||||||
use AnnouncableActivities::*;
|
use AnnouncableActivities::*;
|
||||||
match self {
|
match self {
|
||||||
CreateOrUpdateComment(a) => a.community(context).await,
|
CreateOrUpdateNoteWrapper(a) => a.community(context).await,
|
||||||
CreateOrUpdatePost(a) => a.community(context).await,
|
CreateOrUpdatePost(a) => a.community(context).await,
|
||||||
Vote(a) => a.community(context).await,
|
Vote(a) => a.community(context).await,
|
||||||
UndoVote(a) => a.community(context).await,
|
UndoVote(a) => a.community(context).await,
|
||||||
|
@ -133,40 +99,32 @@ impl InCommunity for AnnouncableActivities {
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
activity_lists::{GroupInboxActivities, PersonInboxActivities, SharedInboxActivities},
|
activity_lists::SharedInboxActivities,
|
||||||
protocol::tests::{test_json, test_parse_lemmy_item},
|
protocol::tests::{test_json, test_parse_lemmy_item},
|
||||||
};
|
};
|
||||||
use lemmy_utils::error::LemmyResult;
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_group_inbox() -> LemmyResult<()> {
|
|
||||||
test_parse_lemmy_item::<GroupInboxActivities>("assets/lemmy/activities/following/follow.json")?;
|
|
||||||
test_parse_lemmy_item::<GroupInboxActivities>(
|
|
||||||
"assets/lemmy/activities/create_or_update/create_note.json",
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_person_inbox() -> LemmyResult<()> {
|
|
||||||
test_parse_lemmy_item::<PersonInboxActivities>(
|
|
||||||
"assets/lemmy/activities/following/accept.json",
|
|
||||||
)?;
|
|
||||||
test_parse_lemmy_item::<PersonInboxActivities>(
|
|
||||||
"assets/lemmy/activities/create_or_update/create_note.json",
|
|
||||||
)?;
|
|
||||||
test_parse_lemmy_item::<PersonInboxActivities>(
|
|
||||||
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
|
||||||
)?;
|
|
||||||
test_json::<PersonInboxActivities>("assets/mastodon/activities/follow.json")?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_shared_inbox() -> LemmyResult<()> {
|
fn test_shared_inbox() -> LemmyResult<()> {
|
||||||
test_parse_lemmy_item::<SharedInboxActivities>(
|
test_parse_lemmy_item::<SharedInboxActivities>(
|
||||||
"assets/lemmy/activities/deletion/delete_user.json",
|
"assets/lemmy/activities/deletion/delete_user.json",
|
||||||
)?;
|
)?;
|
||||||
|
test_parse_lemmy_item::<SharedInboxActivities>(
|
||||||
|
"assets/lemmy/activities/following/accept.json",
|
||||||
|
)?;
|
||||||
|
test_parse_lemmy_item::<SharedInboxActivities>(
|
||||||
|
"assets/lemmy/activities/create_or_update/create_comment.json",
|
||||||
|
)?;
|
||||||
|
test_parse_lemmy_item::<SharedInboxActivities>(
|
||||||
|
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
||||||
|
)?;
|
||||||
|
test_parse_lemmy_item::<SharedInboxActivities>(
|
||||||
|
"assets/lemmy/activities/following/follow.json",
|
||||||
|
)?;
|
||||||
|
test_parse_lemmy_item::<SharedInboxActivities>(
|
||||||
|
"assets/lemmy/activities/create_or_update/create_comment.json",
|
||||||
|
)?;
|
||||||
|
test_json::<SharedInboxActivities>("assets/mastodon/activities/follow.json")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,7 +266,7 @@ pub(crate) mod tests {
|
||||||
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741")?;
|
let url = Url::parse("https://enterprise.lemmy.ml/comment/38741")?;
|
||||||
let data = prepare_comment_test(&url, &context).await?;
|
let data = prepare_comment_test(&url, &context).await?;
|
||||||
|
|
||||||
let json: Note = file_to_json_object("assets/lemmy/objects/note.json")?;
|
let json: Note = file_to_json_object("assets/lemmy/objects/comment.json")?;
|
||||||
ApubComment::verify(&json, &url, &context).await?;
|
ApubComment::verify(&json, &url, &context).await?;
|
||||||
let comment = ApubComment::from_json(json.clone(), &context).await?;
|
let comment = ApubComment::from_json(json.clone(), &context).await?;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::{
|
||||||
fetcher::markdown_links::markdown_rewrite_remote_links,
|
fetcher::markdown_links::markdown_rewrite_remote_links,
|
||||||
objects::read_from_string_or_source,
|
objects::read_from_string_or_source,
|
||||||
protocol::{
|
protocol::{
|
||||||
objects::chat_message::{ChatMessage, ChatMessageType},
|
objects::private_message::{PrivateMessage, PrivateMessageType},
|
||||||
Source,
|
Source,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -25,10 +25,11 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
instance::Instance,
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
person::Person,
|
person::Person,
|
||||||
person_block::PersonBlock,
|
person_block::PersonBlock,
|
||||||
private_message::{PrivateMessage, PrivateMessageInsertForm},
|
private_message::{PrivateMessage as DbPrivateMessage, PrivateMessageInsertForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
@ -37,21 +38,22 @@ use lemmy_utils::{
|
||||||
error::{FederationError, LemmyError, LemmyErrorType, LemmyResult},
|
error::{FederationError, LemmyError, LemmyErrorType, LemmyResult},
|
||||||
utils::markdown::markdown_to_html,
|
utils::markdown::markdown_to_html,
|
||||||
};
|
};
|
||||||
|
use semver::{Version, VersionReq};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ApubPrivateMessage(pub(crate) PrivateMessage);
|
pub struct ApubPrivateMessage(pub(crate) DbPrivateMessage);
|
||||||
|
|
||||||
impl Deref for ApubPrivateMessage {
|
impl Deref for ApubPrivateMessage {
|
||||||
type Target = PrivateMessage;
|
type Target = DbPrivateMessage;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<PrivateMessage> for ApubPrivateMessage {
|
impl From<DbPrivateMessage> for ApubPrivateMessage {
|
||||||
fn from(pm: PrivateMessage) -> Self {
|
fn from(pm: DbPrivateMessage) -> Self {
|
||||||
ApubPrivateMessage(pm)
|
ApubPrivateMessage(pm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +61,7 @@ impl From<PrivateMessage> for ApubPrivateMessage {
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
impl Object for ApubPrivateMessage {
|
impl Object for ApubPrivateMessage {
|
||||||
type DataType = LemmyContext;
|
type DataType = LemmyContext;
|
||||||
type Kind = ChatMessage;
|
type Kind = PrivateMessage;
|
||||||
type Error = LemmyError;
|
type Error = LemmyError;
|
||||||
|
|
||||||
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
|
||||||
|
@ -72,7 +74,7 @@ impl Object for ApubPrivateMessage {
|
||||||
context: &Data<Self::DataType>,
|
context: &Data<Self::DataType>,
|
||||||
) -> LemmyResult<Option<Self>> {
|
) -> LemmyResult<Option<Self>> {
|
||||||
Ok(
|
Ok(
|
||||||
PrivateMessage::read_from_apub_id(&mut context.pool(), object_id)
|
DbPrivateMessage::read_from_apub_id(&mut context.pool(), object_id)
|
||||||
.await?
|
.await?
|
||||||
.map(Into::into),
|
.map(Into::into),
|
||||||
)
|
)
|
||||||
|
@ -84,15 +86,26 @@ impl Object for ApubPrivateMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<ChatMessage> {
|
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<PrivateMessage> {
|
||||||
let creator_id = self.creator_id;
|
let creator_id = self.creator_id;
|
||||||
let creator = Person::read(&mut context.pool(), creator_id).await?;
|
let creator = Person::read(&mut context.pool(), creator_id).await?;
|
||||||
|
|
||||||
let recipient_id = self.recipient_id;
|
let recipient_id = self.recipient_id;
|
||||||
let recipient = Person::read(&mut context.pool(), recipient_id).await?;
|
let recipient = Person::read(&mut context.pool(), recipient_id).await?;
|
||||||
|
|
||||||
let note = ChatMessage {
|
let instance = Instance::read(&mut context.pool(), recipient.instance_id).await?;
|
||||||
r#type: ChatMessageType::ChatMessage,
|
let mut kind = PrivateMessageType::Note;
|
||||||
|
|
||||||
|
// Deprecated: For Lemmy versions before 0.20, send private messages with old type
|
||||||
|
if let (Some(software), Some(version)) = (instance.software, &instance.version) {
|
||||||
|
let req = VersionReq::parse("<0.20")?;
|
||||||
|
if software == "lemmy" && req.matches(&Version::parse(version)?) {
|
||||||
|
kind = PrivateMessageType::ChatMessage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let note = PrivateMessage {
|
||||||
|
kind,
|
||||||
id: self.ap_id.clone().into(),
|
id: self.ap_id.clone().into(),
|
||||||
attributed_to: creator.actor_id.into(),
|
attributed_to: creator.actor_id.into(),
|
||||||
to: [recipient.actor_id.into()],
|
to: [recipient.actor_id.into()],
|
||||||
|
@ -107,7 +120,7 @@ impl Object for ApubPrivateMessage {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn verify(
|
async fn verify(
|
||||||
note: &ChatMessage,
|
note: &PrivateMessage,
|
||||||
expected_domain: &Url,
|
expected_domain: &Url,
|
||||||
context: &Data<Self::DataType>,
|
context: &Data<Self::DataType>,
|
||||||
) -> LemmyResult<()> {
|
) -> LemmyResult<()> {
|
||||||
|
@ -128,7 +141,7 @@ impl Object for ApubPrivateMessage {
|
||||||
|
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
async fn from_json(
|
async fn from_json(
|
||||||
note: ChatMessage,
|
note: PrivateMessage,
|
||||||
context: &Data<Self::DataType>,
|
context: &Data<Self::DataType>,
|
||||||
) -> LemmyResult<ApubPrivateMessage> {
|
) -> LemmyResult<ApubPrivateMessage> {
|
||||||
let creator = note.attributed_to.dereference(context).await?;
|
let creator = note.attributed_to.dereference(context).await?;
|
||||||
|
@ -161,7 +174,7 @@ impl Object for ApubPrivateMessage {
|
||||||
local: Some(false),
|
local: Some(false),
|
||||||
};
|
};
|
||||||
let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now);
|
let timestamp = note.updated.or(note.published).unwrap_or_else(Utc::now);
|
||||||
let pm = PrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
|
let pm = DbPrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
|
||||||
Ok(pm.into())
|
Ok(pm.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -213,7 +226,7 @@ mod tests {
|
||||||
let context = LemmyContext::init_test_context().await;
|
let context = LemmyContext::init_test_context().await;
|
||||||
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621")?;
|
let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621")?;
|
||||||
let data = prepare_comment_test(&url, &context).await?;
|
let data = prepare_comment_test(&url, &context).await?;
|
||||||
let json: ChatMessage = file_to_json_object("assets/lemmy/objects/chat_message.json")?;
|
let json: PrivateMessage = file_to_json_object("assets/lemmy/objects/private_message.json")?;
|
||||||
ApubPrivateMessage::verify(&json, &url, &context).await?;
|
ApubPrivateMessage::verify(&json, &url, &context).await?;
|
||||||
let pm = ApubPrivateMessage::from_json(json.clone(), &context).await?;
|
let pm = ApubPrivateMessage::from_json(json.clone(), &context).await?;
|
||||||
|
|
||||||
|
@ -225,7 +238,7 @@ mod tests {
|
||||||
let to_apub = pm.into_json(&context).await?;
|
let to_apub = pm.into_json(&context).await?;
|
||||||
assert_json_include!(actual: json, expected: to_apub);
|
assert_json_include!(actual: json, expected: to_apub);
|
||||||
|
|
||||||
PrivateMessage::delete(&mut context.pool(), pm_id).await?;
|
DbPrivateMessage::delete(&mut context.pool(), pm_id).await?;
|
||||||
cleanup(data, &context).await?;
|
cleanup(data, &context).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -245,7 +258,7 @@ mod tests {
|
||||||
assert_eq!(pm.content.len(), 3);
|
assert_eq!(pm.content.len(), 3);
|
||||||
assert_eq!(context.request_count(), 0);
|
assert_eq!(context.request_count(), 0);
|
||||||
|
|
||||||
PrivateMessage::delete(&mut context.pool(), pm.id).await?;
|
DbPrivateMessage::delete(&mut context.pool(), pm.id).await?;
|
||||||
cleanup(data, &context).await?;
|
cleanup(data, &context).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
pub mod chat_message;
|
|
||||||
pub mod note;
|
pub mod note;
|
||||||
|
pub(crate) mod note_wrapper;
|
||||||
pub mod page;
|
pub mod page;
|
||||||
|
pub mod private_message;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::note_wrapper::CreateOrUpdateNoteWrapper;
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
activities::create_or_update::{
|
activities::create_or_update::{
|
||||||
chat_message::CreateOrUpdateChatMessage,
|
|
||||||
note::CreateOrUpdateNote,
|
note::CreateOrUpdateNote,
|
||||||
page::CreateOrUpdatePage,
|
page::CreateOrUpdatePage,
|
||||||
|
private_message::CreateOrUpdatePrivateMessage,
|
||||||
},
|
},
|
||||||
tests::test_parse_lemmy_item,
|
tests::test_parse_lemmy_item,
|
||||||
};
|
};
|
||||||
|
@ -23,9 +25,15 @@ mod tests {
|
||||||
"assets/lemmy/activities/create_or_update/update_page.json",
|
"assets/lemmy/activities/create_or_update/update_page.json",
|
||||||
)?;
|
)?;
|
||||||
test_parse_lemmy_item::<CreateOrUpdateNote>(
|
test_parse_lemmy_item::<CreateOrUpdateNote>(
|
||||||
"assets/lemmy/activities/create_or_update/create_note.json",
|
"assets/lemmy/activities/create_or_update/create_comment.json",
|
||||||
)?;
|
)?;
|
||||||
test_parse_lemmy_item::<CreateOrUpdateChatMessage>(
|
test_parse_lemmy_item::<CreateOrUpdatePrivateMessage>(
|
||||||
|
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
||||||
|
)?;
|
||||||
|
test_parse_lemmy_item::<CreateOrUpdateNoteWrapper>(
|
||||||
|
"assets/lemmy/activities/create_or_update/create_comment.json",
|
||||||
|
)?;
|
||||||
|
test_parse_lemmy_item::<CreateOrUpdateNoteWrapper>(
|
||||||
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
"assets/lemmy/activities/create_or_update/create_private_message.json",
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
use activitypub_federation::kinds::object::NoteType;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{Map, Value};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CreateOrUpdateNoteWrapper {
|
||||||
|
pub(crate) object: NoteWrapper,
|
||||||
|
pub(crate) id: Url,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) to: Vec<Url>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) cc: Vec<Url>,
|
||||||
|
pub(crate) actor: Url,
|
||||||
|
#[serde(flatten)]
|
||||||
|
other: Map<String, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub(crate) struct NoteWrapper {
|
||||||
|
pub(crate) r#type: NoteType,
|
||||||
|
#[serde(flatten)]
|
||||||
|
other: Map<String, Value>,
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
objects::person::ApubPerson,
|
objects::person::ApubPerson,
|
||||||
protocol::{activities::CreateOrUpdateType, objects::chat_message::ChatMessage},
|
protocol::{activities::CreateOrUpdateType, objects::private_message::PrivateMessage},
|
||||||
};
|
};
|
||||||
use activitypub_federation::{fetch::object_id::ObjectId, protocol::helpers::deserialize_one};
|
use activitypub_federation::{fetch::object_id::ObjectId, protocol::helpers::deserialize_one};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -8,12 +8,12 @@ use url::Url;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreateOrUpdateChatMessage {
|
pub struct CreateOrUpdatePrivateMessage {
|
||||||
pub(crate) id: Url,
|
pub(crate) id: Url,
|
||||||
pub(crate) actor: ObjectId<ApubPerson>,
|
pub(crate) actor: ObjectId<ApubPerson>,
|
||||||
#[serde(deserialize_with = "deserialize_one")]
|
#[serde(deserialize_with = "deserialize_one")]
|
||||||
pub(crate) to: [ObjectId<ApubPerson>; 1],
|
pub(crate) to: [ObjectId<ApubPerson>; 1],
|
||||||
pub(crate) object: ChatMessage,
|
pub(crate) object: PrivateMessage,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub(crate) kind: CreateOrUpdateType,
|
pub(crate) kind: CreateOrUpdateType,
|
||||||
}
|
}
|
|
@ -8,12 +8,12 @@ use lemmy_utils::error::LemmyResult;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub(crate) mod chat_message;
|
|
||||||
pub(crate) mod group;
|
pub(crate) mod group;
|
||||||
pub(crate) mod instance;
|
pub(crate) mod instance;
|
||||||
pub(crate) mod note;
|
pub(crate) mod note;
|
||||||
pub(crate) mod page;
|
pub(crate) mod page;
|
||||||
pub(crate) mod person;
|
pub(crate) mod person;
|
||||||
|
pub(crate) mod private_message;
|
||||||
pub(crate) mod tombstone;
|
pub(crate) mod tombstone;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
|
@ -102,13 +102,14 @@ impl LanguageTag {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::protocol::{
|
use crate::protocol::{
|
||||||
|
activities::create_or_update::note_wrapper::NoteWrapper,
|
||||||
objects::{
|
objects::{
|
||||||
chat_message::ChatMessage,
|
|
||||||
group::Group,
|
group::Group,
|
||||||
instance::Instance,
|
instance::Instance,
|
||||||
note::Note,
|
note::Note,
|
||||||
page::Page,
|
page::Page,
|
||||||
person::Person,
|
person::Person,
|
||||||
|
private_message::PrivateMessage,
|
||||||
tombstone::Tombstone,
|
tombstone::Tombstone,
|
||||||
},
|
},
|
||||||
tests::{test_json, test_parse_lemmy_item},
|
tests::{test_json, test_parse_lemmy_item},
|
||||||
|
@ -121,8 +122,10 @@ mod tests {
|
||||||
test_parse_lemmy_item::<Group>("assets/lemmy/objects/group.json")?;
|
test_parse_lemmy_item::<Group>("assets/lemmy/objects/group.json")?;
|
||||||
test_parse_lemmy_item::<Person>("assets/lemmy/objects/person.json")?;
|
test_parse_lemmy_item::<Person>("assets/lemmy/objects/person.json")?;
|
||||||
test_parse_lemmy_item::<Page>("assets/lemmy/objects/page.json")?;
|
test_parse_lemmy_item::<Page>("assets/lemmy/objects/page.json")?;
|
||||||
test_parse_lemmy_item::<Note>("assets/lemmy/objects/note.json")?;
|
test_parse_lemmy_item::<Note>("assets/lemmy/objects/comment.json")?;
|
||||||
test_parse_lemmy_item::<ChatMessage>("assets/lemmy/objects/chat_message.json")?;
|
test_parse_lemmy_item::<PrivateMessage>("assets/lemmy/objects/private_message.json")?;
|
||||||
|
test_parse_lemmy_item::<NoteWrapper>("assets/lemmy/objects/comment.json")?;
|
||||||
|
test_parse_lemmy_item::<NoteWrapper>("assets/lemmy/objects/private_message.json")?;
|
||||||
test_parse_lemmy_item::<Tombstone>("assets/lemmy/objects/tombstone.json")?;
|
test_parse_lemmy_item::<Tombstone>("assets/lemmy/objects/tombstone.json")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -131,7 +134,6 @@ mod tests {
|
||||||
fn test_parse_objects_pleroma() -> LemmyResult<()> {
|
fn test_parse_objects_pleroma() -> LemmyResult<()> {
|
||||||
test_json::<Person>("assets/pleroma/objects/person.json")?;
|
test_json::<Person>("assets/pleroma/objects/person.json")?;
|
||||||
test_json::<Note>("assets/pleroma/objects/note.json")?;
|
test_json::<Note>("assets/pleroma/objects/note.json")?;
|
||||||
test_json::<ChatMessage>("assets/pleroma/objects/chat_message.json")?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,9 @@ use serde_with::skip_serializing_none;
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChatMessage {
|
pub struct PrivateMessage {
|
||||||
pub(crate) r#type: ChatMessageType,
|
#[serde(rename = "type")]
|
||||||
|
pub(crate) kind: PrivateMessageType,
|
||||||
pub(crate) id: ObjectId<ApubPrivateMessage>,
|
pub(crate) id: ObjectId<ApubPrivateMessage>,
|
||||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||||
#[serde(deserialize_with = "deserialize_one")]
|
#[serde(deserialize_with = "deserialize_one")]
|
||||||
|
@ -31,8 +32,10 @@ pub struct ChatMessage {
|
||||||
pub(crate) updated: Option<DateTime<Utc>>,
|
pub(crate) updated: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub enum ChatMessageType {
|
pub enum PrivateMessageType {
|
||||||
|
/// Deprecated, for compatibility with Lemmy 0.19 and earlier
|
||||||
|
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
|
Note,
|
||||||
}
|
}
|
|
@ -660,6 +660,7 @@ diesel::table! {
|
||||||
enabled -> Bool,
|
enabled -> Bool,
|
||||||
published -> Timestamptz,
|
published -> Timestamptz,
|
||||||
updated -> Nullable<Timestamptz>,
|
updated -> Nullable<Timestamptz>,
|
||||||
|
use_pkce -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ pub struct OAuthProvider {
|
||||||
pub published: DateTime<Utc>,
|
pub published: DateTime<Utc>,
|
||||||
#[cfg_attr(feature = "full", ts(optional))]
|
#[cfg_attr(feature = "full", ts(optional))]
|
||||||
pub updated: Option<DateTime<Utc>>,
|
pub updated: Option<DateTime<Utc>>,
|
||||||
|
/// switch to enable or disable PKCE
|
||||||
|
pub use_pkce: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
|
#[derive(Clone, PartialEq, Eq, Debug, Deserialize)]
|
||||||
|
@ -83,6 +85,7 @@ impl Serialize for PublicOAuthProvider {
|
||||||
state.serialize_field("authorization_endpoint", &self.0.authorization_endpoint)?;
|
state.serialize_field("authorization_endpoint", &self.0.authorization_endpoint)?;
|
||||||
state.serialize_field("client_id", &self.0.client_id)?;
|
state.serialize_field("client_id", &self.0.client_id)?;
|
||||||
state.serialize_field("scopes", &self.0.scopes)?;
|
state.serialize_field("scopes", &self.0.scopes)?;
|
||||||
|
state.serialize_field("use_pkce", &self.0.use_pkce)?;
|
||||||
state.end()
|
state.end()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,6 +105,7 @@ pub struct OAuthProviderInsertForm {
|
||||||
pub scopes: String,
|
pub scopes: String,
|
||||||
pub auto_verify_email: Option<bool>,
|
pub auto_verify_email: Option<bool>,
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,6 +122,7 @@ pub struct OAuthProviderUpdateForm {
|
||||||
pub scopes: Option<String>,
|
pub scopes: Option<String>,
|
||||||
pub auto_verify_email: Option<bool>,
|
pub auto_verify_email: Option<bool>,
|
||||||
pub account_linking_enabled: Option<bool>,
|
pub account_linking_enabled: Option<bool>,
|
||||||
|
pub use_pkce: Option<bool>,
|
||||||
pub enabled: Option<bool>,
|
pub enabled: Option<bool>,
|
||||||
pub updated: Option<Option<DateTime<Utc>>>,
|
pub updated: Option<Option<DateTime<Utc>>>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -316,17 +316,14 @@ impl CommentView {
|
||||||
comment_id: CommentId,
|
comment_id: CommentId,
|
||||||
my_local_user: Option<&'_ LocalUser>,
|
my_local_user: Option<&'_ LocalUser>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
|
let is_admin = my_local_user.map(|u| u.admin).unwrap_or(false);
|
||||||
// If a person is given, then my_vote (res.9), if None, should be 0, not null
|
// If a person is given, then my_vote (res.9), if None, should be 0, not null
|
||||||
// Necessary to differentiate between other person's votes
|
// Necessary to differentiate between other person's votes
|
||||||
let res = queries().read(pool, (comment_id, my_local_user)).await?;
|
let mut res = queries().read(pool, (comment_id, my_local_user)).await?;
|
||||||
let mut new_view = res.clone();
|
|
||||||
if my_local_user.is_some() && res.my_vote.is_none() {
|
if my_local_user.is_some() && res.my_vote.is_none() {
|
||||||
new_view.my_vote = Some(0);
|
res.my_vote = Some(0);
|
||||||
}
|
}
|
||||||
if res.comment.deleted || res.comment.removed {
|
Ok(handle_deleted(res, is_admin))
|
||||||
new_view.comment.content = String::new();
|
|
||||||
}
|
|
||||||
Ok(new_view)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,22 +347,25 @@ pub struct CommentQuery<'a> {
|
||||||
|
|
||||||
impl CommentQuery<'_> {
|
impl CommentQuery<'_> {
|
||||||
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
|
pub async fn list(self, site: &Site, pool: &mut DbPool<'_>) -> Result<Vec<CommentView>, Error> {
|
||||||
|
let is_admin = self.local_user.map(|u| u.admin).unwrap_or(false);
|
||||||
Ok(
|
Ok(
|
||||||
queries()
|
queries()
|
||||||
.list(pool, (self, site))
|
.list(pool, (self, site))
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut c| {
|
.map(|c| handle_deleted(c, is_admin))
|
||||||
if c.comment.deleted || c.comment.removed {
|
|
||||||
c.comment.content = String::new();
|
|
||||||
}
|
|
||||||
c
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_deleted(mut c: CommentView, is_admin: bool) -> CommentView {
|
||||||
|
if !is_admin && (c.comment.deleted || c.comment.removed) {
|
||||||
|
c.comment.content = String::new();
|
||||||
|
}
|
||||||
|
c
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[expect(clippy::indexing_slicing)]
|
#[expect(clippy::indexing_slicing)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
@ -1301,4 +1301,65 @@ mod tests {
|
||||||
|
|
||||||
cleanup(data, pool).await
|
cleanup(data, pool).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn comment_removed() -> LemmyResult<()> {
|
||||||
|
let pool = &build_db_pool_for_tests();
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
let mut data = init_data(pool).await?;
|
||||||
|
|
||||||
|
// Mark a comment as removed
|
||||||
|
let form = CommentUpdateForm {
|
||||||
|
removed: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Comment::update(pool, data.inserted_comment_0.id, &form).await?;
|
||||||
|
|
||||||
|
// Read as normal user, content is cleared
|
||||||
|
data.timmy_local_user_view.local_user.admin = false;
|
||||||
|
let comment_view = CommentView::read(
|
||||||
|
pool,
|
||||||
|
data.inserted_comment_0.id,
|
||||||
|
Some(&data.timmy_local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!("", comment_view.comment.content);
|
||||||
|
let comment_listing = CommentQuery {
|
||||||
|
community_id: Some(data.inserted_community.id),
|
||||||
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
|
sort: Some(CommentSortType::Old),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!("", comment_listing[0].comment.content);
|
||||||
|
|
||||||
|
// Read as admin, content is returned
|
||||||
|
data.timmy_local_user_view.local_user.admin = true;
|
||||||
|
let comment_view = CommentView::read(
|
||||||
|
pool,
|
||||||
|
data.inserted_comment_0.id,
|
||||||
|
Some(&data.timmy_local_user_view.local_user),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
data.inserted_comment_0.content,
|
||||||
|
comment_view.comment.content
|
||||||
|
);
|
||||||
|
let comment_listing = CommentQuery {
|
||||||
|
community_id: Some(data.inserted_community.id),
|
||||||
|
local_user: Some(&data.timmy_local_user_view.local_user),
|
||||||
|
sort: Some(CommentSortType::Old),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
.list(&data.site, pool)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(
|
||||||
|
data.inserted_comment_0.content,
|
||||||
|
comment_listing[0].comment.content
|
||||||
|
);
|
||||||
|
|
||||||
|
cleanup(data, pool).await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ pub enum LemmyErrorType {
|
||||||
InvalidEmailAddress(String),
|
InvalidEmailAddress(String),
|
||||||
RateLimitError,
|
RateLimitError,
|
||||||
InvalidName,
|
InvalidName,
|
||||||
|
InvalidCodeVerifier,
|
||||||
InvalidDisplayName,
|
InvalidDisplayName,
|
||||||
InvalidMatrixId,
|
InvalidMatrixId,
|
||||||
InvalidPostTitle,
|
InvalidPostTitle,
|
||||||
|
|
3
migrations/2024-11-23-234637_oauth_pkce/down.sql
Normal file
3
migrations/2024-11-23-234637_oauth_pkce/down.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE oauth_provider
|
||||||
|
DROP COLUMN use_pkce;
|
||||||
|
|
3
migrations/2024-11-23-234637_oauth_pkce/up.sql
Normal file
3
migrations/2024-11-23-234637_oauth_pkce/up.sql
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
ALTER TABLE oauth_provider
|
||||||
|
ADD COLUMN use_pkce boolean DEFAULT FALSE NOT NULL;
|
||||||
|
|
Loading…
Reference in a new issue