hyaenidae/server/src/apub.rs

132 lines
3.4 KiB
Rust
Raw Normal View History

use crate::{error::Error, State};
2021-01-06 08:21:37 +00:00
use activitystreams::base::AnyBase;
use actix_web::{web, HttpResponse, Scope};
use hyaenidae_profiles::{apub::ApubIds, Spawner};
use sled::Tree;
use url::Url;
use uuid::Uuid;
#[derive(Clone)]
pub(super) struct Apub {
base_url: Url,
uuid_url: Tree,
url_uuid: Tree,
}
pub(super) fn scope() -> Scope {
web::scope("/apub")
.service(
web::scope("/objects/{id}")
.route("", web::get().to(serve_object))
.route("/inbox", web::post().to(inbox)),
)
.route("/inbox", web::post().to(shared_inbox))
}
// TODO: Authorized fetch
async fn serve_object(
path: web::Path<Uuid>,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
let url = match state.apub.id_for_uuid(path.into_inner())? {
2021-01-06 08:21:37 +00:00
Some(url) => url,
None => return Ok(crate::to_404()),
};
let object = match state.profiles.apub.serve_object(&url)? {
Some(object) => object,
None => return Ok(crate::to_404()),
};
Ok(HttpResponse::Ok()
.content_type("application/activity+json")
.json(&object))
}
async fn inbox(body: web::Json<AnyBase>, state: web::Data<State>) -> Result<HttpResponse, Error> {
do_inbox(body.into_inner(), &state).await
2021-01-06 08:21:37 +00:00
}
async fn shared_inbox(
body: web::Json<AnyBase>,
state: web::Data<State>,
) -> Result<HttpResponse, Error> {
do_inbox(body.into_inner(), &state).await
2021-01-06 08:21:37 +00:00
}
// TODO: signature validation, Actor check
async fn do_inbox(any_base: AnyBase, state: &State) -> Result<HttpResponse, Error> {
state.spawn.process(any_base, vec![]);
Ok(HttpResponse::Created().finish())
}
impl Apub {
pub(super) fn build(base_url: Url, db: &sled::Db) -> Result<Self, sled::Error> {
Ok(Apub {
base_url,
uuid_url: db.open_tree("/main/apub/uuid_url")?,
url_uuid: db.open_tree("/main/apub/url_uuid")?,
})
}
fn id_for_uuid(&self, uuid: Uuid) -> Result<Option<Url>, Error> {
Ok(self.uuid_url.get(uuid.as_bytes())?.and_then(url_from_ivec))
}
}
impl ApubIds for Apub {
fn gen_id(&self) -> Option<Url> {
let mut url = self.base_url.clone();
let mut uuid;
while {
uuid = Uuid::new_v4();
url.set_path(&format!("/apub/objects/{}", uuid));
self.uuid_url
.compare_and_swap(uuid.as_bytes(), None as Option<&[u8]>, Some(url_key(&url)))
.ok()?
.is_err()
} {}
self.url_uuid.insert(url_key(&url), uuid.as_bytes()).ok()?;
Some(url)
}
fn public_key(&self, id: &Url) -> Option<Url> {
let mut url = id.to_owned();
url.set_fragment(Some("main-key"));
Some(url)
}
fn following(&self, id: &Url) -> Option<Url> {
Url::parse(&format!("{}/following", id)).ok()
}
fn followers(&self, id: &Url) -> Option<Url> {
Url::parse(&format!("{}/followers", id)).ok()
}
fn inbox(&self, id: &Url) -> Option<Url> {
Url::parse(&format!("{}/inbox", id)).ok()
}
fn outbox(&self, id: &Url) -> Option<Url> {
Url::parse(&format!("{}/outbox", id)).ok()
}
fn shared_inbox(&self) -> Url {
let mut url = self.base_url.clone();
url.set_path("/apub/inbox");
url
}
}
fn url_from_ivec(ivec: sled::IVec) -> Option<Url> {
String::from_utf8_lossy(&ivec).parse().ok()
}
fn url_key(url: &Url) -> &[u8] {
url.as_str().as_bytes()
}