commit 3358daee48e035ddbb1e0ccae90361900a438212 Author: asonix Date: Sun Jan 27 14:45:44 2019 -0600 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f88dba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9327dd0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "actix-webfinger" +description = "Types and helpers to create and fetch Webfinger resources" +version = "0.1.0" +license = "GPL-3.0" +authors = ["asonix "] +repository = "https://git.asonix.dog/asonix/actix-webfinger" +readme = "README.md" +edition = "2018" + +[dependencies] +actix = "0.7" +actix-web = "0.7" +failure = "0.1" +futures = "0.1" +serde = "1.0" +serde_derive = "1.0" + +[dev-dependencies] +actix-web = { version = "0.7", features = ["ssl"] } +openssl = "0.10" +serde_json = "1.0" diff --git a/README.md b/README.md new file mode 100644 index 0000000..55afe02 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Actix Webfinger diff --git a/examples/sir_boops.rs b/examples/sir_boops.rs new file mode 100644 index 0000000..5c2710c --- /dev/null +++ b/examples/sir_boops.rs @@ -0,0 +1,26 @@ +use actix::{Actor, System}; +use actix_web::client::ClientConnector; +use actix_webfinger::Webfinger; +use futures::Future; +use openssl::ssl::{SslConnector, SslMethod}; + +fn main() { + let sys = System::new("sir-boops"); + + let ssl_conn = SslConnector::builder(SslMethod::tls()).unwrap().build(); + let conn = ClientConnector::with_connector(ssl_conn).start(); + + let fut = Webfinger::fetch(conn, "Sir_Boops@sergal.org", "mastodon.sergal.org") + .map(move |w: Webfinger| { + if let Some(ref link) = w.activitypub() { + println!("Sir Boop's activitypub: {:#?}", link); + } + + System::current().stop(); + }) + .map_err(|e| eprintln!("Error: {}", e)); + + actix::spawn(fut); + + let _ = sys.run(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9a343fc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,120 @@ +use actix::Addr; +use actix_web::{ + client::{self, ClientConnector}, + HttpMessage, +}; +use futures::{future::IntoFuture, Future}; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Link { + pub rel: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub href: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub template: Option, + + #[serde(rename = "type")] + #[serde(skip_serializing_if = "Option::is_none")] + pub kind: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Webfinger { + pub aliases: Vec, + pub links: Vec, + pub subject: String, +} + +impl Webfinger { + pub fn aliases(&self) -> &[String] { + &self.aliases + } + + pub fn links(&self) -> &[Link] { + &self.links + } + + pub fn activitypub(&self) -> Option<&Link> { + self.links.iter().find(|l| { + l.rel == "self" + && l.kind + .as_ref() + .map(|k| k == "application/activity+json") + .unwrap_or(false) + }) + } + + pub fn profile(&self) -> Option<&Link> { + self.links + .iter() + .find(|l| l.rel == "http://webfinger.net/rel/profile-page") + } + + pub fn atom(&self) -> Option<&Link> { + self.links + .iter() + .find(|l| l.rel == "http://schemas.google.com/g/2010#updates-from") + } + + pub fn salmon(&self) -> Option<&Link> { + self.links.iter().find(|l| l.rel == "salmon") + } + + pub fn magic_public_key(&self) -> Option<&Link> { + self.links.iter().find(|l| l.rel == "magic-public-key") + } + + pub fn ostatus(&self) -> Option<&Link> { + self.links + .iter() + .find(|l| l.rel == "http://ostatus.org/schema/1.0/subscribe") + } + + pub fn fetch( + conn: Addr, + user: &str, + domain: &str, + ) -> Box> { + let url = format!( + "https://{}/.well-known/webfinger?resource=acct:{}", + domain, user + ); + + let fut = client::get(url) + .with_connector(conn) + .header("Accept", "application/json") + .finish() + .into_future() + .and_then(|r| { + r.send() + .from_err() + .and_then(|res| res.json::().from_err()) + }); + + Box::new(fut) + } +} + +#[cfg(test)] +mod tests { + use crate::Webfinger; + + const SIR_BOOPS: &'static str = r#"{"subject":"acct:Sir_Boops@sergal.org","aliases":["https://mastodon.sergal.org/@Sir_Boops","https://mastodon.sergal.org/users/Sir_Boops"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://mastodon.sergal.org/@Sir_Boops"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://mastodon.sergal.org/users/Sir_Boops.atom"},{"rel":"self","type":"application/activity+json","href":"https://mastodon.sergal.org/users/Sir_Boops"},{"rel":"salmon","href":"https://mastodon.sergal.org/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.vwDujxmxoYHs64MyVB3LG5ZyBxV3ufaMRBFu42bkcTpISq1WwZ-3Zb6CI8zOO-nM-Q2llrVRYjZa4ZFnOLvMTq_Kf-Zf5wy2aCRer88gX-MsJOAtItSi412y0a_rKOuFaDYLOLeTkRvmGLgZWbsrZJOp-YWb3zQ5qsIOInkc5BwI172tMsGeFtsnbNApPV4lrmtTGaJ8RiM8MR7XANBOfOHggSt1-eAIKGIsCmINEMzs1mG9D75xKtC_sM8GfbvBclQcBstGkHAEj1VHPW0ch6Bok5_QQppicyb8UA1PAA9bznSFtKlYE4xCH8rlCDSDTBRtdnBWHKcj619Ujz4Qaw==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://mastodon.sergal.org/authorize_interaction?uri={uri}"}]}"#; + + #[test] + fn can_deserialize_sir_boops() { + let webfinger: Result = serde_json::from_str(SIR_BOOPS); + + assert!(webfinger.is_ok()); + + let webfinger = webfinger.unwrap(); + + assert!(webfinger.salmon().is_some()); + assert!(webfinger.ostatus().is_some()); + assert!(webfinger.activitypub().is_some()); + assert!(webfinger.atom().is_some()); + assert!(webfinger.magic_public_key().is_some()); + assert!(webfinger.profile().is_some()); + } +}