diff --git a/Cargo.lock b/Cargo.lock index 92138c5..403d8cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2091,6 +2091,7 @@ dependencies = [ "url", "uuid", "vcpkg", + "webfinger", ] [[package]] @@ -4173,6 +4174,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webfinger" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f395336b42f1be22490390830ccfe7a735725eef086cd0574bf8759408ecb3af" +dependencies = [ + "reqwest 0.11.27", + "serde", +] + [[package]] name = "webpki-roots" version = "0.25.4" diff --git a/Cargo.toml b/Cargo.toml index 74d7f0f..3ce96b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ serde_derive = "1.0.201" dotenv = "0.15.0" async-recursion = "1.1.1" base64-url = "3.0.0" +webfinger = "0.5.1" [dependencies.sea-orm] version = "0.12.0" diff --git a/src/http.rs b/src/http.rs index 759bb43..2fc8fd4 100644 --- a/src/http.rs +++ b/src/http.rs @@ -2,8 +2,13 @@ use crate::{ database::StateHandle, entities::user, error::Error, - lysand::{self, conversion::receive_lysand_note}, + lysand::{ + self, + conversion::{db_user_from_url, receive_lysand_note}, + }, objects::person::{DbUser, PersonAcceptedActivities}, + utils::generate_user_id, + API_DOMAIN, LYSAND_DOMAIN, }; use activitypub_federation::{ actix_web::{inbox::receive_activity, signing_actor}, @@ -18,6 +23,7 @@ use anyhow::anyhow; use serde::Deserialize; use tracing::info; use url::Url; +use webfinger::resolve; pub fn listen(config: &FederationConfig) -> Result<(), Error> { let hostname = config.domain(); @@ -101,9 +107,18 @@ pub async fn webfinger( data: Data, ) -> Result { let name = extract_webfinger_name(&query.resource, &data)?; - let db_user = data.read_user(name).await?; + let db_user = data.read_user(name.clone()).await; + let user; + if db_user.is_ok() { + user = db_user.unwrap(); + } else { + let res = resolve("acct:".to_string() + name + "@" + &LYSAND_DOMAIN, true) + .await + .unwrap(); + user = db_user_from_url(Url::parse(&res.subject)?).await?; + } Ok(HttpResponse::Ok().json(build_webfinger_response( query.resource.clone(), - Url::parse(&db_user.id)?, + generate_user_id(&API_DOMAIN, &user.id)?, ))) } diff --git a/src/lysand/http.rs b/src/lysand/http.rs index 7544944..a27c0b7 100644 --- a/src/lysand/http.rs +++ b/src/lysand/http.rs @@ -1,6 +1,6 @@ use activitypub_federation::{ fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor}, - protocol::context::WithContext, + protocol::{context::WithContext, public_key::PublicKey}, traits::Object, FEDERATION_CONTENT_TYPE, }; @@ -18,8 +18,8 @@ use crate::{ error, lysand::conversion::{lysand_post_from_db, lysand_user_from_db}, objects, - utils::{base_url_decode, generate_create_id}, - Response, DB, FEDERATION_CONFIG, + utils::{base_url_decode, generate_create_id, generate_user_id}, + Response, API_DOMAIN, DB, FEDERATION_CONFIG, }; #[derive(serde::Deserialize)] @@ -78,17 +78,17 @@ async fn query_post( } let opt_model = prelude::Post::find() - .filter(post::Column::Url.eq(query.url.clone().unwrap().as_str())) - .one(db) + .filter(post::Column::Url.eq(query.url.clone().unwrap().as_str())) + .one(db) + .await?; + let target; + if let Some(model) = opt_model { + target = model; + } else { + target = ObjectId::::from(Url::parse(query.url.clone().unwrap().as_str())?) + .dereference(&data.to_request_data()) .await?; - let target; - if let Some(model) = opt_model { - target = model; - } else { - target = ObjectId::::from(Url::parse(query.url.clone().unwrap().as_str())?) - .dereference(&data.to_request_data()) - .await?; - } + } Ok(HttpResponse::Ok() .content_type("application/json") @@ -117,6 +117,41 @@ async fn fetch_post( .json(crate::objects::post::Note::from_db(&post))) } +#[get("/apbridge/user/{user}")] +async fn fetch_user( + path: web::Path, + state: web::Data, +) -> actix_web::Result { + let db = DB.get().unwrap(); + + let user = prelude::User::find() + .filter(post::Column::Id.eq(path.as_str())) + .one(db) + .await?; + + let user = match user { + Some(user) => user, + None => return Ok(HttpResponse::NotFound().finish()), + }; + + Ok(HttpResponse::Ok() + .content_type(FEDERATION_CONTENT_TYPE) + .json(crate::objects::person::Person { + kind: Default::default(), + id: generate_user_id(&API_DOMAIN, &user.id)?.into(), + preferred_username: user.username.clone(), + name: user.name.clone(), + summary: user.summary.clone(), + url: Url::parse(user.url.as_str()).unwrap(), + inbox: Url::parse(user.inbox.as_str()).unwrap(), + public_key: PublicKey { + owner: Url::parse(user.url.as_str()).unwrap(), + public_key_pem: user.public_key, + id: format!("{}#main-key", Url::parse(user.url.as_str()).unwrap()), + }, + })) +} + #[get("/apbridge/lysand/object/{post}")] async fn fetch_lysand_post( path: web::Path, diff --git a/src/main.rs b/src/main.rs index 45a951d..1bb733d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use clap::Parser; use database::Database; use entities::post; use http::{http_get_user, http_post_user_inbox, webfinger}; -use lysand::http::{create_activity, fetch_lysand_post, fetch_post, query_post}; +use lysand::http::{create_activity, fetch_lysand_post, fetch_post, fetch_user, query_post}; use objects::person::DbUser; use sea_orm::{ActiveModelTrait, DatabaseConnection, Set}; use serde::{Deserialize, Serialize}; @@ -259,6 +259,7 @@ async fn main() -> actix_web::Result<(), anyhow::Error> { .route("/.well-known/webfinger", web::get().to(webfinger)) .service(index) .service(fetch_post) + .service(fetch_user) .service(create_activity) .service(query_post) .service(fetch_lysand_post) diff --git a/src/objects/person.rs b/src/objects/person.rs index 078b303..47dc821 100644 --- a/src/objects/person.rs +++ b/src/objects/person.rs @@ -71,14 +71,14 @@ impl DbUser { #[serde(rename_all = "camelCase")] pub struct Person { #[serde(rename = "type")] - kind: PersonType, - preferred_username: String, - name: String, - summary: Option, - url: Url, - id: ObjectId, - inbox: Url, - public_key: PublicKey, + pub kind: PersonType, + pub preferred_username: String, + pub name: String, + pub summary: Option, + pub url: Url, + pub id: ObjectId, + pub inbox: Url, + pub public_key: PublicKey, } #[async_trait::async_trait]