chore: merge branch 'dev' of alemi/upub into dev

This commit is contained in:
əlemi 2024-05-31 16:58:22 +02:00
commit 0097a0533a
Signed by: alemi
GPG key ID: A4895B84D311642C
7 changed files with 78 additions and 66 deletions

View file

@ -99,6 +99,7 @@ impl<T : super::Base> Node<T> {
} }
/// returns id of object: url for link, id for object, None if empty or array /// returns id of object: url for link, id for object, None if empty or array
// TODO return Option<&str> and avoid inner clone
pub fn id(&self) -> Option<String> { pub fn id(&self) -> Option<String> {
match self { match self {
Node::Empty => None, Node::Empty => None,

View file

@ -87,6 +87,7 @@ impl axum::response::IntoResponse for UpubError {
// TODO it's kind of jank to hide this print down here, i should probably learn how spans work // TODO it's kind of jank to hide this print down here, i should probably learn how spans work
// in tracing and use the library's features but ehhhh // in tracing and use the library's features but ehhhh
tracing::debug!("emitting error response: {self:?}"); tracing::debug!("emitting error response: {self:?}");
let descr = self.to_string();
match self { match self {
UpubError::Redirect(to) => Redirect::to(&to).into_response(), UpubError::Redirect(to) => Redirect::to(&to).into_response(),
UpubError::Status(status) => status.into_response(), UpubError::Status(status) => status.into_response(),
@ -94,7 +95,7 @@ impl axum::response::IntoResponse for UpubError {
StatusCode::SERVICE_UNAVAILABLE, StatusCode::SERVICE_UNAVAILABLE,
axum::Json(serde_json::json!({ axum::Json(serde_json::json!({
"error": "database", "error": "database",
"description": format!("{e:#?}"), "inner": format!("{e:#?}"),
})) }))
).into_response(), ).into_response(),
UpubError::Reqwest(x) | UpubError::FetchError(x, _) => ( UpubError::Reqwest(x) | UpubError::FetchError(x, _) => (
@ -103,7 +104,8 @@ impl axum::response::IntoResponse for UpubError {
"error": "request", "error": "request",
"status": x.status().map(|s| s.to_string()).unwrap_or_default(), "status": x.status().map(|s| s.to_string()).unwrap_or_default(),
"url": x.url().map(|x| x.to_string()).unwrap_or_default(), "url": x.url().map(|x| x.to_string()).unwrap_or_default(),
"description": format!("{x:#?}"), "description": descr,
"inner": format!("{x:#?}"),
})) }))
).into_response(), ).into_response(),
UpubError::Field(x) => ( UpubError::Field(x) => (
@ -111,7 +113,7 @@ impl axum::response::IntoResponse for UpubError {
axum::Json(serde_json::json!({ axum::Json(serde_json::json!({
"error": "field", "error": "field",
"field": x.0.to_string(), "field": x.0.to_string(),
"description": format!("missing required field from request: '{}'", x.0), "description": descr,
})) }))
).into_response(), ).into_response(),
UpubError::Mismatch(expected, found) => ( UpubError::Mismatch(expected, found) => (
@ -120,14 +122,15 @@ impl axum::response::IntoResponse for UpubError {
"error": "type", "error": "type",
"expected": expected.as_ref().to_string(), "expected": expected.as_ref().to_string(),
"found": found.as_ref().to_string(), "found": found.as_ref().to_string(),
"description": self.to_string(), "description": descr,
})) }))
).into_response(), ).into_response(),
_ => ( x => (
StatusCode::INTERNAL_SERVER_ERROR, StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(serde_json::json!({ axum::Json(serde_json::json!({
"error": "unknown", "error": "unknown",
"description": self.to_string(), "description": descr,
"inner": format!("{x:#?}"),
})) }))
).into_response(), ).into_response(),
} }

View file

