Display remote profiles!

This commit is contained in:
Bat 2018-05-01 12:48:19 +01:00
parent 44473aa292
commit 8047df6848
10 changed files with 112 additions and 13 deletions

1
Cargo.lock generated
View file

@ -144,6 +144,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"num-integer 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
] ]

View file

@ -5,7 +5,7 @@ version = "0.1.0"
[dependencies] [dependencies]
base64 = "0.9.1" base64 = "0.9.1"
bcrypt = "0.2" bcrypt = "0.2"
chrono = "0.4" chrono = { version = "0.4", features = ["serde"] }
dotenv = "*" dotenv = "*"
heck = "0.3.0" heck = "0.3.0"
hex = "0.3" hex = "0.3"

View file

@ -25,7 +25,7 @@ pub trait Webfinger {
} }
pub fn resolve(acct: String) -> Result<String, String> { pub fn resolve(acct: String) -> Result<String, String> {
let instance = acct.split("@").next().unwrap(); let instance = acct.split("@").last().unwrap();
let url = format!("https://{}/.well-known/webfinger?resource=acct:{}", instance, acct); let url = format!("https://{}/.well-known/webfinger?resource=acct:{}", instance, acct);
Client::new() Client::new()
.get(&url[..]) .get(&url[..])
@ -37,12 +37,12 @@ pub fn resolve(acct: String) -> Result<String, String> {
json["links"].as_array().unwrap() json["links"].as_array().unwrap()
.into_iter() .into_iter()
.find_map(|link| { .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())) Some(String::from(link["href"].as_str().unwrap()))
} else { } else {
None None
} }
}).unwrap() }).unwrap()
}) })
.map_err(|_| String::from("Error while fetchin WebFinger resource")) .map_err(|e| format!("Error while fetchin WebFinger resource ({})", e))
} }

View file

@ -15,6 +15,8 @@ extern crate rocket;
extern crate rocket_contrib; extern crate rocket_contrib;
extern crate serde; extern crate serde;
#[macro_use] #[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate serde_json; extern crate serde_json;
use diesel::pg::PgConnection; use diesel::pg::PgConnection;

View file

