Don't notify when muted
This commit is contained in:
parent
cb61f4a86b
commit
22883798b3
19 changed files with 104 additions and 314 deletions
|
@ -1,4 +1,3 @@
|
||||||
use deadpool_postgres::SslMode;
|
|
||||||
use openssl::ssl::{SslConnector, SslMethod};
|
use openssl::ssl::{SslConnector, SslMethod};
|
||||||
use postgres_openssl::MakeTlsConnector;
|
use postgres_openssl::MakeTlsConnector;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
|
@ -11,6 +11,7 @@ use crate::notifications::queries::{
|
||||||
create_mention_notification, create_reply_notification, create_repost_notification,
|
create_mention_notification, create_reply_notification, create_repost_notification,
|
||||||
};
|
};
|
||||||
use crate::profiles::{queries::update_post_count, types::DbActorProfile};
|
use crate::profiles::{queries::update_post_count, types::DbActorProfile};
|
||||||
|
use crate::relationships::queries::is_muted;
|
||||||
use crate::relationships::types::RelationshipType;
|
use crate::relationships::types::RelationshipType;
|
||||||
|
|
||||||
use super::types::{DbPost, Post, PostCreateData, PostUpdateData, Visibility};
|
use super::types::{DbPost, Post, PostCreateData, PostUpdateData, Visibility};
|
||||||
|
@ -249,20 +250,32 @@ pub async fn create_post(
|
||||||
notified_users.push(in_reply_to_author.id);
|
notified_users.push(in_reply_to_author.id);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Notify reposted
|
||||||
if let Some(repost_of_id) = &db_post.repost_of_id {
|
if let Some(repost_of_id) = &db_post.repost_of_id {
|
||||||
update_repost_count(&transaction, repost_of_id, 1).await?;
|
update_repost_count(&transaction, repost_of_id, 1).await?;
|
||||||
let repost_of_author = get_post_author(&transaction, repost_of_id).await?;
|
let repost_of_author = get_post_author(&transaction, repost_of_id).await?;
|
||||||
if repost_of_author.is_local()
|
if repost_of_author.is_local()
|
||||||
&& repost_of_author.id != db_post.author_id
|
|
||||||
&& !notified_users.contains(&repost_of_author.id)
|
&& !notified_users.contains(&repost_of_author.id)
|
||||||
|
// Don't notify themselves that they reported their post
|
||||||
|
&& repost_of_author.id != db_post.author_id
|
||||||
{
|
{
|
||||||
create_repost_notification(
|
// Don't create mention notification if the author is muted
|
||||||
&transaction,
|
if is_muted(&transaction, &repost_of_author.id, &db_post.author_id).await? {
|
||||||
&db_post.author_id,
|
log::warn!(
|
||||||
&repost_of_author.id,
|
"User {} mentioned by muted author id {} on post id {}, ignoring mention..",
|
||||||
repost_of_id,
|
repost_of_author.username,
|
||||||
)
|
db_post.author_id,
|
||||||
.await?;
|
db_post.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
create_repost_notification(
|
||||||
|
&transaction,
|
||||||
|
&db_post.author_id,
|
||||||
|
&repost_of_author.id,
|
||||||
|
repost_of_id,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
notified_users.push(repost_of_author.id);
|
notified_users.push(repost_of_author.id);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -274,8 +287,23 @@ pub async fn create_post(
|
||||||
// or to the author of reposted post
|
// or to the author of reposted post
|
||||||
!notified_users.contains(&profile.id)
|
!notified_users.contains(&profile.id)
|
||||||
{
|
{
|
||||||
create_mention_notification(&transaction, &db_post.author_id, &profile.id, &db_post.id)
|
// Don't create mention notification if the author is muted
|
||||||
|
if is_muted(&transaction, &profile.id, &db_post.author_id).await? {
|
||||||
|
log::warn!(
|
||||||
|
"User {} mentioned by muted author {} in post id {}, ignoring mention..",
|
||||||
|
profile.username,
|
||||||
|
db_post.author_id,
|
||||||
|
db_post.id
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
create_mention_notification(
|
||||||
|
&transaction,
|
||||||
|
&db_post.author_id,
|
||||||
|
&profile.id,
|
||||||
|
&db_post.id,
|
||||||
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// Construct post object
|
// Construct post object
|
||||||
|
@ -437,6 +465,7 @@ pub async fn get_home_timeline(
|
||||||
(
|
(
|
||||||
post.author_id = $current_user_id
|
post.author_id = $current_user_id
|
||||||
OR (
|
OR (
|
||||||
|
-- is following or subscribed the post author
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1 FROM relationship
|
SELECT 1 FROM relationship
|
||||||
WHERE
|
WHERE
|
||||||
|
@ -487,6 +516,14 @@ pub async fn get_home_timeline(
|
||||||
WHERE post_id = post.id AND profile_id = $current_user_id
|
WHERE post_id = post.id AND profile_id = $current_user_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
-- author is not muted
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM relationship
|
||||||
|
WHERE
|
||||||
|
source_id = $current_user_id
|
||||||
|
AND target_id = post.author_id
|
||||||
|
AND relationship_type = {relationship_mute}
|
||||||
|
)
|
||||||
AND {visibility_filter}
|
AND {visibility_filter}
|
||||||
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)
|
AND ($max_post_id::uuid IS NULL OR post.id < $max_post_id)
|
||||||
ORDER BY post.id DESC
|
ORDER BY post.id DESC
|
||||||
|
@ -501,6 +538,7 @@ pub async fn get_home_timeline(
|
||||||
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
relationship_subscription=i16::from(&RelationshipType::Subscription),
|
||||||
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
|
relationship_hide_reposts=i16::from(&RelationshipType::HideReposts),
|
||||||
relationship_hide_replies=i16::from(&RelationshipType::HideReplies),
|
relationship_hide_replies=i16::from(&RelationshipType::HideReplies),
|
||||||
|
relationship_mute=i16::from(&RelationshipType::Mute),
|
||||||
visibility_filter=build_visibility_filter(),
|
visibility_filter=build_visibility_filter(),
|
||||||
);
|
);
|
||||||
let limit: i64 = limit.into();
|
let limit: i64 = limit.into();
|
||||||
|
|
|
@ -652,6 +652,26 @@ pub async fn unmute_posts(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn is_muted(
|
||||||
|
db_client: &impl DatabaseClient,
|
||||||
|
source_id: &Uuid,
|
||||||
|
target_id: &Uuid,
|
||||||
|
) -> Result<bool, DatabaseError> {
|
||||||
|
let rows = db_client
|
||||||
|
.query(
|
||||||
|
"
|
||||||
|
SELECT 1
|
||||||
|
FROM relationship
|
||||||
|
WHERE
|
||||||
|
source_id = $1 AND target_id = $2
|
||||||
|
AND relationship_type = $3
|
||||||
|
",
|
||||||
|
&[&source_id, &target_id, &RelationshipType::Mute],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(rows.len() > 0)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,14 +1,9 @@
|
||||||
use fedimovies_models::profiles::types::{
|
use fedimovies_models::profiles::types::{
|
||||||
ExtraField, IdentityProof, IdentityProofType, PaymentLink, PaymentOption,
|
ExtraField, IdentityProof, IdentityProofType, PaymentLink, PaymentOption,
|
||||||
};
|
};
|
||||||
use fedimovies_utils::did::Did;
|
|
||||||
|
|
||||||
use crate::activitypub::vocabulary::{IDENTITY_PROOF, LINK, PROPERTY_VALUE};
|
use crate::activitypub::vocabulary::{IDENTITY_PROOF, LINK, PROPERTY_VALUE};
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::identity::{
|
|
||||||
claims::create_identity_claim,
|
|
||||||
minisign::{parse_minisign_signature, verify_minisign_signature},
|
|
||||||
};
|
|
||||||
use crate::json_signatures::proofs::{PROOF_TYPE_ID_EIP191, PROOF_TYPE_ID_MINISIGN};
|
use crate::json_signatures::proofs::{PROOF_TYPE_ID_EIP191, PROOF_TYPE_ID_MINISIGN};
|
||||||
use crate::web_client::urls::get_subscription_page_url;
|
use crate::web_client::urls::get_subscription_page_url;
|
||||||
|
|
||||||
|
@ -30,51 +25,10 @@ pub fn attach_identity_proof(proof: IdentityProof) -> ActorAttachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_identity_proof(
|
pub fn parse_identity_proof(
|
||||||
actor_id: &str,
|
_actor_id: &str,
|
||||||
attachment: &ActorAttachment,
|
_attachment: &ActorAttachment,
|
||||||
) -> Result<IdentityProof, ValidationError> {
|
) -> Result<IdentityProof, ValidationError> {
|
||||||
if attachment.object_type != IDENTITY_PROOF {
|
return Err(ValidationError("incorrect proof type".to_string()));
|
||||||
return Err(ValidationError("invalid attachment type".to_string()));
|
|
||||||
};
|
|
||||||
let proof_type_str = attachment
|
|
||||||
.signature_algorithm
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ValidationError("missing proof type".to_string()))?;
|
|
||||||
let proof_type = match proof_type_str.as_str() {
|
|
||||||
PROOF_TYPE_ID_EIP191 => IdentityProofType::LegacyEip191IdentityProof,
|
|
||||||
PROOF_TYPE_ID_MINISIGN => IdentityProofType::LegacyMinisignIdentityProof,
|
|
||||||
_ => return Err(ValidationError("unsupported proof type".to_string())),
|
|
||||||
};
|
|
||||||
let did = attachment
|
|
||||||
.name
|
|
||||||
.parse::<Did>()
|
|
||||||
.map_err(|_| ValidationError("invalid DID".to_string()))?;
|
|
||||||
let message = create_identity_claim(actor_id, &did)
|
|
||||||
.map_err(|_| ValidationError("invalid claim".to_string()))?;
|
|
||||||
let signature = attachment
|
|
||||||
.signature_value
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ValidationError("missing signature".to_string()))?;
|
|
||||||
match did {
|
|
||||||
Did::Key(ref did_key) => {
|
|
||||||
if !matches!(proof_type, IdentityProofType::LegacyMinisignIdentityProof) {
|
|
||||||
return Err(ValidationError("incorrect proof type".to_string()));
|
|
||||||
};
|
|
||||||
let signature_bin = parse_minisign_signature(signature)
|
|
||||||
.map_err(|_| ValidationError("invalid signature encoding".to_string()))?;
|
|
||||||
verify_minisign_signature(did_key, &message, &signature_bin)
|
|
||||||
.map_err(|_| ValidationError("invalid identity proof".to_string()))?;
|
|
||||||
}
|
|
||||||
Did::Pkh(ref _did_pkh) => {
|
|
||||||
return Err(ValidationError("incorrect proof type".to_string()));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let proof = IdentityProof {
|
|
||||||
issuer: did,
|
|
||||||
proof_type: proof_type,
|
|
||||||
value: signature.to_string(),
|
|
||||||
};
|
|
||||||
Ok(proof)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn attach_payment_option(
|
pub fn attach_payment_option(
|
||||||
|
|
|
@ -1,23 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use super::attachments::{
|
||||||
|
attach_extra_field, attach_identity_proof, attach_payment_option, parse_extra_field,
|
||||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
parse_identity_proof, parse_payment_option,
|
||||||
use serde_json::{json, Value};
|
|
||||||
|
|
||||||
use fedimovies_config::Instance;
|
|
||||||
use fedimovies_models::{
|
|
||||||
profiles::types::{DbActor, DbActorPublicKey, ExtraField, IdentityProof, PaymentOption},
|
|
||||||
users::types::User,
|
|
||||||
};
|
};
|
||||||
use fedimovies_utils::{
|
|
||||||
crypto_rsa::{deserialize_private_key, get_public_key_pem},
|
|
||||||
urls::get_hostname,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::activitypub::types::build_default_context;
|
use crate::activitypub::types::build_default_context;
|
||||||
use crate::activitypub::{
|
use crate::activitypub::{
|
||||||
constants::{
|
|
||||||
AP_CONTEXT, MASTODON_CONTEXT, MITRA_CONTEXT, SCHEMA_ORG_CONTEXT, W3ID_SECURITY_CONTEXT,
|
|
||||||
},
|
|
||||||
identifiers::{
|
identifiers::{
|
||||||
local_actor_id, local_actor_key_id, local_instance_actor_id, LocalActorCollection,
|
local_actor_id, local_actor_key_id, local_instance_actor_id, LocalActorCollection,
|
||||||
},
|
},
|
||||||
|
@ -27,11 +13,17 @@ use crate::activitypub::{
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::media::get_file_url;
|
use crate::media::get_file_url;
|
||||||
use crate::webfinger::types::ActorAddress;
|
use crate::webfinger::types::ActorAddress;
|
||||||
|
use fedimovies_config::Instance;
|
||||||
use super::attachments::{
|
use fedimovies_models::{
|
||||||
attach_extra_field, attach_identity_proof, attach_payment_option, parse_extra_field,
|
profiles::types::{DbActor, DbActorPublicKey, ExtraField, IdentityProof, PaymentOption},
|
||||||
parse_identity_proof, parse_payment_option,
|
users::types::User,
|
||||||
};
|
};
|
||||||
|
use fedimovies_utils::{
|
||||||
|
crypto_rsa::{deserialize_private_key, get_public_key_pem},
|
||||||
|
urls::get_hostname,
|
||||||
|
};
|
||||||
|
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
#[cfg_attr(test, derive(Default))]
|
#[cfg_attr(test, derive(Default))]
|
||||||
|
@ -245,27 +237,6 @@ impl Actor {
|
||||||
|
|
||||||
pub type ActorKeyError = rsa::pkcs8::Error;
|
pub type ActorKeyError = rsa::pkcs8::Error;
|
||||||
|
|
||||||
fn build_actor_context() -> (
|
|
||||||
&'static str,
|
|
||||||
&'static str,
|
|
||||||
HashMap<&'static str, &'static str>,
|
|
||||||
) {
|
|
||||||
(
|
|
||||||
AP_CONTEXT,
|
|
||||||
W3ID_SECURITY_CONTEXT,
|
|
||||||
HashMap::from([
|
|
||||||
("manuallyApprovesFollowers", "as:manuallyApprovesFollowers"),
|
|
||||||
("schema", SCHEMA_ORG_CONTEXT),
|
|
||||||
("PropertyValue", "schema:PropertyValue"),
|
|
||||||
("value", "schema:value"),
|
|
||||||
("toot", MASTODON_CONTEXT),
|
|
||||||
("IdentityProof", "toot:IdentityProof"),
|
|
||||||
("fedimovies", MITRA_CONTEXT),
|
|
||||||
("subscribers", "fedimovies:subscribers"),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_local_actor(user: &User, instance_url: &str) -> Result<Actor, ActorKeyError> {
|
pub fn get_local_actor(user: &User, instance_url: &str) -> Result<Actor, ActorKeyError> {
|
||||||
let username = &user.profile.username;
|
let username = &user.profile.username;
|
||||||
let actor_id = local_actor_id(instance_url, username);
|
let actor_id = local_actor_id(instance_url, username);
|
||||||
|
|
|
@ -165,7 +165,7 @@ pub async fn verify_signed_activity(
|
||||||
let signer_key = deserialize_public_key(&signer_actor.public_key.public_key_pem)?;
|
let signer_key = deserialize_public_key(&signer_actor.public_key.public_key_pem)?;
|
||||||
verify_rsa_json_signature(&signature_data, &signer_key)?;
|
verify_rsa_json_signature(&signature_data, &signer_key)?;
|
||||||
}
|
}
|
||||||
JsonSigner::Did(did) => {
|
JsonSigner::Did(_did) => {
|
||||||
return Err(AuthenticationError::InvalidJsonSignatureType);
|
return Err(AuthenticationError::InvalidJsonSignatureType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,12 +4,11 @@ pub const AP_MEDIA_TYPE: &str =
|
||||||
pub const AS_MEDIA_TYPE: &str = "application/activity+json";
|
pub const AS_MEDIA_TYPE: &str = "application/activity+json";
|
||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
pub const AP_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
|
pub const AS_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
|
||||||
pub const W3ID_SECURITY_CONTEXT: &str = "https://w3id.org/security/v1";
|
pub const W3ID_SECURITY_CONTEXT: &str = "https://w3id.org/security/v1";
|
||||||
pub const W3ID_DATA_INTEGRITY_CONTEXT: &str = "https://w3id.org/security/data-integrity/v1";
|
pub const W3ID_DATA_INTEGRITY_CONTEXT: &str = "https://w3id.org/security/data-integrity/v1";
|
||||||
pub const SCHEMA_ORG_CONTEXT: &str = "http://schema.org/";
|
pub const SCHEMA_ORG_CONTEXT: &str = "http://schema.org/#";
|
||||||
pub const MASTODON_CONTEXT: &str = "http://joinmastodon.org/ns#";
|
pub const MASTODON_CONTEXT: &str = "http://joinmastodon.org/ns#";
|
||||||
pub const MITRA_CONTEXT: &str = "http://jsonld.fedimovies.social#";
|
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public";
|
pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public";
|
||||||
|
|
|
@ -9,7 +9,7 @@ use fedimovies_utils::{files::sniff_media_type, urls::guess_protocol};
|
||||||
|
|
||||||
use crate::activitypub::{
|
use crate::activitypub::{
|
||||||
actors::types::Actor,
|
actors::types::Actor,
|
||||||
constants::{AP_CONTEXT, AP_MEDIA_TYPE},
|
constants::{AP_MEDIA_TYPE, AS_CONTEXT},
|
||||||
http_client::{build_federation_client, get_network_type},
|
http_client::{build_federation_client, get_network_type},
|
||||||
identifiers::{local_actor_key_id, local_instance_actor_id},
|
identifiers::{local_actor_key_id, local_instance_actor_id},
|
||||||
types::Object,
|
types::Object,
|
||||||
|
@ -153,7 +153,7 @@ pub async fn perform_webfinger_query(
|
||||||
let jrd: JsonResourceDescriptor = serde_json::from_str(&webfinger_data)?;
|
let jrd: JsonResourceDescriptor = serde_json::from_str(&webfinger_data)?;
|
||||||
// Lemmy servers can have Group and Person actors with the same name
|
// Lemmy servers can have Group and Person actors with the same name
|
||||||
// https://github.com/LemmyNet/lemmy/issues/2037
|
// https://github.com/LemmyNet/lemmy/issues/2037
|
||||||
let ap_type_property = format!("{}#type", AP_CONTEXT);
|
let ap_type_property = format!("{}#type", AS_CONTEXT);
|
||||||
let group_link = jrd.links.iter().find(|link| {
|
let group_link = jrd.links.iter().find(|link| {
|
||||||
link.rel == "self"
|
link.rel == "self"
|
||||||
&& link
|
&& link
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use log::warn;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
use crate::activitypub::constants::{
|
||||||
|
AS_CONTEXT, MASTODON_CONTEXT, SCHEMA_ORG_CONTEXT, W3ID_SECURITY_CONTEXT,
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
use serde::{de::Error as DeserializerError, Deserialize, Deserializer, Serialize};
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
@ -124,11 +127,11 @@ pub type Context = Value;
|
||||||
|
|
||||||
pub fn build_default_context() -> Context {
|
pub fn build_default_context() -> Context {
|
||||||
json!([
|
json!([
|
||||||
"https://www.w3.org/ns/activitystreams",
|
AS_CONTEXT,
|
||||||
"https://w3id.org/security/v1",
|
W3ID_SECURITY_CONTEXT,
|
||||||
{
|
{
|
||||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||||
"toot": "http://joinmastodon.org/ns#",
|
"toot": MASTODON_CONTEXT,
|
||||||
"featured": {
|
"featured": {
|
||||||
"@id": "toot:featured",
|
"@id": "toot:featured",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
|
@ -145,7 +148,7 @@ pub fn build_default_context() -> Context {
|
||||||
"@id": "as:movedTo",
|
"@id": "as:movedTo",
|
||||||
"@type": "@id"
|
"@type": "@id"
|
||||||
},
|
},
|
||||||
"schema": "http://schema.org#",
|
"schema": SCHEMA_ORG_CONTEXT,
|
||||||
"PropertyValue": "schema:PropertyValue",
|
"PropertyValue": "schema:PropertyValue",
|
||||||
"value": "schema:value",
|
"value": "schema:value",
|
||||||
"IdentityProof": "toot:IdentityProof",
|
"IdentityProof": "toot:IdentityProof",
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
use fedimovies_utils::{
|
|
||||||
canonicalization::{canonicalize_object, CanonicalizationError},
|
|
||||||
did::Did,
|
|
||||||
};
|
|
||||||
|
|
||||||
// https://www.w3.org/TR/vc-data-model/#credential-subject
|
|
||||||
#[derive(Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
struct Claim {
|
|
||||||
id: String, // actor ID
|
|
||||||
owner_of: Did,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates key ownership claim and prepares it for signing
|
|
||||||
pub fn create_identity_claim(actor_id: &str, did: &Did) -> Result<String, CanonicalizationError> {
|
|
||||||
let claim = Claim {
|
|
||||||
id: actor_id.to_string(),
|
|
||||||
owner_of: did.clone(),
|
|
||||||
};
|
|
||||||
let message = canonicalize_object(&claim)?;
|
|
||||||
Ok(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use fedimovies_utils::{currencies::Currency, did_pkh::DidPkh};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_identity_claim() {
|
|
||||||
let actor_id = "https://example.org/users/test";
|
|
||||||
let ethereum_address = "0xB9C5714089478a327F09197987f16f9E5d936E8a";
|
|
||||||
let did = Did::Pkh(DidPkh::from_address(&Currency::Ethereum, ethereum_address));
|
|
||||||
let claim = create_identity_claim(actor_id, &did).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
claim,
|
|
||||||
r#"{"id":"https://example.org/users/test","ownerOf":"did:pkh:eip155:1:0xb9c5714089478a327f09197987f16f9e5d936e8a"}"#,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
/// https://jedisct1.github.io/minisign/
|
|
||||||
use blake2::{Blake2b512, Digest};
|
|
||||||
use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
|
|
||||||
|
|
||||||
use fedimovies_utils::did_key::{DidKey, MulticodecError};
|
|
||||||
|
|
||||||
const MINISIGN_SIGNATURE_CODE: [u8; 2] = *b"Ed";
|
|
||||||
const MINISIGN_SIGNATURE_HASHED_CODE: [u8; 2] = *b"ED";
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum ParseError {
|
|
||||||
#[error("invalid encoding")]
|
|
||||||
InvalidEncoding(#[from] base64::DecodeError),
|
|
||||||
|
|
||||||
#[error("invalid key length")]
|
|
||||||
InvalidKeyLength,
|
|
||||||
|
|
||||||
#[error("invalid signature length")]
|
|
||||||
InvalidSignatureLength,
|
|
||||||
|
|
||||||
#[error("invalid signature type")]
|
|
||||||
InvalidSignatureType,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public key format:
|
|
||||||
// base64(<signature_algorithm> || <key_id> || <public_key>)
|
|
||||||
fn parse_minisign_public_key(key_b64: &str) -> Result<[u8; 32], ParseError> {
|
|
||||||
let key_bin = base64::decode(key_b64)?;
|
|
||||||
if key_bin.len() != 42 {
|
|
||||||
return Err(ParseError::InvalidKeyLength);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut signature_algorithm = [0; 2];
|
|
||||||
let mut _key_id = [0; 8];
|
|
||||||
let mut key = [0; 32];
|
|
||||||
signature_algorithm.copy_from_slice(&key_bin[0..2]);
|
|
||||||
_key_id.copy_from_slice(&key_bin[2..10]);
|
|
||||||
key.copy_from_slice(&key_bin[10..42]);
|
|
||||||
|
|
||||||
if signature_algorithm.as_ref() != MINISIGN_SIGNATURE_CODE {
|
|
||||||
return Err(ParseError::InvalidSignatureType);
|
|
||||||
};
|
|
||||||
Ok(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn minisign_key_to_did(key_b64: &str) -> Result<DidKey, ParseError> {
|
|
||||||
let key = parse_minisign_public_key(key_b64)?;
|
|
||||||
let did_key = DidKey::from_ed25519_key(key);
|
|
||||||
Ok(did_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signature format:
|
|
||||||
// base64(<signature_algorithm> || <key_id> || <signature>)
|
|
||||||
pub fn parse_minisign_signature(signature_b64: &str) -> Result<[u8; 64], ParseError> {
|
|
||||||
let signature_bin = base64::decode(signature_b64)?;
|
|
||||||
if signature_bin.len() != 74 {
|
|
||||||
return Err(ParseError::InvalidSignatureLength);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut signature_algorithm = [0; 2];
|
|
||||||
let mut _key_id = [0; 8];
|
|
||||||
let mut signature = [0; 64];
|
|
||||||
signature_algorithm.copy_from_slice(&signature_bin[0..2]);
|
|
||||||
_key_id.copy_from_slice(&signature_bin[2..10]);
|
|
||||||
signature.copy_from_slice(&signature_bin[10..74]);
|
|
||||||
|
|
||||||
if signature_algorithm.as_ref() != MINISIGN_SIGNATURE_HASHED_CODE {
|
|
||||||
return Err(ParseError::InvalidSignatureType);
|
|
||||||
};
|
|
||||||
Ok(signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _verify_ed25519_signature(
|
|
||||||
message: &str,
|
|
||||||
signer: [u8; 32],
|
|
||||||
signature: [u8; 64],
|
|
||||||
) -> Result<(), SignatureError> {
|
|
||||||
let signature = Signature::from_bytes(&signature)?;
|
|
||||||
let public_key = PublicKey::from_bytes(&signer)?;
|
|
||||||
let mut hasher = Blake2b512::new();
|
|
||||||
hasher.update(message);
|
|
||||||
let hash = hasher.finalize();
|
|
||||||
public_key.verify(&hash, &signature)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
|
||||||
pub enum VerificationError {
|
|
||||||
#[error(transparent)]
|
|
||||||
InvalidKey(#[from] MulticodecError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
ParseError(#[from] ParseError),
|
|
||||||
|
|
||||||
#[error(transparent)]
|
|
||||||
SignatureError(#[from] SignatureError),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify_minisign_signature(
|
|
||||||
signer: &DidKey,
|
|
||||||
message: &str,
|
|
||||||
signature: &[u8],
|
|
||||||
) -> Result<(), VerificationError> {
|
|
||||||
let ed25519_key = signer.try_ed25519_key()?;
|
|
||||||
let ed25519_signature = signature
|
|
||||||
.try_into()
|
|
||||||
.map_err(|_| ParseError::InvalidSignatureLength)?;
|
|
||||||
let message = format!("{}\n", message);
|
|
||||||
_verify_ed25519_signature(&message, ed25519_key, ed25519_signature)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_verify_minisign_signature() {
|
|
||||||
let minisign_key = "RWSA58rRENpGFYwAjRjbdST7VHFoIuH9JBHfO2u6i5JgANPIoQhABAF/";
|
|
||||||
let message = "test";
|
|
||||||
let minisign_signature =
|
|
||||||
"RUSA58rRENpGFVKxdZGMG1WdIJ+dlyP83qOqw6GP0H/Li6Brug2A3mFKLtleIRLi6IIG0smzOlX5CEsisNnc897OUHIOSNLsQQs=";
|
|
||||||
let signer = minisign_key_to_did(minisign_key).unwrap();
|
|
||||||
let signature_bin = parse_minisign_signature(minisign_signature).unwrap();
|
|
||||||
let result = verify_minisign_signature(&signer, message, &signature_bin);
|
|
||||||
assert_eq!(result.is_ok(), true);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod claims;
|
|
||||||
pub mod minisign;
|
|
|
@ -114,7 +114,7 @@ pub async fn handle_movies_mentions(
|
||||||
let mut repost =
|
let mut repost =
|
||||||
match create_post(&mut transaction, ¤t_user.id, repost_data).await {
|
match create_post(&mut transaction, ¤t_user.id, repost_data).await {
|
||||||
Ok(repost) => repost,
|
Ok(repost) => repost,
|
||||||
Err(DatabaseError::AlreadyExists(err)) => {
|
Err(DatabaseError::AlreadyExists(_err)) => {
|
||||||
log::info!(
|
log::info!(
|
||||||
"Review as Mention of {} already reposted the post with id {}",
|
"Review as Mention of {} already reposted the post with id {}",
|
||||||
current_user.profile.username,
|
current_user.profile.username,
|
||||||
|
|
|
@ -7,9 +7,6 @@ use crate::json_signatures::proofs::PROOF_TYPE_JCS_RSA;
|
||||||
use fedimovies_utils::crypto_rsa::create_rsa_signature;
|
use fedimovies_utils::crypto_rsa::create_rsa_signature;
|
||||||
use fedimovies_utils::{
|
use fedimovies_utils::{
|
||||||
canonicalization::{canonicalize_object, CanonicalizationError},
|
canonicalization::{canonicalize_object, CanonicalizationError},
|
||||||
crypto_rsa::create_rsa_sha256_signature,
|
|
||||||
did_key::DidKey,
|
|
||||||
did_pkh::DidPkh,
|
|
||||||
multibase::encode_multibase_base58btc,
|
multibase::encode_multibase_base58btc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ pub const PROOF_TYPE_ID_MINISIGN: &str = "MitraMinisignSignature2022A";
|
||||||
// - Digest algorithm: SHA-256
|
// - Digest algorithm: SHA-256
|
||||||
// - Signature algorithm: RSASSA-PKCS1-v1_5
|
// - Signature algorithm: RSASSA-PKCS1-v1_5
|
||||||
pub const PROOF_TYPE_JCS_RSA: &str = "MitraJcsRsaSignature2022";
|
pub const PROOF_TYPE_JCS_RSA: &str = "MitraJcsRsaSignature2022";
|
||||||
pub const PROOF_TYPE_JCS_RSA_LEGACY: &str = "JcsRsaSignature2022";
|
|
||||||
|
|
||||||
// https://w3c.github.io/vc-data-integrity/#dataintegrityproof
|
// https://w3c.github.io/vc-data-integrity/#dataintegrityproof
|
||||||
pub const DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof";
|
pub const DATA_INTEGRITY_PROOF: &str = "DataIntegrityProof";
|
||||||
|
@ -41,7 +40,7 @@ impl FromStr for ProofType {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProofType {
|
impl ProofType {
|
||||||
pub fn from_cryptosuite(value: &str) -> Result<Self, ConversionError> {
|
pub fn from_cryptosuite(_value: &str) -> Result<Self, ConversionError> {
|
||||||
Err(ConversionError)
|
Err(ConversionError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,11 @@ use fedimovies_utils::{
|
||||||
canonicalization::{canonicalize_object, CanonicalizationError},
|
canonicalization::{canonicalize_object, CanonicalizationError},
|
||||||
crypto_rsa::verify_rsa_sha256_signature,
|
crypto_rsa::verify_rsa_sha256_signature,
|
||||||
did::Did,
|
did::Did,
|
||||||
did_key::DidKey,
|
|
||||||
did_pkh::DidPkh,
|
|
||||||
multibase::{decode_multibase_base58btc, MultibaseError},
|
multibase::{decode_multibase_base58btc, MultibaseError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::create::{IntegrityProof, PROOF_KEY, PROOF_PURPOSE};
|
use super::create::{IntegrityProof, PROOF_KEY, PROOF_PURPOSE};
|
||||||
use super::proofs::{ProofType, DATA_INTEGRITY_PROOF};
|
use super::proofs::{ProofType, DATA_INTEGRITY_PROOF};
|
||||||
use crate::identity::minisign::verify_minisign_signature;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum JsonSigner {
|
pub enum JsonSigner {
|
||||||
|
|
|
@ -4,7 +4,6 @@ pub mod atom;
|
||||||
mod errors;
|
mod errors;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
mod http_signatures;
|
mod http_signatures;
|
||||||
mod identity;
|
|
||||||
mod ipfs;
|
mod ipfs;
|
||||||
pub mod job_queue;
|
pub mod job_queue;
|
||||||
mod json_signatures;
|
mod json_signatures;
|
||||||
|
|
|
@ -11,22 +11,18 @@ use fedimovies_models::{
|
||||||
profiles::queries::{
|
profiles::queries::{
|
||||||
get_profile_by_acct, get_profile_by_id, search_profiles_by_did, update_profile,
|
get_profile_by_acct, get_profile_by_id, search_profiles_by_did, update_profile,
|
||||||
},
|
},
|
||||||
profiles::types::{IdentityProof, IdentityProofType, ProfileUpdateData},
|
|
||||||
relationships::queries::{
|
relationships::queries::{
|
||||||
get_followers_paginated, get_following_paginated, hide_replies, hide_reposts, show_replies,
|
get_followers_paginated, get_following_paginated, hide_replies, hide_reposts, show_replies,
|
||||||
show_reposts, unfollow,
|
show_reposts, unfollow,
|
||||||
},
|
},
|
||||||
subscriptions::queries::get_incoming_subscriptions,
|
subscriptions::queries::get_incoming_subscriptions,
|
||||||
users::queries::{create_user, get_user_by_did, is_valid_invite_code},
|
users::queries::{create_user, is_valid_invite_code},
|
||||||
users::types::{Role, UserCreateData},
|
users::types::{Role, UserCreateData},
|
||||||
};
|
};
|
||||||
use fedimovies_utils::{
|
use fedimovies_utils::{
|
||||||
caip2::ChainId,
|
|
||||||
canonicalization::canonicalize_object,
|
canonicalization::canonicalize_object,
|
||||||
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
crypto_rsa::{generate_rsa_key, serialize_private_key},
|
||||||
currencies::Currency,
|
|
||||||
did::Did,
|
did::Did,
|
||||||
did_pkh::DidPkh,
|
|
||||||
id::generate_ulid,
|
id::generate_ulid,
|
||||||
passwords::hash_password,
|
passwords::hash_password,
|
||||||
};
|
};
|
||||||
|
@ -34,25 +30,16 @@ use fedimovies_utils::{
|
||||||
use super::helpers::{get_aliases, get_relationship};
|
use super::helpers::{get_aliases, get_relationship};
|
||||||
use super::types::{
|
use super::types::{
|
||||||
Account, AccountCreateData, AccountUpdateData, ActivityParams, ApiSubscription, FollowData,
|
Account, AccountCreateData, AccountUpdateData, ActivityParams, ApiSubscription, FollowData,
|
||||||
FollowListQueryParams, IdentityClaim, IdentityClaimQueryParams, IdentityProofData,
|
FollowListQueryParams, LookupAcctQueryParams, RelationshipQueryParams, SearchAcctQueryParams,
|
||||||
LookupAcctQueryParams, RelationshipQueryParams, SearchAcctQueryParams, SearchDidQueryParams,
|
SearchDidQueryParams, StatusListQueryParams, UnsignedActivity,
|
||||||
SignedActivity, StatusListQueryParams, UnsignedActivity,
|
|
||||||
};
|
};
|
||||||
use crate::activitypub::{
|
use crate::activitypub::builders::{
|
||||||
builders::{
|
follow::follow_or_create_request,
|
||||||
follow::follow_or_create_request,
|
undo_follow::prepare_undo_follow,
|
||||||
undo_follow::prepare_undo_follow,
|
update_person::{build_update_person, prepare_update_person},
|
||||||
update_person::{build_update_person, prepare_update_person},
|
|
||||||
},
|
|
||||||
identifiers::local_actor_id,
|
|
||||||
};
|
};
|
||||||
use crate::errors::ValidationError;
|
use crate::errors::ValidationError;
|
||||||
use crate::http::{get_request_base_url, FormOrJson};
|
use crate::http::{get_request_base_url, FormOrJson};
|
||||||
use crate::identity::{
|
|
||||||
claims::create_identity_claim,
|
|
||||||
minisign::{minisign_key_to_did, parse_minisign_signature, verify_minisign_signature},
|
|
||||||
};
|
|
||||||
use crate::json_signatures::create::IntegrityProof;
|
|
||||||
use crate::mastodon_api::{
|
use crate::mastodon_api::{
|
||||||
errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response,
|
errors::MastodonError, oauth::auth::get_current_user, pagination::get_paginated_response,
|
||||||
search::helpers::search_profiles_only, statuses::helpers::build_status_list,
|
search::helpers::search_profiles_only, statuses::helpers::build_status_list,
|
||||||
|
|
Loading…
Reference in a new issue