forked from alemi/upub
feat: implemented authed inbox
This commit is contained in:
parent
a763b60a93
commit
8dece0f004
2 changed files with 69 additions and 27 deletions
|
@ -51,15 +51,24 @@ pub async fn post(
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Json(activity): Json<serde_json::Value>
|
Json(activity): Json<serde_json::Value>
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
if !matches!(auth, Identity::Remote(_)) {
|
let Identity::Remote(server) = auth else {
|
||||||
if activity.activity_type() != Some(ActivityType::Delete) { // this is spammy af, ignore them!
|
if activity.activity_type() != Some(ActivityType::Delete) { // this is spammy af, ignore them!
|
||||||
tracing::warn!("refusing unauthorized activity: {}", pretty_json!(activity));
|
tracing::warn!("refusing unauthorized activity: {}", pretty_json!(activity));
|
||||||
}
|
}
|
||||||
match auth {
|
if matches!(auth, Identity::Anonymous) {
|
||||||
Identity::Local(_user) => return Err(UpubError::forbidden()),
|
return Err(UpubError::unauthorized());
|
||||||
Identity::Anonymous => return Err(UpubError::unauthorized()),
|
} else {
|
||||||
_ => {},
|
return Err(UpubError::forbidden());
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(actor) = activity.actor().id() else {
|
||||||
|
return Err(UpubError::bad_request());
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO add whitelist of relays
|
||||||
|
if !server.ends_with(&Context::server(&actor)) {
|
||||||
|
return Err(UpubError::unauthorized());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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?
|
||||||
|
@ -69,15 +78,15 @@ pub async fn post(
|
||||||
Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff
|
Err(StatusCode::UNPROCESSABLE_ENTITY.into()) // won't ingest useless stuff
|
||||||
},
|
},
|
||||||
|
|
||||||
ActivityType::Delete => Ok(ctx.delete(activity).await?),
|
ActivityType::Create => Ok(ctx.create(server, activity).await?),
|
||||||
ActivityType::Follow => Ok(ctx.follow(activity).await?),
|
ActivityType::Like => Ok(ctx.like(server, activity).await?),
|
||||||
ActivityType::Accept(_) => Ok(ctx.accept(activity).await?),
|
ActivityType::Follow => Ok(ctx.follow(server, activity).await?),
|
||||||
ActivityType::Reject(_) => Ok(ctx.reject(activity).await?),
|
ActivityType::Announce => Ok(ctx.announce(server, activity).await?),
|
||||||
ActivityType::Like => Ok(ctx.like(activity).await?),
|
ActivityType::Accept(_) => Ok(ctx.accept(server, activity).await?),
|
||||||
ActivityType::Create => Ok(ctx.create(activity).await?),
|
ActivityType::Reject(_) => Ok(ctx.reject(server, activity).await?),
|
||||||
ActivityType::Update => Ok(ctx.update(activity).await?),
|
ActivityType::Undo => Ok(ctx.undo(server, activity).await?),
|
||||||
ActivityType::Undo => Ok(ctx.undo(activity).await?),
|
ActivityType::Delete => Ok(ctx.delete(server, activity).await?),
|
||||||
ActivityType::Announce => Ok(ctx.announce(activity).await?),
|
ActivityType::Update => Ok(ctx.update(server, activity).await?),
|
||||||
|
|
||||||
_x => {
|
_x => {
|
||||||
tracing::info!("received unimplemented activity on inbox: {}", pretty_json!(activity));
|
tracing::info!("received unimplemented activity on inbox: {}", pretty_json!(activity));
|
||||||
|
|
|
@ -12,14 +12,35 @@ impl apb::server::Inbox for Context {
|
||||||
type Error = UpubError;
|
type Error = UpubError;
|
||||||
type Activity = serde_json::Value;
|
type Activity = serde_json::Value;
|
||||||
|
|
||||||
async fn create(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn create(&self, server: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
|
let aid = activity_model.id.clone();
|
||||||
let Some(object_node) = activity.object().extract() else {
|
let Some(object_node) = activity.object().extract() else {
|
||||||
// TODO we could process non-embedded activities or arrays but im lazy rn
|
// TODO we could process non-embedded activities or arrays but im lazy rn
|
||||||
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&activity).unwrap());
|
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&activity).unwrap());
|
||||||
return Err(UpubError::unprocessable());
|
return Err(UpubError::unprocessable());
|
||||||
};
|
};
|
||||||
let object_model = model::object::Model::new(&object_node)?;
|
let mut object_model = model::object::Model::new(&object_node)?;
|
||||||
|
let oid = object_model.id.clone();
|
||||||
|
// make sure we're allowed to edit this object
|
||||||
|
if let Some(object_author) = &object_model.attributed_to {
|
||||||
|
if server != Context::server(object_author) {
|
||||||
|
return Err(UpubError::forbidden());
|
||||||
|
}
|
||||||
|
} else if server != Context::server(&object_model.id) {
|
||||||
|
return Err(UpubError::forbidden());
|
||||||
|
};
|
||||||
|
// fix context also for remote posts
|
||||||
|
// TODO this is not really appropriate because we're mirroring incorrectly remote objects, but
|
||||||
|
// it makes it SOO MUCH EASIER for us to fetch threads and stuff, so we're filling it for them
|
||||||
|
match (&object_model.in_reply_to, &object_model.context) {
|
||||||
|
(Some(reply_id), None) => // get context from replied object
|
||||||
|
object_model.context = self.fetch_object(reply_id).await?.context,
|
||||||
|
(None, None) => // generate a new context
|
||||||
|
object_model.context = Some(crate::url!(self, "/context/{}", uuid::Uuid::new_v4().to_string())),
|
||||||
|
(_, Some(_)) => {}, // leave it as set by user
|
||||||
|
}
|
||||||
|
// update replies counter
|
||||||
if let Some(ref in_reply_to) = object_model.in_reply_to {
|
if let Some(ref in_reply_to) = object_model.in_reply_to {
|
||||||
if self.fetch_object(in_reply_to).await.is_ok() {
|
if self.fetch_object(in_reply_to).await.is_ok() {
|
||||||
model::object::Entity::update_many()
|
model::object::Entity::update_many()
|
||||||
|
@ -29,8 +50,6 @@ impl apb::server::Inbox for Context {
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let aid = activity_model.id.clone();
|
|
||||||
let oid = object_model.id.clone();
|
|
||||||
model::object::Entity::insert(object_model.into_active_model()).exec(self.db()).await?;
|
model::object::Entity::insert(object_model.into_active_model()).exec(self.db()).await?;
|
||||||
model::activity::Entity::insert(activity_model.into_active_model()).exec(self.db()).await?;
|
model::activity::Entity::insert(activity_model.into_active_model()).exec(self.db()).await?;
|
||||||
for attachment in object_node.attachment() {
|
for attachment in object_node.attachment() {
|
||||||
|
@ -45,7 +64,7 @@ impl apb::server::Inbox for Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn like(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn like(&self, _: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let aid = activity.id().ok_or(UpubError::bad_request())?;
|
let aid = activity.id().ok_or(UpubError::bad_request())?;
|
||||||
let uid = activity.actor().id().ok_or(UpubError::bad_request())?;
|
let uid = activity.actor().id().ok_or(UpubError::bad_request())?;
|
||||||
let oid = activity.object().id().ok_or(UpubError::bad_request())?;
|
let oid = activity.object().id().ok_or(UpubError::bad_request())?;
|
||||||
|
@ -83,7 +102,7 @@ impl apb::server::Inbox for Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn follow(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn follow(&self, _: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
let aid = activity_model.id.clone();
|
let aid = activity_model.id.clone();
|
||||||
tracing::info!("{} wants to follow {}", activity_model.actor, activity_model.object.as_deref().unwrap_or("<no-one???>"));
|
tracing::info!("{} wants to follow {}", activity_model.actor, activity_model.object.as_deref().unwrap_or("<no-one???>"));
|
||||||
|
@ -94,7 +113,7 @@ impl apb::server::Inbox for Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn accept(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn accept(&self, _: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
// TODO what about TentativeAccept
|
// TODO what about TentativeAccept
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
let Some(follow_request_id) = &activity_model.object else {
|
let Some(follow_request_id) = &activity_model.object else {
|
||||||
|
@ -135,7 +154,7 @@ impl apb::server::Inbox for Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn reject(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn reject(&self, _: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
// TODO what about TentativeReject?
|
// TODO what about TentativeReject?
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
let Some(follow_request_id) = &activity_model.object else {
|
let Some(follow_request_id) = &activity_model.object else {
|
||||||
|
@ -161,7 +180,7 @@ impl apb::server::Inbox for Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn delete(&self, _: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
// TODO verify the signature before just deleting lmao
|
// TODO verify the signature before just deleting lmao
|
||||||
let oid = activity.object().id().ok_or(UpubError::bad_request())?;
|
let oid = activity.object().id().ok_or(UpubError::bad_request())?;
|
||||||
tracing::debug!("deleting '{oid}'"); // this is so spammy wtf!
|
tracing::debug!("deleting '{oid}'"); // this is so spammy wtf!
|
||||||
|
@ -172,17 +191,25 @@ impl apb::server::Inbox for Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn update(&self, server: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
|
let aid = activity_model.id.clone();
|
||||||
let Some(object_node) = activity.object().extract() else {
|
let Some(object_node) = activity.object().extract() else {
|
||||||
// TODO we could process non-embedded activities or arrays but im lazy rn
|
// TODO we could process non-embedded activities or arrays but im lazy rn
|
||||||
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&activity).unwrap());
|
tracing::error!("refusing to process activity without embedded object: {}", serde_json::to_string_pretty(&activity).unwrap());
|
||||||
return Err(UpubError::unprocessable());
|
return Err(UpubError::unprocessable());
|
||||||
};
|
};
|
||||||
let aid = activity_model.id.clone();
|
|
||||||
let Some(oid) = object_node.id().map(|x| x.to_string()) else {
|
let Some(oid) = object_node.id().map(|x| x.to_string()) else {
|
||||||
return Err(UpubError::bad_request());
|
return Err(UpubError::bad_request());
|
||||||
};
|
};
|
||||||
|
// make sure we're allowed to edit this object
|
||||||
|
if let Some(object_author) = object_node.attributed_to().id() {
|
||||||
|
if server != Context::server(&object_author) {
|
||||||
|
return Err(UpubError::forbidden());
|
||||||
|
}
|
||||||
|
} else if server != Context::server(&oid) {
|
||||||
|
return Err(UpubError::forbidden());
|
||||||
|
};
|
||||||
match object_node.object_type() {
|
match object_node.object_type() {
|
||||||
Some(apb::ObjectType::Actor(_)) => {
|
Some(apb::ObjectType::Actor(_)) => {
|
||||||
// TODO oof here is an example of the weakness of this model, we have to go all the way
|
// TODO oof here is an example of the weakness of this model, we have to go all the way
|
||||||
|
@ -209,13 +236,19 @@ impl apb::server::Inbox for Context {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn undo(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn undo(&self, server: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let uid = activity.actor().id().ok_or_else(UpubError::bad_request)?;
|
let uid = activity.actor().id().ok_or_else(UpubError::bad_request)?;
|
||||||
// TODO in theory we could work with just object_id but right now only accept embedded
|
// TODO in theory we could work with just object_id but right now only accept embedded
|
||||||
let undone_activity = activity.object().extract().ok_or_else(UpubError::bad_request)?;
|
let undone_activity = activity.object().extract().ok_or_else(UpubError::bad_request)?;
|
||||||
let undone_aid = undone_activity.id().ok_or_else(UpubError::bad_request)?;
|
let undone_aid = undone_activity.id().ok_or_else(UpubError::bad_request)?;
|
||||||
let undone_object_id = undone_activity.object().id().ok_or_else(UpubError::bad_request)?;
|
let undone_object_id = undone_activity.object().id().ok_or_else(UpubError::bad_request)?;
|
||||||
let activity_type = undone_activity.activity_type().ok_or_else(UpubError::bad_request)?;
|
let activity_type = undone_activity.activity_type().ok_or_else(UpubError::bad_request)?;
|
||||||
|
let undone_activity_author = undone_activity.actor().id().ok_or_else(UpubError::bad_request)?;
|
||||||
|
|
||||||
|
// can't undo activities from remote actors!
|
||||||
|
if server != Context::server(&undone_activity_author) {
|
||||||
|
return Err(UpubError::forbidden());
|
||||||
|
};
|
||||||
|
|
||||||
match activity_type {
|
match activity_type {
|
||||||
apb::ActivityType::Like => {
|
apb::ActivityType::Like => {
|
||||||
|
@ -255,7 +288,7 @@ impl apb::server::Inbox for Context {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn announce(&self, activity: serde_json::Value) -> crate::Result<()> {
|
async fn announce(&self, _: String, activity: serde_json::Value) -> crate::Result<()> {
|
||||||
let activity_model = model::activity::Model::new(&activity)?;
|
let activity_model = model::activity::Model::new(&activity)?;
|
||||||
let Some(oid) = &activity_model.object else {
|
let Some(oid) = &activity_model.object else {
|
||||||
return Err(FieldError("object").into());
|
return Err(FieldError("object").into());
|
||||||
|
|
Loading…
Reference in a new issue