From 8047df684865c5a1e14a808a9689c39b23621e09 Mon Sep 17 00:00:00 2001 From: Bat Date: Tue, 1 May 2018 12:48:19 +0100 Subject: [PATCH] Display remote profiles! --- Cargo.lock | 1 + Cargo.toml | 2 +- src/activity_pub/webfinger.rs | 6 +-- src/main.rs | 2 + src/models/instance.rs | 12 ++++++ src/models/users.rs | 73 +++++++++++++++++++++++++++++++++-- src/routes/session.rs | 2 +- src/routes/user.rs | 12 ++++-- src/routes/well_known.rs | 2 +- templates/users/details.tera | 13 +++++++ 10 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 templates/users/details.tera diff --git a/Cargo.lock b/Cargo.lock index d70a070c..6137ab2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -144,6 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.42 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/Cargo.toml b/Cargo.toml index c7ab0c87..3493fcad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ version = "0.1.0" [dependencies] base64 = "0.9.1" bcrypt = "0.2" -chrono = "0.4" +chrono = { version = "0.4", features = ["serde"] } dotenv = "*" heck = "0.3.0" hex = "0.3" diff --git a/src/activity_pub/webfinger.rs b/src/activity_pub/webfinger.rs index 2149f2e3..d9c28038 100644 --- a/src/activity_pub/webfinger.rs +++ b/src/activity_pub/webfinger.rs @@ -25,7 +25,7 @@ pub trait Webfinger { } pub fn resolve(acct: String) -> Result { - let instance = acct.split("@").next().unwrap(); + let instance = acct.split("@").last().unwrap(); let url = format!("https://{}/.well-known/webfinger?resource=acct:{}", instance, acct); Client::new() .get(&url[..]) @@ -37,12 +37,12 @@ pub fn resolve(acct: String) -> Result { json["links"].as_array().unwrap() .into_iter() .find_map(|link| { - if link["rel"].as_str().unwrap() == "self" && link["href"].as_str().unwrap() == "application/activity+json" { + if link["rel"].as_str().unwrap() == "self" && link["type"].as_str().unwrap() == "application/activity+json" { Some(String::from(link["href"].as_str().unwrap())) } else { None } }).unwrap() }) - .map_err(|_| String::from("Error while fetchin WebFinger resource")) + .map_err(|e| format!("Error while fetchin WebFinger resource ({})", e)) } diff --git a/src/main.rs b/src/main.rs index 82586157..e009dff5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,8 @@ extern crate rocket; extern crate rocket_contrib; extern crate serde; #[macro_use] +extern crate serde_derive; +#[macro_use] extern crate serde_json; use diesel::pg::PgConnection; diff --git a/src/models/instance.rs b/src/models/instance.rs index 476e525a..80b19cde 100644 --- a/src/models/instance.rs +++ b/src/models/instance.rs @@ -34,6 +34,10 @@ impl Instance { .into_iter().nth(0) } + pub fn local_id(conn: &PgConnection) -> i32 { + Instance::get_local(conn).unwrap().id + } + pub fn insert<'a>(conn: &PgConnection, loc_dom: String, pub_dom: String, name: String, local: bool) -> Instance { diesel::insert_into(instances::table) .values(NewInstance { @@ -54,6 +58,14 @@ impl Instance { .into_iter().nth(0) } + pub fn get_by_domain(conn: &PgConnection, domain: String) -> Option { + instances::table.filter(instances::public_domain.eq(domain)) + .limit(1) + .load::(conn) + .expect("Couldn't load instance by domain") + .into_iter().nth(0) + } + pub fn block(&self) {} pub fn has_admin(&self, conn: &PgConnection) -> bool { diff --git a/src/models/users.rs b/src/models/users.rs index 5f4509d4..3b00ac02 100644 --- a/src/models/users.rs +++ b/src/models/users.rs @@ -2,13 +2,17 @@ use bcrypt; use chrono::NaiveDateTime; use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection}; use diesel::dsl::any; +use reqwest::Client; +use reqwest::header::{Accept, qitem}; +use reqwest::mime::Mime; use rocket::request::{self, FromRequest, Request}; use rocket::outcome::IntoOutcome; +use serde_json; use activity_pub::activity::Activity; use activity_pub::actor::{ActorType, Actor}; use activity_pub::outbox::Outbox; -use activity_pub::webfinger::Webfinger; +use activity_pub::webfinger::{Webfinger, resolve}; use db_conn::DbConn; use models::instance::Instance; use models::post_authors::PostAuthor; @@ -17,7 +21,7 @@ use schema::users; pub const AUTH_COOKIE: &'static str = "user_id"; -#[derive(Queryable, Identifiable)] +#[derive(Queryable, Identifiable, Serialize)] pub struct User { pub id: i32, pub username: String, @@ -72,14 +76,77 @@ impl User { .into_iter().nth(0) } - pub fn find_by_name(conn: &PgConnection, username: String) -> Option { + pub fn find_by_name(conn: &PgConnection, username: String, instance_id: i32) -> Option { users::table.filter(users::username.eq(username)) + .filter(users::instance_id.eq(instance_id)) .limit(1) .load::(conn) .expect("Error loading user by email") .into_iter().nth(0) } + pub fn find_local(conn: &PgConnection, username: String) -> Option { + User::find_by_name(conn, username, Instance::local_id(conn)) + } + + pub fn find_by_fqn(conn: &PgConnection, fqn: String) -> Option { + if fqn.contains("@") { // remote user + match Instance::get_by_domain(conn, String::from(fqn.split("@").last().unwrap())) { + Some(instance) => { + match User::find_by_name(conn, String::from(fqn.split("@").nth(0).unwrap()), instance.id) { + Some(u) => Some(u), + None => User::fetch_from_webfinger(conn, fqn) + } + }, + None => User::fetch_from_webfinger(conn, fqn) + } + } else { // local user + User::find_local(conn, fqn) + } + } + + fn fetch_from_webfinger(conn: &PgConnection, acct: String) -> Option { + match resolve(acct) { + Ok(url) => { + let req = Client::new() + .get(&url[..]) + .header(Accept(vec![qitem("application/activity+json".parse::().unwrap())])) + .send(); + match req { + Ok(mut res) => { + let json: serde_json::Value = serde_json::from_str(&res.text().unwrap()).unwrap(); + Some(User::from_activity(conn, json, url.split("@").last().unwrap().to_string())) + }, + Err(_) => None + } + }, + Err(details) => { + println!("{}", details); + None + } + } + } + + fn from_activity(conn: &PgConnection, acct: serde_json::Value, inst: String) -> User { + let instance = match Instance::get_by_domain(conn, inst.clone()) { + Some(instance) => instance, + None => { + Instance::insert(conn, String::from(""), inst.clone(), inst.clone(), false) + } + }; + User::insert(conn, NewUser { + username: acct["preferredUsername"].as_str().unwrap().to_string(), + display_name: acct["name"].as_str().unwrap().to_string(), + outbox_url: acct["outbox"].as_str().unwrap().to_string(), + inbox_url: acct["inbox"].as_str().unwrap().to_string(), + is_admin: false, + summary: acct["summary"].as_str().unwrap().to_string(), + email: None, + hashed_password: None, + instance_id: instance.id + }) + } + pub fn hash_pass(pass: String) -> String { bcrypt::hash(pass.as_str(), bcrypt::DEFAULT_COST).unwrap() } diff --git a/src/routes/session.rs b/src/routes/session.rs index bd8f2350..a4aefb45 100644 --- a/src/routes/session.rs +++ b/src/routes/session.rs @@ -24,7 +24,7 @@ fn create(conn: DbConn, data: Form, mut cookies: Cookies) -> Result Ok(usr), - None => match User::find_by_name(&*conn, form.email_or_name.to_string()) { + None => match User::find_local(&*conn, form.email_or_name.to_string()) { Some(usr) => Ok(usr), None => Err("Invalid username or password") } diff --git a/src/routes/user.rs b/src/routes/user.rs index af2bb7f3..b7cc42cf 100644 --- a/src/routes/user.rs +++ b/src/routes/user.rs @@ -1,6 +1,7 @@ use rocket::request::Form; use rocket::response::Redirect; use rocket_contrib::Template; +use serde_json; use std::collections::HashMap; use activity_pub::ActivityPub; @@ -16,13 +17,16 @@ fn me(user: User) -> String { } #[get("/@/", rank = 2)] -fn details(name: String) -> String { - format!("Hello, @{}", name) +fn details(name: String, conn: DbConn) -> Template { + let user = User::find_by_fqn(&*conn, name).unwrap(); + Template::render("users/details", json!({ + "user": serde_json::to_value(user).unwrap() + })) } #[get("/@/", format = "application/activity+json", rank = 1)] fn activity_details(name: String, conn: DbConn) -> ActivityPub { - let user = User::find_by_name(&*conn, name).unwrap(); + let user = User::find_local(&*conn, name).unwrap(); user.as_activity_pub(&*conn) } @@ -61,6 +65,6 @@ fn create(conn: DbConn, data: Form) -> Redirect { #[get("/@//outbox")] fn outbox(name: String, conn: DbConn) -> Outbox { - let user = User::find_by_name(&*conn, name).unwrap(); + let user = User::find_local(&*conn, name).unwrap(); user.outbox(&*conn) } diff --git a/src/routes/well_known.rs b/src/routes/well_known.rs index e8f6e3ab..ba87a44f 100644 --- a/src/routes/well_known.rs +++ b/src/routes/well_known.rs @@ -37,7 +37,7 @@ fn webfinger(query: WebfingerQuery, conn: DbConn) -> Content Ok(usr.webfinger(&*conn)), None => match Blog::find_by_actor_id(&*conn, String::from(user)) { Some(blog) => Ok(blog.webfinger(&*conn)), diff --git a/templates/users/details.tera b/templates/users/details.tera new file mode 100644 index 00000000..7e8658a3 --- /dev/null +++ b/templates/users/details.tera @@ -0,0 +1,13 @@ + + + + + {{ user.display_name }} + + +

{{ user.display_name }}

+
+ {{ user.summary | safe }} +
+ +