activitypub-federation-rust/examples/local_federation/axum/http.rs
Kevin Kuriakose 7994df3706
Use OriginalUri for Axum ActivityData (#141)
* Use OriginalUri for axum ActivityData

When the inbox path is under a nested `Router`, the received request has a URI
with the common prefix stripped. This causes incoming signatures to be considered
invalid since the path is different (see https://github.com/LemmyNet/activitypub-federation-rust/issues/107#issuecomment-2767428107)

This commit uses `OriginalUri` for URI extraction instead, which will retrieve
the full URI regardless of router nesting

* Use router nesting for local_federation

With 8c787f5, router nesting is supported correctly in axum

* Fix docs typo (#143)

---------

Co-authored-by: Zami <szgie@proton.me>
Co-authored-by: Felix Ableitner <me@nutomic.com>
2025-06-02 04:56:25 -04:00

92 lines
2.5 KiB
Rust

use crate::{
error::Error,
instance::DatabaseHandle,
objects::person::{DbUser, Person, PersonAcceptedActivities},
};
use activitypub_federation::{
axum::{
inbox::{receive_activity, ActivityData},
json::FederationJson,
},
config::{Data, FederationConfig, FederationMiddleware},
fetch::webfinger::{build_webfinger_response, extract_webfinger_name, Webfinger},
protocol::context::WithContext,
traits::Object,
};
use axum::{
debug_handler,
extract::{Path, Query},
response::IntoResponse,
routing::{get, post},
Json,
Router,
};
use serde::Deserialize;
use std::net::ToSocketAddrs;
use tracing::info;
pub fn listen(config: &FederationConfig<DatabaseHandle>) -> Result<(), Error> {
let hostname = config.domain();
info!("Listening with axum on {hostname}");
let config = config.clone();
let app = Router::new()
.route("/{user}/inbox", post(http_post_user_inbox))
.route("/{user}", get(http_get_user))
.route("/.well-known/webfinger", get(webfinger))
.layer(FederationMiddleware::new(config));
let addr = hostname
.to_socket_addrs()?
.next()
.expect("Failed to lookup domain name");
let fut = async move {
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app.into_make_service())
.await
.unwrap();
};
tokio::spawn(fut);
Ok(())
}
#[debug_handler]
async fn http_get_user(
Path(name): Path<String>,
data: Data<DatabaseHandle>,
) -> Result<FederationJson<WithContext<Person>>, Error> {
let db_user = data.read_user(&name)?;
let json_user = db_user.into_json(&data).await?;
Ok(FederationJson(WithContext::new_default(json_user)))
}
#[debug_handler]
async fn http_post_user_inbox(
data: Data<DatabaseHandle>,
activity_data: ActivityData,
) -> impl IntoResponse {
receive_activity::<WithContext<PersonAcceptedActivities>, DbUser, DatabaseHandle>(
activity_data,
&data,
)
.await
}
#[derive(Deserialize)]
struct WebfingerQuery {
resource: String,
}
#[debug_handler]
async fn webfinger(
Query(query): Query<WebfingerQuery>,
data: Data<DatabaseHandle>,
) -> Result<Json<Webfinger>, Error> {
let name = extract_webfinger_name(&query.resource, &data)?;
let db_user = data.read_user(name)?;
Ok(Json(build_webfinger_response(
query.resource,
db_user.ap_id.into_inner(),
)))
}