feat: implemented authed inbox

This commit is contained in:
əlemi 2024-04-22 22:52:05 +02:00
parent a763b60a93
commit 8dece0f004
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 69 additions and 27 deletions

View file

@ -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));

View file

@ -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());