feat: allow resolving webfinger for remote users

returned JRDs are already expired, indicating that we can't really be
trusted for remote users, go fetch their webfinger server instead
This commit is contained in:
əlemi 2024-05-23 01:59:31 +02:00
parent e146dc2a51
commit ebb7d77cae
Signed by: alemi
GPG key ID: A4895B84D311642C

View file

@ -1,8 +1,8 @@
use axum::{extract::{Path, Query, State}, http::StatusCode, response::{IntoResponse, Response}, Json}; use axum::{extract::{Path, Query, State}, http::StatusCode, response::{IntoResponse, Response}, Json};
use jrd::{JsonResourceDescriptor, JsonResourceDescriptorLink}; use jrd::{JsonResourceDescriptor, JsonResourceDescriptorLink};
use sea_orm::{EntityTrait, PaginatorTrait}; use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter};
use crate::{model, server::Context, url, VERSION}; use crate::{errors::UpubError, model, server::Context, url, VERSION};
#[derive(serde::Serialize)] #[derive(serde::Serialize)]
pub struct NodeInfoDiscovery { pub struct NodeInfoDiscovery {
@ -96,14 +96,16 @@ impl<T: serde::Serialize> IntoResponse for JsonRD<T> {
} }
} }
pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<WebfingerQuery>) -> Result<JsonRD<JsonResourceDescriptor>, StatusCode> { pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<WebfingerQuery>) -> crate::Result<JsonRD<JsonResourceDescriptor>> {
if let Some((user, domain)) = query if let Some((user, domain)) = query
.resource .resource
.replace("acct:", "") .replace("acct:", "")
.split_once('@') .split_once('@')
{ {
if user == ctx.domain() && domain == ctx.domain() { if domain == ctx.domain() {
return Ok(JsonRD(JsonResourceDescriptor { if user == ctx.domain() {
// we fetch with our domain as user, they are checking us back, this is a special edge case
Ok(JsonRD(JsonResourceDescriptor {
subject: format!("acct:{user}@{domain}"), subject: format!("acct:{user}@{domain}"),
aliases: vec![ctx.base().to_string()], aliases: vec![ctx.base().to_string()],
links: vec![ links: vec![
@ -117,36 +119,62 @@ pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<Webfinger
], ],
expires: None, expires: None,
properties: jrd::Map::default(), properties: jrd::Map::default(),
})); }))
}
} else {
// local user
let uid = ctx.uid(user); let uid = ctx.uid(user);
match model::user::Entity::find_by_id(uid) let usr = model::user::Entity::find_by_id(uid)
.one(ctx.db()) .one(ctx.db())
.await .await?
{ .ok_or_else(UpubError::not_found)?;
Ok(Some(x)) => Ok(JsonRD(JsonResourceDescriptor {
Ok(JsonRD(JsonResourceDescriptor {
subject: format!("acct:{user}@{domain}"), subject: format!("acct:{user}@{domain}"),
aliases: vec![x.id.clone()], aliases: vec![usr.id.clone()],
links: vec![ links: vec![
JsonResourceDescriptorLink { JsonResourceDescriptorLink {
rel: "self".to_string(), rel: "self".to_string(),
link_type: Some("application/ld+json".to_string()), link_type: Some("application/ld+json".to_string()),
href: Some(x.id), href: Some(usr.id),
properties: jrd::Map::default(), properties: jrd::Map::default(),
titles: jrd::Map::default(), titles: jrd::Map::default(),
}, },
], ],
expires: None, expires: None,
properties: jrd::Map::default(), properties: jrd::Map::default(),
})), }))
Ok(None) => Err(StatusCode::NOT_FOUND), }
Err(e) => {
tracing::error!("error executing webfinger query: {e}"); } else {
Err(StatusCode::INTERNAL_SERVER_ERROR) // remote user
let usr = model::user::Entity::find()
.filter(model::user::Column::PreferredUsername.eq(user))
.filter(model::user::Column::Domain.eq(domain))
.one(ctx.db())
.await?
.ok_or_else(UpubError::not_found)?;
Ok(JsonRD(JsonResourceDescriptor {
subject: format!("acct:{user}@{domain}"),
aliases: vec![usr.id.clone()],
links: vec![
JsonResourceDescriptorLink {
rel: "self".to_string(),
link_type: Some("application/ld+json".to_string()),
href: Some(usr.id),
properties: jrd::Map::default(),
titles: jrd::Map::default(),
}, },
],
properties: jrd::Map::default(),
// we are no authority on local users, this info should be considered already outdated,
// but can still be relevant, for example for our frontend
expires: Some(chrono::Utc::now()),
}))
} }
} else { } else {
Err(StatusCode::UNPROCESSABLE_ENTITY) Err(StatusCode::UNPROCESSABLE_ENTITY.into())
} }
} }