feat: add host-meta endpoint with domain-aware XRD response

This commit is contained in:
Till Wegmueller 2026-04-03 19:30:26 +02:00
parent 697c84accf
commit 7aa5a6738c
No known key found for this signature in database
3 changed files with 101 additions and 0 deletions

45
src/handler/host_meta.rs Normal file
View file

@ -0,0 +1,45 @@
use axum::extract::State;
use axum_extra::extract::Host;
use axum::http::header;
use axum::response::{IntoResponse, Response};
use axum::routing::get;
use axum::Router;
use sea_orm::*;
use crate::entity::domains;
use crate::error::{AppError, AppResult};
use crate::state::AppState;
async fn host_meta(
State(state): State<AppState>,
Host(hostname): Host,
) -> AppResult<Response> {
// Strip port if present
let domain = hostname.split(':').next().unwrap_or(&hostname);
// Check this domain is registered and verified
let _domain = domains::Entity::find()
.filter(domains::Column::Domain.eq(domain))
.filter(domains::Column::Verified.eq(true))
.one(&state.db)
.await?
.ok_or(AppError::NotFound)?;
let base_url = &state.settings.server.base_url;
let xrd = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
<Link rel="lrdd" type="application/jrd+json" template="{base_url}/.well-known/webfinger?resource={{uri}}" />
</XRD>"#
);
Ok((
[(header::CONTENT_TYPE, "application/xrd+xml; charset=utf-8")],
xrd,
)
.into_response())
}
pub fn router() -> Router<AppState> {
Router::new().route("/.well-known/host-meta", get(host_meta))
}

View file

@ -1,4 +1,5 @@
mod health;
mod host_meta;
mod webfinger;
use axum::Router;
@ -7,6 +8,7 @@ use crate::state::AppState;
pub fn router(state: AppState) -> Router {
Router::new()
.merge(webfinger::router())
.merge(host_meta::router())
.merge(health::router())
.with_state(state)
}

54
tests/test_host_meta.rs Normal file
View file

@ -0,0 +1,54 @@
mod common;
use axum_test::TestServer;
use webfingerd::handler;
#[tokio::test]
async fn test_host_meta_returns_xrd_for_known_domain() {
let state = common::test_state().await;
// Seed a verified domain in DB
use sea_orm::ActiveModelTrait;
use sea_orm::Set;
use webfingerd::entity::domains;
let domain = domains::ActiveModel {
id: Set(uuid::Uuid::new_v4().to_string()),
domain: Set("example.com".into()),
owner_token_hash: Set("hash".into()),
registration_secret: Set("secret".into()),
challenge_type: Set("dns-01".into()),
challenge_token: Set(None),
verified: Set(true),
created_at: Set(chrono::Utc::now().naive_utc()),
verified_at: Set(Some(chrono::Utc::now().naive_utc())),
};
domain.insert(&state.db).await.unwrap();
let app = handler::router(state);
let server = TestServer::new(app);
let response = server
.get("/.well-known/host-meta")
.add_header("Host", "example.com")
.await;
response.assert_status_ok();
let body = response.text();
assert!(body.contains("application/jrd+json") || body.contains("XRD"));
assert!(body.contains("/.well-known/webfinger"));
}
#[tokio::test]
async fn test_host_meta_returns_404_for_unknown_domain() {
let state = common::test_state().await;
let app = handler::router(state);
let server = TestServer::new(app);
let response = server
.get("/.well-known/host-meta")
.add_header("Host", "unknown.example.com")
.await;
response.assert_status_not_found();
}