@ -34,6 +34,10 @@ impl Instance {
.into_iter().nth(0) .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 { pub fn insert<'a>(conn: &PgConnection, loc_dom: String, pub_dom: String, name: String, local: bool) -> Instance {
diesel::insert_into(instances::table) diesel::insert_into(instances::table)
.values(NewInstance { .values(NewInstance {
@ -54,6 +58,14 @@ impl Instance {
.into_iter().nth(0) .into_iter().nth(0)
} }
pub fn get_by_domain(conn: &PgConnection, domain: String) -> Option<Instance> {
instances::table.filter(instances::public_domain.eq(domain))
.limit(1)
.load::<Instance>(conn)
.expect("Couldn't load instance by domain")
.into_iter().nth(0)
}
pub fn block(&self) {} pub fn block(&self) {}
pub fn has_admin(&self, conn: &PgConnection) -> bool { pub fn has_admin(&self, conn: &PgConnection) -> bool {

View file

@ -2,13 +2,17 @@ use bcrypt;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection}; use diesel::{self, QueryDsl, RunQueryDsl, ExpressionMethods, BelongingToDsl, PgConnection};
use diesel::dsl::any; use diesel::dsl::any;
use reqwest::Client;
use reqwest::header::{Accept, qitem};
use reqwest::mime::Mime;
use rocket::request::{self, FromRequest, Request}; use rocket::request::{self, FromRequest, Request};
use rocket::outcome::IntoOutcome; use rocket::outcome::IntoOutcome;
use serde_json;
use activity_pub::activity::Activity; use activity_pub::activity::Activity;
use activity_pub::actor::{ActorType, Actor}; use activity_pub::actor::{ActorType, Actor};
use activity_pub::outbox::Outbox; use activity_pub::outbox::Outbox;
use activity_pub::webfinger::Webfinger; use activity_pub::webfinger::{Webfinger, resolve};
use db_conn::DbConn; use db_conn::DbConn;
use models::instance::Instance; use models::instance::Instance;
use models::post_authors::PostAuthor; use models::post_authors::PostAuthor;
@ -17,7 +21,7 @@ use schema::users;
pub const AUTH_COOKIE: &'static str = "user_id"; pub const AUTH_COOKIE: &'static str = "user_id";
#[derive(Queryable, Identifiable)] #[derive(Queryable, Identifiable, Serialize)]
pub struct User { pub struct User {
pub id: i32, pub id: i32,
pub username: String, pub username: String,
@ -72,14 +76,77 @@ impl User {
.into_iter().nth(0) .into_iter().nth(0)
} }
pub fn find_by_name(conn: &PgConnection, username: String) -> Option<User> { pub fn find_by_name(conn: &PgConnection, username: String, instance_id: i32) -> Option<User> {
users::table.filter(users::username.eq(username)) users::table.filter(users::username.eq(username))
.filter(users::instance_id.eq(instance_id))
.limit(1) .limit(1)
.load::<User>(conn) .load::<User>(conn)
.expect("Error loading user by email") .expect("Error loading user by email")
.into_iter().nth(0) .into_iter().nth(0)
} }
pub fn find_local(conn: &PgConnection, username: String) -> Option<User> {
User::find_by_name(conn, username, Instance::local_id(conn))
}
pub fn find_by_fqn(conn: &PgConnection, fqn: String) -> Option<User> {
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<User> {
match resolve(acct) {
Ok(url) => {
let req = Client::new()
.get(&url[..])
.header(Accept(vec![qitem("application/activity+json".parse::<Mime>().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 { pub fn hash_pass(pass: String) -> String {
bcrypt::hash(pass.as_str(), bcrypt::DEFAULT_COST).unwrap() bcrypt::hash(pass.as_str(), bcrypt::DEFAULT_COST).unwrap()
} }

View file

@ -24,7 +24,7 @@ fn create(conn: DbConn, data: Form<LoginForm>, mut cookies: Cookies) -> Result<R
let form = data.get(); let form = data.get();
let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) { let user = match User::find_by_email(&*conn, form.email_or_name.to_string()) {
Some(usr) => Ok(usr), Some(usr) => 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), Some(usr) => Ok(usr),
None => Err("Invalid username or password") None => Err("Invalid username or password")
} }

View file

@ -1,6 +1,7 @@
use rocket::request::Form; use rocket::request::Form;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket_contrib::Template; use rocket_contrib::Template;
use serde_json;
use std::collections::HashMap; use std::collections::HashMap;
use activity_pub::ActivityPub; use activity_pub::ActivityPub;
@ -16,13 +17,16 @@ fn me(user: User) -> String {
} }
#[get("/@/<name>", rank = 2)] #[get("/@/<name>", rank = 2)]
fn details(name: String) -> String { fn details(name: String, conn: DbConn) -> Template {
format!("Hello, @{}", name) let user = User::find_by_fqn(&*conn, name).unwrap();
Template::render("users/details", json!({
"user": serde_json::to_value(user).unwrap()
}))
} }
#[get("/@/<name>", format = "application/activity+json", rank = 1)] #[get("/@/<name>", format = "application/activity+json", rank = 1)]
fn activity_details(name: String, conn: DbConn) -> ActivityPub { 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) user.as_activity_pub(&*conn)
} }
@ -61,6 +65,6 @@ fn create(conn: DbConn, data: Form<NewUserForm>) -> Redirect {
#[get("/@/<name>/outbox")] #[get("/@/<name>/outbox")]
fn outbox(name: String, conn: DbConn) -> 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) user.outbox(&*conn)
} }

View file

@ -37,7 +37,7 @@ fn webfinger(query: WebfingerQuery, conn: DbConn) -> Content<Result<String, &'st
let domain = Instance::get_local(&*conn).unwrap().public_domain; let domain = Instance::get_local(&*conn).unwrap().public_domain;
if res_dom == domain { if res_dom == domain {
let res = match User::find_by_name(&*conn, String::from(user)) { let res = match User::find_local(&*conn, String::from(user)) {
Some(usr) => Ok(usr.webfinger(&*conn)), Some(usr) => Ok(usr.webfinger(&*conn)),
None => match Blog::find_by_actor_id(&*conn, String::from(user)) { None => match Blog::find_by_actor_id(&*conn, String::from(user)) {
Some(blog) => Ok(blog.webfinger(&*conn)), Some(blog) => Ok(blog.webfinger(&*conn)),

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>{{ user.display_name }}</title>
</head>
<body>
<h1>{{ user.display_name }}</h1>
<div>
{{ user.summary | safe }}
</div>
</body>
</html>