diff --git a/src/activity_pub/mod.rs b/src/activity_pub/mod.rs index 7c7aa8c6..569ccb12 100644 --- a/src/activity_pub/mod.rs +++ b/src/activity_pub/mod.rs @@ -2,6 +2,8 @@ use models::instance::Instance; use diesel::PgConnection; use serde_json::Value; +pub mod webfinger; + pub enum ActorType { Person, Blog diff --git a/src/activity_pub/webfinger.rs b/src/activity_pub/webfinger.rs new file mode 100644 index 00000000..901d0148 --- /dev/null +++ b/src/activity_pub/webfinger.rs @@ -0,0 +1,50 @@ +use serde_json; +use diesel::PgConnection; + +pub trait Webfinger { + fn webfinger_subject(&self, conn: &PgConnection) -> String; + fn webfinger_aliases(&self, conn: &PgConnection) -> Vec; + fn webfinger_links(&self, conn: &PgConnection) -> Vec>; + + fn webfinger_json(&self, conn: &PgConnection) -> serde_json::Value { + json!({ + "subject": self.webfinger_subject(conn), + "aliases": self.webfinger_aliases(conn), + "links": self.webfinger_links(conn).into_iter().map(|link| { + let mut link_obj = serde_json::Map::new(); + for (k, v) in link { + link_obj.insert(k, serde_json::Value::String(v)); + } + serde_json::Value::Object(link_obj) + }).collect::>() + }) + } + + fn webfinger_xml(&self, conn: &PgConnection) -> String { + format!(r#" + + + {subject} + {aliases} + {links} + + "#, + subject = self.webfinger_subject(conn), + aliases = self.webfinger_aliases(conn).into_iter().map(|a| { + format!("{a}", a = a) + }).collect::>().join("\n"), + links = self.webfinger_links(conn).into_iter().map(|l| { + format!("", l.into_iter().map(|prop| { + format!("{}=\"{}\"", prop.0, prop.1) + }).collect::>().join(" ")) + }).collect::>().join("\n") + ) + } + + fn webfinger(&self, format: &'static str, conn: &PgConnection) -> String { + match format { + "json" => self.webfinger_json(conn).to_string(), + _ => self.webfinger_xml(conn) + } + } +} diff --git a/src/main.rs b/src/main.rs index b7793421..262e3d75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,6 +53,10 @@ fn index(conn: DbConn) -> String { fn main() { rocket::ignite() .mount("/", routes![ + routes::well_known::host_meta, + routes::well_known::webfinger_json, + routes::well_known::webfinger_xml, + routes::instance::configure, routes::instance::post_config, diff --git a/src/models/blogs.rs b/src/models/blogs.rs index da6dcb09..e30012e6 100644 --- a/src/models/blogs.rs +++ b/src/models/blogs.rs @@ -3,6 +3,7 @@ use diesel::{QueryDsl, RunQueryDsl, ExpressionMethods, PgConnection}; use schema::blogs; use activity_pub::{Actor, ActorType}; use models::instance::Instance; +use activity_pub::webfinger::*; #[derive(Queryable, Identifiable)] pub struct Blog { @@ -83,6 +84,33 @@ impl Actor for Blog { } } +impl Webfinger for Blog { + fn webfinger_subject(&self, conn: &PgConnection) -> String { + format!("acct:{}@{}", self.actor_id, self.get_instance(conn).public_domain) + } + fn webfinger_aliases(&self, conn: &PgConnection) -> Vec { + vec![self.compute_id(conn)] + } + fn webfinger_links(&self, conn: &PgConnection) -> Vec> { + vec![ + vec![ + (String::from("rel"), String::from("http://webfinger.net/rel/profile-page")), + (String::from("href"), self.compute_id(conn)) + ], + vec![ + (String::from("rel"), String::from("http://schemas.google.com/g/2010#updates-from")), + (String::from("type"), String::from("application/atom+xml")), + (String::from("href"), self.compute_box(conn, "feed.atom")) + ], + vec![ + (String::from("rel"), String::from("self")), + (String::from("type"), String::from("application/activity+json")), + (String::from("href"), self.compute_id(conn)) + ] + ] + } +} + impl NewBlog { pub fn new_local( actor_id: String, diff --git a/src/models/users.rs b/src/models/users.rs index b74e79dd..d67d50fb 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -8,6 +8,7 @@ use db_conn::DbConn; use activity_pub::{ActorType, Actor}; use models::instance::Instance; use bcrypt; +use activity_pub::webfinger::Webfinger; pub const AUTH_COOKIE: &'static str = "user_id"; @@ -127,6 +128,33 @@ impl Actor for User { } } +impl Webfinger for User { + fn webfinger_subject(&self, conn: &PgConnection) -> String { + format!("acct:{}@{}", self.username, self.get_instance(conn).public_domain) + } + fn webfinger_aliases(&self, conn: &PgConnection) -> Vec { + vec![self.compute_id(conn)] + } + fn webfinger_links(&self, conn: &PgConnection) -> Vec> { + vec![ + vec![ + (String::from("rel"), String::from("http://webfinger.net/rel/profile-page")), + (String::from("href"), self.compute_id(conn)) + ], + vec![ + (String::from("rel"), String::from("http://schemas.google.com/g/2010#updates-from")), + (String::from("type"), String::from("application/atom+xml")), + (String::from("href"), self.compute_box(conn, "feed.atom")) + ], + vec![ + (String::from("rel"), String::from("self")), + (String::from("type"), String::from("application/activity+json")), + (String::from("href"), self.compute_id(conn)) + ] + ] + } +} + impl NewUser { /// Creates a new local user pub fn new_local( diff --git a/src/routes/mod.rs b/src/routes/mod.rs index e9076b5a..278d5bb5 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -3,3 +3,4 @@ pub mod instance; pub mod session; pub mod posts; pub mod user; +pub mod well_known; diff --git a/src/routes/well_known.rs b/src/routes/well_known.rs new file mode 100644 index 00000000..c16aa2df --- /dev/null +++ b/src/routes/well_known.rs @@ -0,0 +1,60 @@ +use models::instance::Instance; +use db_conn::DbConn; +use models::users::User; +use models::blogs::Blog; +use rocket_contrib::Json; +use activity_pub::webfinger::Webfinger; + +#[get("/.well-known/host-meta", format = "application/xml")] +fn host_meta(conn: DbConn) -> String { + let domain = Instance::get_local(&*conn).unwrap().public_domain; + format!(r#" + + + + + "#, domain = domain) +} + +#[derive(FromForm)] +struct WebfingerQuery { + resource: String +} + +#[get("/.well-known/webfinger?", format = "application/jrd+json")] +fn webfinger_json(query: WebfingerQuery, conn: DbConn) -> Result { + webfinger(query, conn, "json") +} + +#[get("/.well-known/webfinger?", format = "application/xrd+xml")] +fn webfinger_xml(query: WebfingerQuery, conn: DbConn) -> Result { + webfinger(query, conn, "xml") +} + +fn webfinger(query: WebfingerQuery, conn: DbConn, format: &'static str) -> Result { + let mut parsed_query = query.resource.split(":"); + println!("{:?}", parsed_query.clone().collect::>()); + let res_type = parsed_query.next().unwrap(); + let res = parsed_query.next().unwrap(); + if res_type == "acct" { + let mut parsed_res = res.split("@"); + let user = parsed_res.next().unwrap(); + let res_dom = parsed_res.next().unwrap(); + + let domain = Instance::get_local(&*conn).unwrap().public_domain; + + if res_dom == domain { + match User::find_by_name(&*conn, String::from(user)) { + Some(usr) => Ok(usr.webfinger(format, &*conn)), + None => match Blog::find_by_actor_id(&*conn, String::from(user)) { + Some(blog) => Ok(blog.webfinger(format, &*conn)), + None => Err("Requested actor not found") + } + } + } else { + Err("Invalid instance") + } + } else { + Err("Invalid resource type. Only acct is supported") + } +}