Compare commits

...

2 Commits

Author SHA1 Message Date
9e795eefdc fix: make ThoughtNote url field optional for AP compat
Some checks failed
lint / lint (push) Failing after 5m1s
test / integration (push) Has been cancelled
test / unit (push) Has been cancelled
2026-05-17 11:57:10 +02:00
18cf2c9f54 feat: implement verify() for all stub activity handlers
Undo: inner activity actor must match Undo actor
Announce/Like/Block: verify_domains_match(activity_id, actor_url)
Add: attributedTo must match actor (same as Create/Update)
2026-05-17 11:55:17 +02:00
2 changed files with 23 additions and 2 deletions

View File

@@ -4,6 +4,7 @@ use activitypub_federation::{
kinds::activity::{
AcceptType, CreateType, DeleteType, FollowType, RejectType, UndoType, UpdateType,
},
protocol::verification::verify_domains_match,
traits::Activity,
};
use serde::{Deserialize, Serialize};
@@ -239,6 +240,14 @@ impl Activity for UndoActivity {
}
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
// The actor undoing must be the same as the actor in the wrapped activity.
if let Some(inner_actor) = self.object.get("actor").and_then(|v| v.as_str()) {
if inner_actor != self.actor.inner().as_str() {
return Err(Error::bad_request(anyhow::anyhow!(
"Undo actor does not match inner activity actor"
)));
}
}
Ok(())
}
@@ -570,6 +579,7 @@ impl Activity for AnnounceActivity {
}
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
verify_domains_match(&self.id, self.actor.inner())?;
Ok(())
}
@@ -633,6 +643,7 @@ impl Activity for LikeActivity {
}
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
verify_domains_match(&self.id, self.actor.inner())?;
Ok(())
}
@@ -692,6 +703,14 @@ impl Activity for AddActivity {
}
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
if let Some(attributed_to) = self.object.get("attributedTo").and_then(|v| v.as_str())
&& let Ok(attributed_url) = Url::parse(attributed_to)
&& &attributed_url != self.actor.inner()
{
return Err(Error::bad_request(anyhow::anyhow!(
"Add actor does not match object attributedTo"
)));
}
Ok(())
}
@@ -742,6 +761,7 @@ impl Activity for BlockActivity {
}
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
verify_domains_match(&self.id, self.actor.inner())?;
Ok(())
}

View File

@@ -11,7 +11,8 @@ pub struct ThoughtNote {
#[serde(rename = "type")]
pub kind: NoteType,
pub id: Url,
pub url: Url, // Mastodon uses this as the clickable link
#[serde(skip_serializing_if = "Option::is_none", default)]
pub url: Option<Url>,
pub attributed_to: Url,
pub content: String,
pub published: DateTime<Utc>,
@@ -42,7 +43,7 @@ impl ThoughtNote {
) -> Self {
Self {
kind: Default::default(),
url: id.clone(),
url: Some(id.clone()),
id,
attributed_to: actor_url,
content,