@ -10,6 +10,7 @@ use super::{fetcher::Fetcher, httpsign::HttpSignature};
pub enum Identity { pub enum Identity {
Anonymous, Anonymous,
Remote { Remote {
user: String,
domain: String, domain: String,
internal: i64, internal: i64,
}, },
@ -114,6 +115,7 @@ where
// TODO assert payload's digest is equal to signature's // TODO assert payload's digest is equal to signature's
let user_id = http_signature.key_id let user_id = http_signature.key_id
.replace("/main-key", "") // gotosocial whyyyyy
.split('#') .split('#')
.next().ok_or(UpubError::bad_request())? .next().ok_or(UpubError::bad_request())?
.to_string(); .to_string();
@ -124,11 +126,11 @@ where
.verify(&user.public_key) .verify(&user.public_key)
{ {
Ok(true) => { Ok(true) => {
// TODO can we avoid this extra db rountrip made on each server fetch? let user = user.id;
let domain = Context::server(&user_id); let domain = Context::server(&user_id);
// TODO this will fail because we never fetch and insert into instance oops // TODO this will fail because we never fetch and insert into instance oops
let internal = model::instance::Entity::domain_to_internal(&domain, ctx.db()).await?; let internal = model::instance::Entity::domain_to_internal(&domain, ctx.db()).await?;
identity = Identity::Remote { domain, internal }; identity = Identity::Remote { user, domain, internal };
}, },
Ok(false) => tracing::warn!("invalid signature: {http_signature:?}"), Ok(false) => tracing::warn!("invalid signature: {http_signature:?}"),
Err(e) => tracing::error!("error verifying signature: {e}"), Err(e) => tracing::error!("error verifying signature: {e}"),

View file

@ -131,7 +131,7 @@ impl Fetcher for Context {
let document = Self::request( let document = Self::request(
Method::GET, id, None, Method::GET, id, None,
&format!("https://{}", self.domain()), self.pkey(), self.domain(), &format!("https://{}/", self.domain()), self.pkey(), self.domain(),
) )
.await? .await?
.json::<serde_json::Value>() .json::<serde_json::Value>()
@ -207,7 +207,8 @@ impl Fetcher for Context {
}; };
if let Ok(res) = Self::request( if let Ok(res) = Self::request(
Method::GET, &format!("https://{domain}"), None, &format!("https://{}", self.domain()), self.pkey(), self.domain(), Method::GET, &format!("https://{domain}"), None,
&format!("https://{}/", self.domain()), self.pkey(), self.domain(),
).await { ).await {
if let Ok(actor) = res.json::<serde_json::Value>().await { if let Ok(actor) = res.json::<serde_json::Value>().await {
if let Some(name) = actor.name() { if let Some(name) = actor.name() {
@ -243,7 +244,7 @@ impl Fetcher for Context {
if let Some(followers_url) = &document.followers().id() { if let Some(followers_url) = &document.followers().id() {
let req = Self::request( let req = Self::request(
Method::GET, followers_url, None, Method::GET, followers_url, None,
&format!("https://{}", self.domain()), self.pkey(), self.domain(), &format!("https://{}/", self.domain()), self.pkey(), self.domain(),
).await; ).await;
if let Ok(res) = req { if let Ok(res) = req {
if let Ok(user_followers) = res.json::<serde_json::Value>().await { if let Ok(user_followers) = res.json::<serde_json::Value>().await {
@ -257,7 +258,7 @@ impl Fetcher for Context {
if let Some(following_url) = &document.following().id() { if let Some(following_url) = &document.following().id() {
let req = Self::request( let req = Self::request(
Method::GET, following_url, None, Method::GET, following_url, None,
&format!("https://{}", self.domain()), self.pkey(), self.domain(), &format!("https://{}/", self.domain()), self.pkey(), self.domain(),
).await; ).await;
if let Ok(res) = req { if let Ok(res) = req {
if let Ok(user_following) = res.json::<serde_json::Value>().await { if let Ok(user_following) = res.json::<serde_json::Value>().await {

View file

@ -1,4 +1,4 @@
use apb::{server::Inbox, Activity, ActivityType}; use apb::{server::Inbox, Activity, ActivityType, Base};
use axum::{extract::{Query, State}, http::StatusCode, Json}; use axum::{extract::{Query, State}, http::StatusCode, Json};
use sea_orm::{sea_query::IntoCondition, ColumnTrait}; use sea_orm::{sea_query::IntoCondition, ColumnTrait};
use upub::{server::auth::{AuthIdentity, Identity}, Context}; use upub::{server::auth::{AuthIdentity, Identity}, Context};
@ -43,7 +43,7 @@ pub async fn post(
AuthIdentity(auth): AuthIdentity, AuthIdentity(auth): AuthIdentity,
Json(activity): Json<serde_json::Value> Json(activity): Json<serde_json::Value>
) -> upub::Result<()> { ) -> upub::Result<()> {
let Identity::Remote { domain: server, .. } = auth else { let Identity::Remote { domain: server, user: uid, .. } = auth else {
if activity.activity_type() == Some(ActivityType::Delete) { if activity.activity_type() == Some(ActivityType::Delete) {
// this is spammy af, ignore them! // this is spammy af, ignore them!
// we basically received a delete for a user we can't fetch and verify, meaning remote // we basically received a delete for a user we can't fetch and verify, meaning remote
@ -61,15 +61,14 @@ pub async fn post(
} }
}; };
let Some(actor) = activity.actor().id() else { let aid = activity.id().ok_or_else(|| upub::Error::field("id"))?.to_string();
return Err(upub::Error::bad_request()); let actor = activity.actor().id().ok_or_else(|| upub::Error::field("actor"))?;
};
if server != Context::server(&actor) { if uid != actor {
return Err(upub::Error::unauthorized()); return Err(upub::Error::unauthorized());
} }
tracing::debug!("processing federated activity: '{}'", serde_json::to_string(&activity).unwrap_or_default()); tracing::debug!("processing federated activity: '{:#}'", activity);
// TODO we could process Links and bare Objects maybe, but probably out of AP spec? // TODO we could process Links and bare Objects maybe, but probably out of AP spec?
match activity.activity_type().ok_or_else(upub::Error::bad_request)? { match activity.activity_type().ok_or_else(upub::Error::bad_request)? {

View file

@ -45,24 +45,16 @@ pub fn Item(
let config = use_context::<Signal<crate::Config>>().expect("missing config context"); let config = use_context::<Signal<crate::Config>>().expect("missing config context");
let id = item.id().unwrap_or_default().to_string(); let id = item.id().unwrap_or_default().to_string();
let sep = if sep { Some(view! { <hr /> }) } else { None }; let sep = if sep { Some(view! { <hr /> }) } else { None };
match item.object_type() { if !replies && !config.get().filters.visible(&item) {
// special case for placeholder activities return None;
Some(apb::ObjectType::Note) | Some(apb::ObjectType::Document(_)) => (move || {
if !config.get().filters.replies && item.in_reply_to().id().is_some() {
None
} else if config.get().filters.orphans {
Some(view! { <Object object=item.clone() />{sep.clone()} })
} else {
None
} }
}).into_view(), match item.object_type().unwrap_or(apb::ObjectType::Object) {
// special case for placeholder activities
apb::ObjectType::Note | apb::ObjectType::Document(_) =>
Some(view! { <Object object=item.clone() />{sep.clone()} }.into_view()),
// everything else // everything else
Some(apb::ObjectType::Activity(t)) => (move || { apb::ObjectType::Activity(t) => {
if config.get().filters.visible(apb::ObjectType::Activity(t)) {
let object_id = item.object().id().unwrap_or_default(); let object_id = item.object().id().unwrap_or_default();
if !replies && !config.get().filters.replies && CACHE.get(&object_id).map(|x| x.in_reply_to().id().is_some()).unwrap_or(false) {
None
} else {
let object = match t { let object = match t {
apb::ActivityType::Create | apb::ActivityType::Announce => apb::ActivityType::Create | apb::ActivityType::Announce =>
CACHE.get(&object_id).map(|obj| { CACHE.get(&object_id).map(|obj| {
@ -83,13 +75,9 @@ pub fn Item(
<ActivityLine activity=item.clone() /> <ActivityLine activity=item.clone() />
{object} {object}
{sep.clone()} {sep.clone()}
}) }.into_view())
} },
} else {
None
}
}).into_view(),
// should never happen // should never happen
_ => view! { <p><code>type not implemented</code></p> }.into_view(), t => Some(view! { <p><code>type not implemented : {t.as_ref().to_string()}</code></p> }.into_view()),
} }
} }

View file

@ -36,21 +36,39 @@ pub struct FiltersConfig {
#[serde_inline_default(true)] #[serde_inline_default(true)]
pub follows: bool, pub follows: bool,
#[serde_inline_default(false)]
pub updates: bool,
#[serde_inline_default(true)] #[serde_inline_default(true)]
pub orphans: bool, pub orphans: bool,
} }
impl FiltersConfig { impl FiltersConfig {
pub fn visible(&self, object_type: apb::ObjectType) -> bool { pub fn visible(&self, item: &crate::Object) -> bool {
match object_type { use apb::{Object, Activity};
let type_filter = match item.object_type().unwrap_or(apb::ObjectType::Object) {
apb::ObjectType::Note | apb::ObjectType::Document(_) => self.orphans, apb::ObjectType::Note | apb::ObjectType::Document(_) => self.orphans,
apb::ObjectType::Activity(apb::ActivityType::Like | apb::ActivityType::EmojiReact) => self.likes, apb::ObjectType::Activity(apb::ActivityType::Like | apb::ActivityType::EmojiReact) => self.likes,
apb::ObjectType::Activity(apb::ActivityType::Create) => self.creates, apb::ObjectType::Activity(apb::ActivityType::Create) => self.creates,
apb::ObjectType::Activity(apb::ActivityType::Announce) => self.announces, apb::ObjectType::Activity(apb::ActivityType::Announce) => self.announces,
apb::ObjectType::Activity(apb::ActivityType::Update) => self.updates,
apb::ObjectType::Activity( apb::ObjectType::Activity(
apb::ActivityType::Follow | apb::ActivityType::Accept(_) | apb::ActivityType::Reject(_) apb::ActivityType::Follow | apb::ActivityType::Accept(_) | apb::ActivityType::Reject(_)
) => self.follows, ) => self.follows,
_ => true, _ => true,
} };
let mut reply_filter = true;
if
item.in_reply_to().id().is_some() ||
item.object().get().map(|x|
x.in_reply_to().id().is_some()
).unwrap_or(false)
{
reply_filter = self.replies;
};
type_filter && reply_filter
} }
} }