mirror of
https://git.asonix.dog/asonix/relay.git
synced 2025-11-27 19:10:35 +00:00
Handle webfinger directly
This commit is contained in:
parent
6ff7b59778
commit
6cf19bd4c9
15
Cargo.lock
generated
15
Cargo.lock
generated
|
|
@ -224,19 +224,6 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "actix-webfinger"
|
|
||||||
version = "0.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "74a22b44deff50693521b489885151fd65a2a596f7aef6d8c0753485b8915082"
|
|
||||||
dependencies = [
|
|
||||||
"actix-rt",
|
|
||||||
"actix-web",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
|
|
@ -396,7 +383,6 @@ dependencies = [
|
||||||
"activitystreams",
|
"activitystreams",
|
||||||
"activitystreams-ext",
|
"activitystreams-ext",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-webfinger",
|
|
||||||
"ammonia",
|
"ammonia",
|
||||||
"async-cpupool",
|
"async-cpupool",
|
||||||
"background-jobs",
|
"background-jobs",
|
||||||
|
|
@ -448,6 +434,7 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
"tracing-opentelemetry",
|
"tracing-opentelemetry",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ default = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = { version = "4.4.0", default-features = false, features = ["compress-brotli", "compress-gzip", "rustls-0_23"] }
|
actix-web = { version = "4.4.0", default-features = false, features = ["compress-brotli", "compress-gzip", "rustls-0_23"] }
|
||||||
actix-webfinger = { version = "0.5.0", default-features = false }
|
|
||||||
activitystreams = "0.7.0-alpha.25"
|
activitystreams = "0.7.0-alpha.25"
|
||||||
activitystreams-ext = "0.1.0-alpha.3"
|
activitystreams-ext = "0.1.0-alpha.3"
|
||||||
ammonia = "4.0.0"
|
ammonia = "4.0.0"
|
||||||
|
|
@ -84,6 +83,7 @@ tracing-subscriber = { version = "0.3", features = [
|
||||||
"fmt",
|
"fmt",
|
||||||
] }
|
] }
|
||||||
tokio = { version = "1", features = ["full", "tracing"] }
|
tokio = { version = "1", features = ["full", "tracing"] }
|
||||||
|
url = { version = "2.5.4", features = ["serde"] }
|
||||||
uuid = { version = "1", features = ["v4", "serde"] }
|
uuid = { version = "1", features = ["v4", "serde"] }
|
||||||
|
|
||||||
[dependencies.background-jobs]
|
[dependencies.background-jobs]
|
||||||
|
|
|
||||||
|
|
@ -50,8 +50,8 @@ use self::{
|
||||||
data::{ActorCache, MediaCache, State},
|
data::{ActorCache, MediaCache, State},
|
||||||
db::Db,
|
db::Db,
|
||||||
jobs::create_workers,
|
jobs::create_workers,
|
||||||
middleware::{DebugPayload, MyVerify, RelayResolver, Timings},
|
middleware::{DebugPayload, MyVerify, Timings},
|
||||||
routes::{actor, healthz, inbox, index, nodeinfo, nodeinfo_meta, statics},
|
routes::{actor, healthz, inbox, index, nodeinfo, nodeinfo_meta, statics, webfinger},
|
||||||
spawner::Spawner,
|
spawner::Spawner,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -377,7 +377,7 @@ async fn server_main(
|
||||||
.service(web::resource("/nodeinfo/2.0.json").route(web::get().to(nodeinfo)))
|
.service(web::resource("/nodeinfo/2.0.json").route(web::get().to(nodeinfo)))
|
||||||
.service(
|
.service(
|
||||||
web::scope("/.well-known")
|
web::scope("/.well-known")
|
||||||
.service(actix_webfinger::scoped::<RelayResolver>())
|
.service(web::resource("/webfinger").route(web::get().to(webfinger)))
|
||||||
.service(web::resource("/nodeinfo").route(web::get().to(nodeinfo_meta))),
|
.service(web::resource("/nodeinfo").route(web::get().to(nodeinfo_meta))),
|
||||||
)
|
)
|
||||||
.service(web::resource("/static/{filename}").route(web::get().to(statics)))
|
.service(web::resource("/static/{filename}").route(web::get().to(statics)))
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
mod payload;
|
mod payload;
|
||||||
mod timings;
|
mod timings;
|
||||||
mod verifier;
|
mod verifier;
|
||||||
mod webfinger;
|
|
||||||
|
|
||||||
pub(crate) use payload::DebugPayload;
|
pub(crate) use payload::DebugPayload;
|
||||||
pub(crate) use timings::Timings;
|
pub(crate) use timings::Timings;
|
||||||
pub(crate) use verifier::MyVerify;
|
pub(crate) use verifier::MyVerify;
|
||||||
pub(crate) use webfinger::RelayResolver;
|
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
use crate::{
|
|
||||||
config::{Config, UrlKind},
|
|
||||||
data::State,
|
|
||||||
future::LocalBoxFuture,
|
|
||||||
};
|
|
||||||
use actix_web::web::Data;
|
|
||||||
use actix_webfinger::{Resolver, Webfinger};
|
|
||||||
use rsa_magic_public_key::AsMagicPublicKey;
|
|
||||||
|
|
||||||
pub(crate) struct RelayResolver;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
|
||||||
#[error("Error resolving webfinger data")]
|
|
||||||
pub(crate) struct RelayError;
|
|
||||||
|
|
||||||
impl Resolver for RelayResolver {
|
|
||||||
type State = (Data<State>, Data<Config>);
|
|
||||||
type Error = RelayError;
|
|
||||||
|
|
||||||
fn find(
|
|
||||||
scheme: Option<&str>,
|
|
||||||
account: &str,
|
|
||||||
domain: &str,
|
|
||||||
(state, config): Self::State,
|
|
||||||
) -> LocalBoxFuture<'static, Result<Option<Webfinger>, Self::Error>> {
|
|
||||||
let domain = domain.to_owned();
|
|
||||||
let account = account.to_owned();
|
|
||||||
let scheme = scheme.map(|scheme| scheme.to_owned());
|
|
||||||
|
|
||||||
let fut = async move {
|
|
||||||
if let Some(scheme) = scheme {
|
|
||||||
if scheme != "acct:" {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if domain != config.hostname() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
if account != "relay" {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut wf = Webfinger::new(config.generate_resource().as_str());
|
|
||||||
wf.add_alias(config.generate_url(UrlKind::Actor).as_str())
|
|
||||||
.add_activitypub(config.generate_url(UrlKind::Actor).as_str())
|
|
||||||
.add_magic_public_key(&state.public_key.as_magic_public_key());
|
|
||||||
|
|
||||||
Ok(Some(wf))
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::pin(fut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl actix_web::error::ResponseError for RelayError {}
|
|
||||||
|
|
@ -5,6 +5,7 @@ mod index;
|
||||||
mod media;
|
mod media;
|
||||||
mod nodeinfo;
|
mod nodeinfo;
|
||||||
mod statics;
|
mod statics;
|
||||||
|
mod webfinger;
|
||||||
|
|
||||||
pub(crate) use self::{
|
pub(crate) use self::{
|
||||||
actor::route as actor,
|
actor::route as actor,
|
||||||
|
|
@ -14,6 +15,7 @@ pub(crate) use self::{
|
||||||
media::route as media,
|
media::route as media,
|
||||||
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
|
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
|
||||||
statics::route as statics,
|
statics::route as statics,
|
||||||
|
webfinger::resolve as webfinger,
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_web::HttpResponse;
|
use actix_web::HttpResponse;
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,15 @@ use crate::{
|
||||||
data::State,
|
data::State,
|
||||||
};
|
};
|
||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder};
|
||||||
use actix_webfinger::Link;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[tracing::instrument(name = "Well Known NodeInfo", skip(config))]
|
#[tracing::instrument(name = "Well Known NodeInfo", skip(config))]
|
||||||
pub(crate) async fn well_known(config: web::Data<Config>) -> impl Responder {
|
pub(crate) async fn well_known(config: web::Data<Config>) -> impl Responder {
|
||||||
web::Json(Links {
|
web::Json(Links {
|
||||||
links: vec![Link {
|
links: [serde_json::json!({
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_owned(),
|
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
||||||
href: Some(config.generate_url(UrlKind::NodeInfo).to_string()),
|
"href": config.generate_url(UrlKind::NodeInfo),
|
||||||
template: None,
|
})],
|
||||||
kind: None,
|
|
||||||
}],
|
|
||||||
})
|
})
|
||||||
.customize()
|
.customize()
|
||||||
.insert_header(("Content-Type", "application/jrd+json"))
|
.insert_header(("Content-Type", "application/jrd+json"))
|
||||||
|
|
@ -21,7 +19,7 @@ pub(crate) async fn well_known(config: web::Data<Config>) -> impl Responder {
|
||||||
|
|
||||||
#[derive(serde::Serialize)]
|
#[derive(serde::Serialize)]
|
||||||
struct Links {
|
struct Links {
|
||||||
links: Vec<Link>,
|
links: [Value; 1],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(name = "NodeInfo", skip_all)]
|
#[tracing::instrument(name = "NodeInfo", skip_all)]
|
||||||
|
|
|
||||||
160
src/routes/webfinger.rs
Normal file
160
src/routes/webfinger.rs
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
use crate::{
|
||||||
|
config::{Config, UrlKind},
|
||||||
|
data::State,
|
||||||
|
};
|
||||||
|
use actix_web::{
|
||||||
|
dev::Payload,
|
||||||
|
http::{header::ACCEPT, StatusCode},
|
||||||
|
web::{Data, Query},
|
||||||
|
FromRequest, HttpRequest, HttpResponse, ResponseError,
|
||||||
|
};
|
||||||
|
use rsa_magic_public_key::AsMagicPublicKey;
|
||||||
|
use std::future::{ready, Ready};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub(crate) enum ErrorKind {
|
||||||
|
#[error("Accept Header is required")]
|
||||||
|
MissingAccept,
|
||||||
|
|
||||||
|
#[error("Unsupported accept type")]
|
||||||
|
InvalidAccept,
|
||||||
|
|
||||||
|
#[error("Query is malformed")]
|
||||||
|
InvalidQuery,
|
||||||
|
|
||||||
|
#[error("No records match the provided resource")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for ErrorKind {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
Self::MissingAccept | Self::InvalidAccept | Self::InvalidQuery => {
|
||||||
|
StatusCode::BAD_REQUEST
|
||||||
|
}
|
||||||
|
Self::NotFound => StatusCode::NOT_FOUND,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse<actix_web::body::BoxBody> {
|
||||||
|
HttpResponse::build(self.status_code()).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
struct Resource {
|
||||||
|
resource: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum WebfingerResource {
|
||||||
|
Url(Url),
|
||||||
|
Unknown(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_supported_json(m: &mime::Mime) -> bool {
|
||||||
|
matches!(
|
||||||
|
(
|
||||||
|
m.type_().as_str(),
|
||||||
|
m.subtype().as_str(),
|
||||||
|
m.suffix().map(|s| s.as_str()),
|
||||||
|
),
|
||||||
|
("*", "*", None)
|
||||||
|
| ("application", "*", None)
|
||||||
|
| ("application", "json", None)
|
||||||
|
| ("application", "jrd", Some("json"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebfingerResource {
|
||||||
|
fn parse_request(req: &HttpRequest) -> Result<Self, ErrorKind> {
|
||||||
|
let Some(accept) = req.headers().get(ACCEPT) else {
|
||||||
|
return Err(ErrorKind::MissingAccept);
|
||||||
|
};
|
||||||
|
|
||||||
|
let accept = accept.to_str().map_err(|_| ErrorKind::InvalidAccept)?;
|
||||||
|
let accept = accept
|
||||||
|
.parse::<mime::Mime>()
|
||||||
|
.map_err(|_| ErrorKind::InvalidAccept)?;
|
||||||
|
|
||||||
|
if !is_supported_json(&accept) {
|
||||||
|
return Err(ErrorKind::InvalidAccept);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Resource { resource } = Query::<Resource>::from_query(req.query_string())
|
||||||
|
.map_err(|_| ErrorKind::InvalidQuery)?
|
||||||
|
.into_inner();
|
||||||
|
|
||||||
|
let wr = match Url::parse(&resource) {
|
||||||
|
Ok(url) => WebfingerResource::Url(url),
|
||||||
|
Err(_) => WebfingerResource::Unknown(resource),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(wr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for WebfingerResource {
|
||||||
|
type Error = ErrorKind;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
|
||||||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
ready(Self::parse_request(req))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn resolve(
|
||||||
|
config: Data<Config>,
|
||||||
|
state: Data<State>,
|
||||||
|
resource: WebfingerResource,
|
||||||
|
) -> Result<HttpResponse, ErrorKind> {
|
||||||
|
match resource {
|
||||||
|
WebfingerResource::Unknown(handle) => {
|
||||||
|
if handle.trim_start_matches('@') == config.generate_resource() {
|
||||||
|
return Ok(respond(&config, &state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WebfingerResource::Url(url) => match url.scheme() {
|
||||||
|
"acct" => {
|
||||||
|
if url.path().trim_start_matches('@') == config.generate_resource() {
|
||||||
|
return Ok(respond(&config, &state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"http" | "https" => {
|
||||||
|
if url.as_str() == config.generate_url(UrlKind::Actor).as_str() {
|
||||||
|
return Ok(respond(&config, &state));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(ErrorKind::NotFound),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ErrorKind::NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn respond(config: &Config, state: &State) -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_type("application/jrd+json")
|
||||||
|
.json(serde_json::json!({
|
||||||
|
"subject": format!("acct:{}", config.generate_resource()),
|
||||||
|
"aliases": [
|
||||||
|
config.generate_url(UrlKind::Actor),
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"rel": "self",
|
||||||
|
"href": config.generate_url(UrlKind::Actor),
|
||||||
|
"type": "application/activity+json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "self",
|
||||||
|
"href": config.generate_url(UrlKind::Actor),
|
||||||
|
"type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rel": "magic-public-key",
|
||||||
|
"href": state.public_key.as_magic_public_key()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}))
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user