feat: Implement WebFinger discovery and ActivityPub user actor endpoint

- Added a new router for handling well-known endpoints, specifically WebFinger.
- Implemented the `webfinger` function to respond to WebFinger queries.
- Created a new route for WebFinger in the router.
- Refactored user retrieval logic to support both user ID and username in a single endpoint.
- Updated user router to use the new `get_user_by_param` function.
- Added tests for WebFinger discovery and ActivityPub user actor endpoint.
- Updated dependencies in Cargo.toml files to include necessary libraries.
This commit is contained in:
2025-09-06 01:18:04 +02:00
parent 3d73c7f198
commit c7c573f3f4
11 changed files with 935 additions and 70 deletions

View File

@@ -0,0 +1,71 @@
use app::state::AppState;
use axum::{
extract::{Query, State},
response::{IntoResponse, Json},
};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Deserialize)]
pub struct WebFingerQuery {
resource: String,
}
#[derive(Serialize)]
pub struct WebFingerLink {
rel: String,
#[serde(rename = "type")]
type_: String,
href: Url,
}
#[derive(Serialize)]
pub struct WebFingerResponse {
subject: String,
links: Vec<WebFingerLink>,
}
pub async fn webfinger(
State(state): State<AppState>,
Query(query): Query<WebFingerQuery>,
) -> Result<impl IntoResponse, impl IntoResponse> {
if let Some((scheme, account_info)) = query.resource.split_once(':') {
if scheme != "acct" {
return Err((
axum::http::StatusCode::BAD_REQUEST,
"Invalid resource scheme",
));
}
let account_parts: Vec<&str> = account_info.split('@').collect();
let username = account_parts[0];
let user = match app::persistence::user::get_user_by_username(&state.conn, username).await {
Ok(Some(user)) => user,
_ => return Err((axum::http::StatusCode::NOT_FOUND, "User not found")),
};
let base_url = "http://localhost:3000";
let user_url = Url::parse(&format!("{}/users/{}", base_url, user.username)).unwrap();
let response = WebFingerResponse {
subject: query.resource,
links: vec![WebFingerLink {
rel: "self".to_string(),
type_: "application/activity+json".to_string(),
href: user_url,
}],
};
Ok(Json(response))
} else {
Err((
axum::http::StatusCode::BAD_REQUEST,
"Invalid resource format",
))
}
}
pub fn create_well_known_router() -> axum::Router<AppState> {
axum::Router::new().route("/webfinger", axum::routing::get(webfinger))
}