forked from alemi/upub
chore: merge branch 'dev' of alemi/upub into dev
This commit is contained in:
commit
0097a0533a
7 changed files with 78 additions and 66 deletions
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}"),
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)? {
|
||||||
|
|
|
@ -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()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue