]> Untitled Git - lemmy.git/commitdiff
Implement webfinger (fixes #149)
authorFelix Ableitner <me@nutomic.com>
Wed, 18 Dec 2019 00:59:47 +0000 (01:59 +0100)
committerFelix Ableitner <me@nutomic.com>
Fri, 27 Dec 2019 16:29:50 +0000 (17:29 +0100)
server/src/db/community.rs
server/src/db/user.rs
server/src/feeds.rs
server/src/lib.rs
server/src/main.rs
server/src/settings.rs
server/src/webfinger.rs [new file with mode: 0644]

index 57b962d13609e6ddad3a9064e12973e8798d5403..74579535eab92ffcbfa0e23de25920610cb6692b 100644 (file)
@@ -68,6 +68,10 @@ impl Community {
       .filter(name.eq(community_name))
       .first::<Self>(conn)
   }
+
+  pub fn get_community_url(community_name: &str) -> String {
+    format!("https://{}/c/{}", Settings::get().hostname, community_name)
+  }
 }
 
 #[derive(Identifiable, Queryable, Associations, PartialEq, Debug)]
index 3d3865e8d7039b85f94fca3a303d3abfb0fdd071..21407e58c382982ede9a022c492aab909e4c3238 100644 (file)
@@ -132,23 +132,27 @@ impl User_ {
     .unwrap()
   }
 
+  pub fn find_by_username(conn: &PgConnection, username: &str) -> Result<Self, Error> {
+    user_.filter(name.eq(username)).first::<User_>(conn)
+  }
+
+  pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<Self, Error> {
+    user_.filter(email.eq(from_email)).first::<User_>(conn)
+  }
+
   pub fn find_by_email_or_username(
     conn: &PgConnection,
     username_or_email: &str,
   ) -> Result<Self, Error> {
     if is_email_regex(username_or_email) {
-      user_
-        .filter(email.eq(username_or_email))
-        .first::<User_>(conn)
+      User_::find_by_email(conn, username_or_email)
     } else {
-      user_
-        .filter(name.eq(username_or_email))
-        .first::<User_>(conn)
+      User_::find_by_username(conn, username_or_email)
     }
   }
 
-  pub fn find_by_email(conn: &PgConnection, from_email: &str) -> Result<Self, Error> {
-    user_.filter(email.eq(from_email)).first::<User_>(conn)
+  pub fn get_user_profile_url(username: &str) -> String {
+    format!("https://{}/u/{}", Settings::get().hostname, username)
   }
 
   pub fn find_by_jwt(conn: &PgConnection, jwt: &str) -> Result<Self, Error> {
index 5337edcf9384f2df1f899f72a310f5528e785931..c735688bea6fe36d0575dfd252d107b5cc4d06c3 100644 (file)
@@ -110,8 +110,8 @@ fn get_feed_user(sort_type: &SortType, user_name: String) -> Result<String, Erro
   let conn = establish_connection();
 
   let site_view = SiteView::read(&conn)?;
-  let user = User_::find_by_email_or_username(&conn, &user_name)?;
-  let user_url = format!("https://{}/u/{}", Settings::get().hostname, user.name);
+  let user = User_::find_by_username(&conn, &user_name)?;
+  let user_url = User_::get_user_profile_url(&user_name);
 
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(ListingType::All)
@@ -135,7 +135,7 @@ fn get_feed_community(sort_type: &SortType, community_name: String) -> Result<St
 
   let site_view = SiteView::read(&conn)?;
   let community = Community::read_from_name(&conn, community_name)?;
-  let community_url = format!("https://{}/c/{}", Settings::get().hostname, community.name);
+  let community_url = Community::get_community_url(&community.name);
 
   let posts = PostQueryBuilder::create(&conn)
     .listing_type(ListingType::All)
index c23c97d2bda4a036d1310594900799f863e7bda5..7afe494714e144daa40b28d6b478cdf28936aabf 100644 (file)
@@ -30,6 +30,7 @@ pub mod nodeinfo;
 pub mod schema;
 pub mod settings;
 pub mod version;
+pub mod webfinger;
 pub mod websocket;
 
 use crate::settings::Settings;
index 2d657a6136dd84c8a714915ae99f8e89f44eb5e1..29f361ea37685d26710d0c4188e8b43e78fd9acf 100644 (file)
@@ -11,6 +11,7 @@ use lemmy_server::db::establish_connection;
 use lemmy_server::feeds;
 use lemmy_server::nodeinfo;
 use lemmy_server::settings::Settings;
+use lemmy_server::webfinger;
 use lemmy_server::websocket::server::*;
 use std::env;
 use std::time::{Duration, Instant};
@@ -255,7 +256,10 @@ fn main() {
       )
       .route(
         "/federation/u/{user_name}",
-        web::get().to(apub::user::get_apub_user),
+        web::get().to(apub::user::get_apub_user))
+      .route(
+        ".well-known/webfinger",
+        web::get().to(webfinger::get_webfinger_response),
       )
   })
   .bind((settings.bind, settings.port))
index a7203a1eea0568446a1452141bdd548f6bbfbb28..4df2979e1945fddc05ddeea037b3ee6ffd603623 100644 (file)
@@ -56,7 +56,6 @@ lazy_static! {
 }
 
 impl Settings {
-  
   /// Reads config from the files and environment.
   /// First, defaults are loaded from CONFIG_FILE_DEFAULTS, then these values can be overwritten
   /// from CONFIG_FILE (optional). Finally, values from the environment (with prefix LEMMY) are
diff --git a/server/src/webfinger.rs b/server/src/webfinger.rs
new file mode 100644 (file)
index 0000000..47e2037
--- /dev/null
@@ -0,0 +1,69 @@
+use crate::db::community::Community;
+use crate::db::establish_connection;
+use crate::Settings;
+use actix_web::body::Body;
+use actix_web::web::Query;
+use actix_web::HttpResponse;
+use serde::Deserialize;
+use serde_json::json;
+
+#[derive(Deserialize)]
+pub struct Params {
+  resource: String,
+}
+
+/// Responds to webfinger requests of the following format. There isn't any real documentation for
+/// this, but it described in this blog post:
+/// https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social
+///
+/// You can also view the webfinger response that Mastodon sends:
+/// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town
+pub fn get_webfinger_response(info: Query<Params>) -> HttpResponse<Body> {
+  // NOTE: Calling the parameter "account" maybe doesn't really make sense, but should give us the
+  // best compatibility with existing implementations. We could also support an alternative name
+  // like "group", and encourage others to use that.
+  let community_identifier = info.resource.replace("acct:", "");
+  let split_identifier: Vec<&str> = community_identifier.split("@").collect();
+  let community_name = split_identifier[0];
+  // It looks like Mastodon does not return webfinger requests for users from other instances, so we
+  // don't do that either.
+  if split_identifier.len() != 2 || split_identifier[1] != Settings::get().hostname {
+    return HttpResponse::NotFound().finish();
+  }
+
+  // Make sure the requested community exists.
+  let conn = establish_connection();
+  match Community::read_from_name(&conn, community_name.to_owned()) {
+    Err(_) => return HttpResponse::NotFound().finish(),
+    Ok(c) => c,
+  };
+
+  let community_url = Community::get_community_url(&community_name);
+
+  let json = json!({
+    "subject": info.resource,
+    "aliases": [
+      community_url,
+    ],
+    "links": [
+      {
+        "rel": "http://webfinger.net/rel/profile-page",
+        "type": "text/html",
+        "href": community_url
+      },
+      {
+        "rel": "self",
+        "type": "application/activity+json",
+        "href": community_url // Yes this is correct, this link doesn't include the `.json` extension
+      }
+      // TODO: this also needs to return the subscribe link once that's implemented
+      //{
+      //  "rel": "http://ostatus.org/schema/1.0/subscribe",
+      //  "template": "https://my_instance.com/authorize_interaction?uri={uri}"
+      //}
+    ]
+  });
+  return HttpResponse::Ok()
+    .content_type("application/activity+json")
+    .body(json.to_string());
+}