mirror of
https://git.asonix.dog/asonix/relay.git
synced 2025-11-27 11:00: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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "addr2line"
|
||||
version = "0.21.0"
|
||||
|
|
@ -396,7 +383,6 @@ dependencies = [
|
|||
"activitystreams",
|
||||
"activitystreams-ext",
|
||||
"actix-web",
|
||||
"actix-webfinger",
|
||||
"ammonia",
|
||||
"async-cpupool",
|
||||
"background-jobs",
|
||||
|
|
@ -448,6 +434,7 @@ dependencies = [
|
|||
"tracing-log",
|
||||
"tracing-opentelemetry",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ default = []
|
|||
|
||||
[dependencies]
|
||||
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-ext = "0.1.0-alpha.3"
|
||||
ammonia = "4.0.0"
|
||||
|
|
@ -84,6 +83,7 @@ tracing-subscriber = { version = "0.3", features = [
|
|||
"fmt",
|
||||
] }
|
||||
tokio = { version = "1", features = ["full", "tracing"] }
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
|
||||
[dependencies.background-jobs]
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ use self::{
|
|||
data::{ActorCache, MediaCache, State},
|
||||
db::Db,
|
||||
jobs::create_workers,
|
||||
middleware::{DebugPayload, MyVerify, RelayResolver, Timings},
|
||||
routes::{actor, healthz, inbox, index, nodeinfo, nodeinfo_meta, statics},
|
||||
middleware::{DebugPayload, MyVerify, Timings},
|
||||
routes::{actor, healthz, inbox, index, nodeinfo, nodeinfo_meta, statics, webfinger},
|
||||
spawner::Spawner,
|
||||
};
|
||||
|
||||
|
|
@ -377,7 +377,7 @@ async fn server_main(
|
|||
.service(web::resource("/nodeinfo/2.0.json").route(web::get().to(nodeinfo)))
|
||||
.service(
|
||||
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("/static/{filename}").route(web::get().to(statics)))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
mod payload;
|
||||
mod timings;
|
||||
mod verifier;
|
||||
mod webfinger;
|
||||
|
||||
pub(crate) use payload::DebugPayload;
|
||||
pub(crate) use timings::Timings;
|
||||
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 nodeinfo;
|
||||
mod statics;
|
||||
mod webfinger;
|
||||
|
||||
pub(crate) use self::{
|
||||
actor::route as actor,
|
||||
|
|
@ -14,6 +15,7 @@ pub(crate) use self::{
|
|||
media::route as media,
|
||||
nodeinfo::{route as nodeinfo, well_known as nodeinfo_meta},
|
||||
statics::route as statics,
|
||||
webfinger::resolve as webfinger,
|
||||
};
|
||||
|
||||
use actix_web::HttpResponse;
|
||||
|
|
|
|||
|
|
@ -3,17 +3,15 @@ use crate::{
|
|||
data::State,
|
||||
};
|
||||
use actix_web::{web, Responder};
|
||||
use actix_webfinger::Link;
|
||||
use serde_json::Value;
|
||||
|
||||
#[tracing::instrument(name = "Well Known NodeInfo", skip(config))]
|
||||
pub(crate) async fn well_known(config: web::Data<Config>) -> impl Responder {
|
||||
web::Json(Links {
|
||||
links: vec![Link {
|
||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_owned(),
|
||||
href: Some(config.generate_url(UrlKind::NodeInfo).to_string()),
|
||||
template: None,
|
||||
kind: None,
|
||||
}],
|
||||
links: [serde_json::json!({
|
||||
"rel": "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
||||
"href": config.generate_url(UrlKind::NodeInfo),
|
||||
})],
|
||||
})
|
||||
.customize()
|
||||
.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)]
|
||||
struct Links {
|
||||
links: Vec<Link>,
|
||||
links: [Value; 1],
|
||||
}
|
||||
|
||||
#[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