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
// TODO return Option<&str> and avoid inner clone
pub fn id(&self) -> Option<String> {
match self {
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
// in tracing and use the library's features but ehhhh
tracing::debug!("emitting error response: {self:?}");
let descr = self.to_string();
match self {
UpubError::Redirect(to) => Redirect::to(&to).into_response(),
UpubError::Status(status) => status.into_response(),
@ -94,7 +95,7 @@ impl axum::response::IntoResponse for UpubError {
StatusCode::SERVICE_UNAVAILABLE,
axum::Json(serde_json::json!({
"error": "database",
"description": format!("{e:#?}"),
"inner": format!("{e:#?}"),
}))
).into_response(),
UpubError::Reqwest(x) | UpubError::FetchError(x, _) => (
@ -103,7 +104,8 @@ impl axum::response::IntoResponse for UpubError {
"error": "request",
"status": x.status().map(|s| s.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(),
UpubError::Field(x) => (
@ -111,7 +113,7 @@ impl axum::response::IntoResponse for UpubError {
axum::Json(serde_json::json!({
"error": "field",
"field": x.0.to_string(),
"description": format!("missing required field from request: '{}'", x.0),
"description": descr,
}))
).into_response(),
UpubError::Mismatch(expected, found) => (
@ -120,14 +122,15 @@ impl axum::response::IntoResponse for UpubError {
"error": "type",
"expected": expected.as_ref().to_string(),
"found": found.as_ref().to_string(),
"description": self.to_string(),
"description": descr,
}))
).into_response(),
_ => (
x => (
StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(serde_json::json!({
"error": "unknown",
"description": self.to_string(),
"description": descr,
"inner": format!("{x:#?}"),
}))
).into_response(),
}

View file

@ -10,6 +10,7 @@ use super::{fetcher::Fetcher, httpsign::HttpSignature};
pub enum Identity {
Anonymous,
Remote {
user: String,
domain: String,
internal: i64,
},
@ -114,6 +115,7 @@ where
// TODO assert payload's digest is equal to signature's
let user_id = http_signature.key_id
.replace("/main-key", "") // gotosocial whyyyyy
.split('#')
.next().ok_or(UpubError::bad_request())?
.to_string();
@ -124,11 +126,11 @@ where
.verify(&user.public_key)
{
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);
// 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?;
identity = Identity::Remote { domain, internal };
identity = Identity::Remote { user, domain, internal };
},
Ok(false) => tracing::warn!("invalid signature: {http_signature:?}"),
Err(e) => tracing::error!("error verifying signature: {e}"),

View file

@ -131,7 +131,7 @@ impl Fetcher for Context {
let document = Self::request(
Method::GET, id, None,
&format!("https://{}", self.domain()), self.pkey(), self.domain(),
&format!("https://{}/", self.domain()), self.pkey(), self.domain(),
)
.await?
.json::<serde_json::Value>()
@ -207,7 +207,8 @@ impl Fetcher for Context {
};
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 {
if let Ok(actor) = res.json::<serde_json::Value>().await {
if let Some(name) = actor.name() {
@ -243,7 +244,7 @@ impl Fetcher for Context {
if let Some(followers_url) = &document.followers().id() {
let req = Self::request(
Method::GET, followers_url, None,
&format!("https://{}", self.domain()), self.pkey(), self.domain(),
&format!("https://{}/", self.domain()), self.pkey(), self.domain(),
).await;
if let Ok(res) = req {
if let Ok(user_followers) = res.json::<serde_json::Value>().await {
@ -255,9 +256,9 @@ impl Fetcher for Context {
}
if let Some(following_url) = &document.following().id() {
let req = Self::request(
let req = Self::request(
Method::GET, following_url, None,
&format!("https://{}", self.domain()), self.pkey(), self.domain(),
&format!("https://{}/", self.domain()), self.pkey(), self.domain(),
).await;
if let Ok(res) = req {
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 sea_orm::{sea_query::IntoCondition, ColumnTrait};
use upub::{server::auth::{AuthIdentity, Identity}, Context};
@ -43,7 +43,7 @@ pub async fn post(
AuthIdentity(auth): AuthIdentity,
Json(activity): Json<serde_json::Value>
) -> 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) {
// this is spammy af, ignore them!
// 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 {
return Err(upub::Error::bad_request());
};
let aid = activity.id().ok_or_else(|| upub::Error::field("id"))?.to_string();
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());
}
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?
match activity.activity_type().ok_or_else(upub::Error::bad_request)? {

View file

@ -45,51 +45,39 @@ pub fn Item(
let config = use_context::<Signal<crate::Config>>().expect("missing config context");
let id = item.id().unwrap_or_default().to_string();
let sep = if sep { Some(view! { <hr /> }) } else { None };
match item.object_type() {
if !replies && !config.get().filters.visible(&item) {
return None;
}
match item.object_type().unwrap_or(apb::ObjectType::Object) {
// special case for placeholder activities
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(),
apb::ObjectType::Note | apb::ObjectType::Document(_) =>
Some(view! { <Object object=item.clone() />{sep.clone()} }.into_view()),
// everything else
Some(apb::ObjectType::Activity(t)) => (move || {
if config.get().filters.visible(apb::ObjectType::Activity(t)) {
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 {
apb::ActivityType::Create | apb::ActivityType::Announce =>
CACHE.get(&object_id).map(|obj| {
view! { <Object object=obj /> }
}.into_view()),
apb::ActivityType::Follow =>
CACHE.get(&object_id).map(|obj| {
view! {
<div class="ml-1">
<ActorBanner object=obj />
<FollowRequestButtons activity_id=id.clone() actor_id=object_id />
</div>
}
}.into_view()),
_ => None,
};
Some(view! {
<ActivityLine activity=item.clone() />
{object}
{sep.clone()}
})
}
} else {
None
}
}).into_view(),
apb::ObjectType::Activity(t) => {
let object_id = item.object().id().unwrap_or_default();
let object = match t {
apb::ActivityType::Create | apb::ActivityType::Announce =>
CACHE.get(&object_id).map(|obj| {
view! { <Object object=obj /> }
}.into_view()),
apb::ActivityType::Follow =>
CACHE.get(&object_id).map(|obj| {
view! {
<div class="ml-1">
<ActorBanner object=obj />
<FollowRequestButtons activity_id=id.clone() actor_id=object_id />
</div>
}
}.into_view()),
_ => None,
};
Some(view! {
<ActivityLine activity=item.clone() />
{object}
{sep.clone()}
}.into_view())
},
// 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)]
pub follows: bool,
#[serde_inline_default(false)]
pub updates: bool,
#[serde_inline_default(true)]
pub orphans: bool,
}
impl FiltersConfig {
pub fn visible(&self, object_type: apb::ObjectType) -> bool {
match object_type {
pub fn visible(&self, item: &crate::Object) -> bool {
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::Activity(apb::ActivityType::Like | apb::ActivityType::EmojiReact) => self.likes,
apb::ObjectType::Activity(apb::ActivityType::Create) => self.creates,
apb::ObjectType::Activity(apb::ActivityType::Announce) => self.announces,
apb::ObjectType::Activity(apb::ActivityType::Update) => self.updates,
apb::ObjectType::Activity(
apb::ActivityType::Follow | apb::ActivityType::Accept(_) | apb::ActivityType::Reject(_)
) => self.follows,
_ => 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
}
}