diff --git a/Cargo.toml b/Cargo.toml index 55682f8..4803fb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ tokio = { version = "1.35.1", features = ["full"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" uuid = { version = "1.8.0", features = ["v4"] } +jrd = "0.1" diff --git a/src/activitypub/mod.rs b/src/activitypub/mod.rs index 195ec0e..8225152 100644 --- a/src/activitypub/mod.rs +++ b/src/activitypub/mod.rs @@ -1,6 +1,8 @@ pub mod user; pub mod object; pub mod activity; +pub mod well_known; + pub mod jsonld; pub use jsonld::JsonLD; diff --git a/src/activitypub/well_known.rs b/src/activitypub/well_known.rs new file mode 100644 index 0000000..5c8f52d --- /dev/null +++ b/src/activitypub/well_known.rs @@ -0,0 +1,58 @@ +use axum::{extract::{Query, State}, http::StatusCode, response::{IntoResponse, Response}}; +use jrd::{JsonResourceDescriptor, JsonResourceDescriptorLink}; +use sea_orm::EntityTrait; + +use crate::{server::Context, model}; + +#[derive(Debug, serde::Deserialize)] +pub struct WebfingerQuery { + pub resource: String, +} + +pub struct JsonRD(pub T); +impl IntoResponse for JsonRD { + fn into_response(self) -> Response { + ([("Content-Type", "application/jrd+json")], axum::Json(self.0)).into_response() + } +} + +pub async fn webfinger(State(ctx): State, Query(query): Query) -> Result, StatusCode> { + if let Some((user, domain)) = query.resource.split_once('@') { + let uid = ctx.uid(user.to_string()); + match model::user::Entity::find_by_id(uid) + .one(ctx.db()) + .await + { + Ok(Some(x)) => Ok(JsonRD(JsonResourceDescriptor { + subject: format!("acct:{user}@{domain}"), + aliases: vec![x.id.clone()], + links: vec![ + JsonResourceDescriptorLink { + rel: "self".to_string(), + link_type: Some("application/ld+json".to_string()), + href: Some(x.id), + properties: jrd::Map::default(), + titles: jrd::Map::default(), + }, + ], + expires: None, + properties: jrd::Map::default(), + })), + Ok(None) => Err(StatusCode::NOT_FOUND), + Err(e) => { + tracing::error!("error executing webfinger query: {e}"); + Err(StatusCode::INTERNAL_SERVER_ERROR) + }, + } + } else { + Err(StatusCode::UNPROCESSABLE_ENTITY) + } +} + +// i don't even want to bother with XML, im just returning a formatted xml string +pub async fn host_meta(State(ctx): State) -> String { + format!(r#" + + + "#, ctx.base()) +} diff --git a/src/server.rs b/src/server.rs index 860b410..040313e 100644 --- a/src/server.rs +++ b/src/server.rs @@ -77,6 +77,9 @@ pub async fn serve(db: DatabaseConnection, domain: String) { .route("/", get(ap::view)) .route("/inbox", post(ap::inbox)) .route("/outbox", get(ap::outbox)) + // .well-known and discovery + .route("/.well-known/webfinger", get(ap::well_known::webfinger)) + .route("/.well-known/host-meta", get(ap::well_known::host_meta)) // actor routes .route("/users/:id", get(ap::user::view)) .route("/users/:id/inbox", post(ap::user::inbox))