2019-01-28 20:16:51 +00:00
//! # Actix Webfinger
//! A library to aid in resolving and providing webfinger objects with the Actix Web web framework.
//!
//! The main functionality this crate provides is through the `Webfinger::fetch` method for Actix
2020-04-21 19:08:59 +00:00
//! Web-based clients, and the `Resolver` trait for Actix Web-based servers.
2019-01-28 20:16:51 +00:00
//!
//! ### Usage
//! First, add Actix Webfinger as a dependency
//!
2019-07-30 22:37:43 +00:00
//! - [Read the documentation on docs.rs](https://docs.rs/actix-webfinger)
//! - [Find the crate on crates.io](https://crates.io/crates/actix-webfinger)
2020-03-16 01:03:35 +00:00
//! - [Hit me up on Mastodon](https://asonix.dog/@asonix)
2019-07-30 22:37:43 +00:00
//!
2019-01-28 20:16:51 +00:00
//! ```toml
//! [dependencies]
2020-03-16 01:03:35 +00:00
//! actix = "0.10.0-alpha.1"
//! actix-web = "3.0.0-alpha.1"
2020-03-16 02:13:11 +00:00
//! actix-webfinger = "0.3.0-alpha.2"
2019-01-28 20:16:51 +00:00
//! ```
//!
//! Then use it in your application
//!
//! #### Client Example
//! ```rust,ignore
//! use actix_webfinger::Webfinger;
2021-03-10 01:58:42 +00:00
//! use awc::Client;
2019-05-12 17:19:49 +00:00
//! use std::error::Error;
//!
2020-03-16 01:03:35 +00:00
//! #[actix_rt::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
//! let client = Client::default();
//! let wf = Webfinger::fetch(&client, "asonix@asonix.dog", "localhost:8000", false).await?;
2019-05-12 17:19:49 +00:00
//!
2020-03-16 01:03:35 +00:00
//! println!("asonix's webfinger:\n{:#?}", wf);
2019-05-12 17:19:49 +00:00
//! Ok(())
2019-01-28 20:16:51 +00:00
//! }
//! ```
//!
//! #### Server Example
//! ```rust,ignore
2019-05-12 17:19:49 +00:00
//! use actix_web::{web::Data, App, HttpServer};
2019-01-28 20:16:51 +00:00
//! use actix_webfinger::{Resolver, Webfinger};
2020-03-16 01:03:35 +00:00
//! use std::{error::Error, future::Future, pin::Pin};
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! #[derive(Clone, Debug)]
//! pub struct MyState {
//! domain: String,
//! }
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! pub struct MyResolver;
2019-05-12 17:19:49 +00:00
//!
2020-04-21 19:08:59 +00:00
//! impl Resolver for MyResolver {
//! type State = Data<MyState>;
2019-01-28 20:16:51 +00:00
//! type Error = actix_web::error::JsonPayloadError;
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! fn find(
//! account: &str,
//! domain: &str,
2020-03-16 01:17:51 +00:00
//! state: S,
2020-03-16 01:03:35 +00:00
//! ) -> Pin<Box<dyn Future<Output = Result<Option<Webfinger>, Self::Error>>>> {
2019-01-28 20:16:51 +00:00
//! let w = if domain == state.domain {
//! Some(Webfinger::new(&format!("{}@{}", account, domain)))
//! } else {
//! None
//! };
2019-05-12 17:19:49 +00:00
//!
2020-03-16 01:03:35 +00:00
//! Box::pin(async move { Ok(w) })
2019-01-28 20:16:51 +00:00
//! }
//! }
2019-05-12 17:19:49 +00:00
//!
2020-03-16 01:03:35 +00:00
//! #[actix_rt::main]
//! async fn main() -> Result<(), Box<dyn Error>> {
2019-05-12 17:19:49 +00:00
//! HttpServer::new(|| {
//! App::new()
//! .data(MyState {
//! domain: "asonix.dog".to_owned(),
//! })
2020-04-21 19:08:59 +00:00
//! .service(actix_webfinger::resource::<MyResolver>())
2019-01-28 20:16:51 +00:00
//! })
2019-05-12 17:19:49 +00:00
//! .bind("127.0.0.1:8000")?
2020-03-16 01:03:35 +00:00
//! .run()
//! .await?;
2019-05-12 17:19:49 +00:00
//!
//! Ok(())
2019-01-28 20:16:51 +00:00
//! }
//! ```
//!
//! ### Contributing
//! Feel free to open issues for anything you find an issue with. Please note that any contributed code will be licensed under the GPLv3.
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! ### License
2019-05-12 17:19:49 +00:00
//!
2020-03-16 01:03:35 +00:00
//! Copyright © 2020 Riley Trautman
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! Actix Webfinger is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! Actix Webfinger is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This file is part of Tokio ZMQ.
2019-05-12 17:19:49 +00:00
//!
2019-01-28 20:16:51 +00:00
//! You should have received a copy of the GNU General Public License along with Actix Webfinger. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).
2019-01-27 20:45:44 +00:00
use actix_web ::{
2019-05-12 17:19:49 +00:00
dev ::RequestHead ,
2019-01-27 23:48:14 +00:00
error ::ResponseError ,
2019-05-12 17:19:49 +00:00
guard ::Guard ,
2019-01-28 00:29:01 +00:00
http ::Method ,
2019-05-12 17:19:49 +00:00
web ::{ get , Query } ,
FromRequest , HttpResponse , Resource ,
2019-01-27 20:45:44 +00:00
} ;
2021-03-10 01:58:42 +00:00
use awc ::Client ;
2019-01-27 20:45:44 +00:00
use serde_derive ::{ Deserialize , Serialize } ;
2020-03-16 01:03:35 +00:00
use std ::{ future ::Future , pin ::Pin } ;
2019-01-27 20:45:44 +00:00
2019-01-28 20:16:51 +00:00
/// A predicate for Actix Web route filters
///
/// This predicate matches GET requests with valid Accept headers. A valid Accept header is any
/// Accept headers that matches a superset of `application/jrd+json`.
///
/// Valid Accept Headers
/// - `application/jrd+json'
/// - `application/json`
/// - `application/*`
/// - `*/*`
///
/// ```rust,ignore
/// use actix_web::App;
2019-05-12 17:19:49 +00:00
/// use actix_webfinger::WebfingerGuard;
2019-01-28 20:16:51 +00:00
///
/// let app = App::new()
/// .resource("/.well-known/webfinger", |r| {
/// r.route()
2019-05-12 17:19:49 +00:00
/// .filter(WebfingerGuard)
2019-01-28 20:16:51 +00:00
/// .with(your_route_handler)
/// })
/// .finish();
/// ```
2019-05-12 17:19:49 +00:00
pub struct WebfingerGuard ;
2019-01-28 00:29:01 +00:00
2019-05-12 17:19:49 +00:00
impl Guard for WebfingerGuard {
fn check ( & self , request : & RequestHead ) -> bool {
2020-03-17 16:24:51 +00:00
let valid_accept = if let Some ( val ) = request . headers ( ) . get ( " Accept " ) {
2019-01-28 00:29:01 +00:00
if let Ok ( s ) = val . to_str ( ) {
2020-04-26 01:28:17 +00:00
s . split ( ',' ) . any ( | v | {
2019-01-28 00:29:01 +00:00
let v = if let Some ( index ) = v . find ( ';' ) {
v . split_at ( index ) . 0
} else {
v
} ;
let trimmed = v . trim ( ) ;
2020-03-17 16:24:51 +00:00
// The following accept mimes are valid
2019-01-28 00:29:01 +00:00
trimmed = = " application/jrd+json "
| | trimmed = = " application/json "
| | trimmed = = " application/* "
| | trimmed = = " */* "
2020-03-17 16:24:51 +00:00
} )
} else {
// unparsable accept headers are not valid
false
2019-01-28 00:29:01 +00:00
}
2020-03-17 16:24:51 +00:00
} else {
// no accept header is valid i guess
true
} ;
2019-01-28 00:29:01 +00:00
2020-03-17 16:24:51 +00:00
valid_accept & & request . method = = Method ::GET
2019-01-28 00:29:01 +00:00
}
}
2019-05-12 17:19:49 +00:00
/// A simple way to mount the webfinger service to your Actix Web application
2019-01-28 20:16:51 +00:00
///
/// ```rust,ignore
2019-05-12 17:19:49 +00:00
/// use actix_web::HttpServer;
2019-01-28 20:16:51 +00:00
///
2019-05-12 17:19:49 +00:00
/// HttpServer::new(|| {
/// App::new()
2020-04-21 19:08:59 +00:00
/// .service(actix_webfinger::resource::<MyResolver>())
2019-01-28 20:16:51 +00:00
/// })
/// .bind("127.0.0.1:8000")?
2019-05-12 17:19:49 +00:00
/// .start();
2019-01-28 20:16:51 +00:00
/// ```
2020-04-21 19:08:59 +00:00
pub fn resource < R > ( ) -> Resource
2019-01-28 00:37:49 +00:00
where
2020-04-21 19:08:59 +00:00
R : Resolver + 'static ,
2019-01-28 00:37:49 +00:00
{
2019-05-12 17:19:49 +00:00
actix_web ::web ::resource ( " /.well-known/webfinger " )
. guard ( WebfingerGuard )
2020-04-21 19:08:59 +00:00
. route ( get ( ) . to ( endpoint ::< R > ) )
2019-05-12 17:19:49 +00:00
}
/// A simple way to mount the webfinger service inside the /.well-known scope
///
/// ```rust,ignore
/// use actix_web::{App, HttpServer, web::scope};
/// use actix_webfinger::resource;
///
/// HttpServer::new(|| {
/// App::new()
/// .data(())
/// .service(
/// scope("/")
2020-04-21 19:08:59 +00:00
/// .service(resource::<MyResolver>())
2019-05-12 17:19:49 +00:00
/// )
/// })
/// .bind("127.0.0.1:8000")
/// .start();
/// ```
2020-04-21 19:08:59 +00:00
pub fn scoped < R > ( ) -> Resource
2019-05-12 17:19:49 +00:00
where
2020-04-21 19:08:59 +00:00
R : Resolver + 'static ,
2019-05-12 17:19:49 +00:00
{
actix_web ::web ::resource ( " /webfinger " )
. guard ( WebfingerGuard )
2020-04-21 19:08:59 +00:00
. route ( get ( ) . to ( endpoint ::< R > ) )
2019-01-28 00:37:49 +00:00
}
2019-01-28 20:16:51 +00:00
/// The error created if the webfinger resource query is malformed
///
/// Resource queries should have a valid `username@instance` format.
///
/// The following resource formats will not produce errors
/// - `acct:asonix@asonix.dog`
/// - `acct:@asonix@asonix.dog`
/// - `asonix@asonix.dog`
/// - `@asonix@asonix.dog`
///
/// The following resource formats will produce errors
/// - `@asonix`
/// - `asonix`
///
/// This error type captures the invalid string for inspection
2020-03-16 01:03:35 +00:00
#[ derive(Clone, Debug, thiserror::Error) ]
#[ error( " Resource {0} is invalid " ) ]
2019-01-27 23:48:14 +00:00
pub struct InvalidResource ( String ) ;
2019-01-28 20:16:51 +00:00
/// A type representing a valid resource query
///
/// Resource queries should have a valid `username@instance` format.
///
/// The following resource formats will not produce errors
/// - `acct:asonix@asonix.dog`
/// - `acct:@asonix@asonix.dog`
/// - `asonix@asonix.dog`
/// - `@asonix@asonix.dog`
///
/// The following resource formats will produce errors
/// - `@asonix`
/// - `asonix`
///
/// This type implements `FromStr` and `serde::de::Deserialize` so it can be used to enforce valid
/// formatting before the request reaches the route handler.
2019-01-27 23:48:14 +00:00
#[ derive(Clone, Debug) ]
pub struct WebfingerResource {
2019-01-28 20:16:51 +00:00
pub account : String ,
pub domain : String ,
2019-01-27 23:48:14 +00:00
}
impl std ::str ::FromStr for WebfingerResource {
type Err = InvalidResource ;
fn from_str ( s : & str ) -> Result < Self , Self ::Err > {
let trimmed = s . trim_start_matches ( " acct: " ) . trim_start_matches ( '@' ) ;
if let Some ( index ) = trimmed . find ( '@' ) {
let ( account , domain ) = trimmed . split_at ( index ) ;
Ok ( WebfingerResource {
account : account . to_owned ( ) ,
domain : domain . trim_start_matches ( '@' ) . to_owned ( ) ,
} )
} else {
Err ( InvalidResource ( s . to_owned ( ) ) )
}
}
}
impl < ' de > serde ::de ::Deserialize < ' de > for WebfingerResource {
fn deserialize < D > ( deserializer : D ) -> Result < Self , D ::Error >
where
D : serde ::de ::Deserializer < ' de > ,
{
let s = String ::deserialize ( deserializer ) ? ;
s . parse ::< WebfingerResource > ( )
. map_err ( serde ::de ::Error ::custom )
}
}
2019-01-28 20:16:51 +00:00
/// A wrapper type for a Webfinger Resource
///
/// This type is used to deserialize from queries like the following:
/// - `resource=acct:asonix@asonix.dog`
///
/// This can be used in Actix Web with the following code:
///
/// ```rust,ignore
/// use actix_web::Query;
/// use actix_webfinger::{WebfingerQuery, WebfingerResource};
///
/// fn my_route(query: Query<WebfingerQuery>) -> String {
/// let WebfingerResource {
/// account,
/// domain,
/// } = query.into_inner().resource;
///
/// // do things
/// String::from("got resource")
/// }
/// ```
2019-01-27 23:48:14 +00:00
#[ derive(Clone, Debug, Deserialize) ]
pub struct WebfingerQuery {
resource : WebfingerResource ,
}
2019-01-28 20:16:51 +00:00
/// A trait to ease the implementation of Webfinger Resolvers
///
/// ```rust,ignore
/// use actix_webfinger::{Resolver, Webfinger};
2020-03-16 01:03:35 +00:00
/// use std::{future::Future, pin::Pin};
2019-01-28 20:16:51 +00:00
///
/// struct MyResolver;
///
2020-04-21 19:08:59 +00:00
/// impl Resolver for MyResolver {
/// type State = ();
2019-01-28 20:16:51 +00:00
/// type Error = CustomError;
///
/// fn find(
/// account: &str,
/// domain: &str,
2020-04-21 19:08:59 +00:00
/// _state: Self::State,
2020-03-16 01:03:35 +00:00
/// ) -> Pin<Box<dyn Future<Output = Result<Option<Webfinger>, Self::Error>>>> {
2019-01-28 20:16:51 +00:00
/// let webfinger = Webfinger::new(&format!("{}@{}", account, domain));
///
/// // do something
///
2020-03-16 01:03:35 +00:00
/// Box::pin(async move { Ok(Some(webfinger)) })
2019-01-28 20:16:51 +00:00
/// }
/// }
///
2020-03-16 01:03:35 +00:00
/// #[actix_rt::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
2019-05-12 17:19:49 +00:00
/// HttpServer::new(|| {
2019-01-28 20:16:51 +00:00
/// App::new()
2019-05-12 17:19:49 +00:00
/// .data(())
2020-04-21 19:08:59 +00:00
/// .service(resource::<MyResolver>())
2019-01-28 20:16:51 +00:00
/// })
/// .bind("127.0.0.1:8000")?
2020-03-16 01:03:35 +00:00
/// .run()
/// .await?;
2019-05-12 17:19:49 +00:00
///
/// Ok(())
2019-01-28 20:16:51 +00:00
/// }
/// ```
2020-04-21 19:08:59 +00:00
pub trait Resolver {
type State : FromRequest + 'static ;
2019-05-12 17:19:49 +00:00
type Error : ResponseError + 'static ;
2019-01-27 23:48:14 +00:00
fn find (
account : & str ,
domain : & str ,
2020-04-21 19:08:59 +00:00
state : Self ::State ,
2020-04-26 01:28:17 +00:00
) -> Pin < Box < dyn Future < Output = WebfingerResult < Self ::Error > > > > ;
2020-04-21 19:08:59 +00:00
}
2020-04-26 01:28:17 +00:00
type WebfingerResult < E > = Result < Option < Webfinger > , E > ;
2019-01-27 23:48:14 +00:00
2020-04-21 19:08:59 +00:00
pub fn endpoint < R > (
( query , state ) : ( Query < WebfingerQuery > , R ::State ) ,
) -> Pin < Box < dyn Future < Output = Result < HttpResponse , R ::Error > > > >
where
R : Resolver ,
{
let WebfingerResource { account , domain } = query . into_inner ( ) . resource ;
2019-01-27 23:48:14 +00:00
2020-04-21 19:08:59 +00:00
Box ::pin ( async move {
match R ::find ( & account , & domain , state ) . await ? {
Some ( w ) = > Ok ( w . respond ( ) ) ,
None = > Ok ( HttpResponse ::NotFound ( ) . finish ( ) ) ,
}
} )
2019-01-27 23:48:14 +00:00
}
2019-01-28 20:16:51 +00:00
/// The webfinger Link type
///
/// All links have a `rel` the determines what the link is describing, most have a `type` that adds
/// some more context, and a `href` to point to, or contain the data.
///
/// In some cases, the Link can have a `rel` and a `template` and nothing else.
///
/// This type can be serialized and deserialized
2019-01-27 20:45:44 +00:00
#[ derive(Clone, Debug, Deserialize, Serialize) ]
pub struct Link {
pub rel : String ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub href : Option < String > ,
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub template : Option < String > ,
2019-01-28 20:16:51 +00:00
/// renamed to `type` via serde
2019-01-27 20:45:44 +00:00
#[ serde(rename = " type " ) ]
#[ serde(skip_serializing_if = " Option::is_none " ) ]
pub kind : Option < String > ,
}
2019-01-28 20:16:51 +00:00
/// The webfinger type
///
/// Webfinger data has three parts
/// - aliases
/// - links
/// - subject
///
/// `subject` defines the name or primary identifier of the object being looked up.
/// `aliases` are alternate names for the object.
/// `links` are references to more information about the subject, and can be in a variety of
/// formats. For example, some links will reference activitypub person objects, and others will
/// contain public key data.
///
/// This type can be serialized and deserialized
2019-01-27 20:45:44 +00:00
#[ derive(Clone, Debug, Deserialize, Serialize) ]
pub struct Webfinger {
pub aliases : Vec < String > ,
pub links : Vec < Link > ,
pub subject : String ,
}
impl Webfinger {
2019-01-28 20:16:51 +00:00
/// Create a new Webfinger with the given subject
2019-01-27 23:48:14 +00:00
pub fn new ( subject : & str ) -> Self {
Webfinger {
aliases : Vec ::new ( ) ,
links : Vec ::new ( ) ,
subject : subject . to_owned ( ) ,
}
}
2019-01-28 20:16:51 +00:00
/// Add multiple aliases to the Webfinger
2019-01-27 23:48:14 +00:00
pub fn add_aliases ( & mut self , aliases : & [ String ] ) -> & mut Self {
self . aliases . extend_from_slice ( aliases ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Add a single alias to the Webfinger
2019-01-27 23:48:14 +00:00
pub fn add_alias ( & mut self , alias : & str ) -> & mut Self {
self . aliases . push ( alias . to_owned ( ) ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get the aliases for this Webfinger
2019-01-27 20:45:44 +00:00
pub fn aliases ( & self ) -> & [ String ] {
& self . aliases
}
2019-01-28 20:16:51 +00:00
/// Add multiple Links to this Webfinger
pub fn add_links ( & mut self , links : & [ Link ] ) -> & mut Self {
self . links . extend_from_slice ( links ) ;
self
}
/// Add single Link to this Webfinger
2019-01-27 23:48:14 +00:00
pub fn add_link ( & mut self , link : Link ) -> & mut Self {
self . links . push ( link ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get the Links from this Webfinger
2019-01-27 20:45:44 +00:00
pub fn links ( & self ) -> & [ Link ] {
& self . links
}
2020-04-21 18:51:43 +00:00
/// Add an ActivityPub link to this Webfinger
///
/// Since ActivityPub extends JsonLD, this also adds an ActivityStreams JsonLD link
2019-01-27 23:48:14 +00:00
pub fn add_activitypub ( & mut self , href : & str ) -> & mut Self {
self . links . push ( Link {
rel : " self " . to_owned ( ) ,
kind : Some ( " application/activity+json " . to_owned ( ) ) ,
href : Some ( href . to_owned ( ) ) ,
template : None ,
} ) ;
2020-04-21 18:51:43 +00:00
self . add_json_ld ( href , " https://www.w3.org/ns/activitystreams " )
}
/// Add a JsonLD Link to this Webfinger
2020-04-22 00:24:23 +00:00
pub fn add_json_ld ( & mut self , href : & str , profile : & str ) -> & mut Self {
2020-04-21 18:51:43 +00:00
self . links . push ( Link {
rel : " self " . to_owned ( ) ,
2020-04-22 00:24:23 +00:00
kind : Some ( format! ( " application/ld+json; profile= \" {} \" " , profile ) ) ,
2020-04-21 18:51:43 +00:00
href : Some ( href . to_owned ( ) ) ,
template : None ,
} ) ;
2019-01-27 23:48:14 +00:00
self
}
2020-04-21 18:51:43 +00:00
/// Get an ActivityPub link from this Webfinger
2019-01-27 20:45:44 +00:00
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 )
} )
}
2020-04-21 18:51:43 +00:00
/// Get a JsonLD link from this Webfinger
pub fn json_ld ( & self ) -> Option < & Link > {
self . links . iter ( ) . find ( | l | {
l . rel = = " self "
& & l . kind
. as_ref ( )
. map ( | k | k . starts_with ( " application/ld+json " ) )
. unwrap_or ( false )
} )
}
2019-01-28 20:16:51 +00:00
/// Add a profile link to this Webfinger
2019-01-27 23:48:14 +00:00
pub fn add_profile ( & mut self , href : & str ) -> & mut Self {
self . links . push ( Link {
rel : " http://webfinger.net/rel/profile-page " . to_owned ( ) ,
href : Some ( href . to_owned ( ) ) ,
kind : Some ( " text/html " . to_owned ( ) ) ,
template : None ,
} ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get a profile link from this Webfinger
2019-01-27 20:45:44 +00:00
pub fn profile ( & self ) -> Option < & Link > {
self . links
. iter ( )
. find ( | l | l . rel = = " http://webfinger.net/rel/profile-page " )
}
2019-01-28 20:16:51 +00:00
/// Add an atom link to this Webfinger
2019-01-27 23:48:14 +00:00
pub fn add_atom ( & mut self , href : & str ) -> & mut Self {
self . links . push ( Link {
rel : " http://schemas.google.com/g/2010#updates-from " . to_owned ( ) ,
href : Some ( href . to_owned ( ) ) ,
kind : Some ( " application/atom+xml " . to_owned ( ) ) ,
template : None ,
} ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get an atom link from this Webfinger
2019-01-27 20:45:44 +00:00
pub fn atom ( & self ) -> Option < & Link > {
self . links
. iter ( )
. find ( | l | l . rel = = " http://schemas.google.com/g/2010#updates-from " )
}
2019-01-28 20:16:51 +00:00
/// Set a salmon link from this Webfinger
2020-03-16 02:13:11 +00:00
pub fn add_salmon ( & mut self , href : & str ) -> & mut Self {
2019-01-27 23:48:14 +00:00
self . links . push ( Link {
rel : " salmon " . to_owned ( ) ,
href : Some ( href . to_owned ( ) ) ,
kind : None ,
template : None ,
} ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get a salmon link from this Webfinger
2019-01-27 20:45:44 +00:00
pub fn salmon ( & self ) -> Option < & Link > {
self . links . iter ( ) . find ( | l | l . rel = = " salmon " )
}
2019-01-28 20:16:51 +00:00
/// Set a magic public key link for this Webfinger
2020-03-16 02:13:11 +00:00
pub fn add_magic_public_key ( & mut self , magic_public_key : & str ) -> & mut Self {
2019-01-27 23:48:14 +00:00
self . links . push ( Link {
rel : " magic-public-key " . to_owned ( ) ,
href : Some ( format! (
" data:application/magic-public-key,{} " ,
magic_public_key
) ) ,
kind : None ,
template : None ,
} ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get a magic public key link from this Webfinger
2019-01-27 20:45:44 +00:00
pub fn magic_public_key ( & self ) -> Option < & Link > {
self . links . iter ( ) . find ( | l | l . rel = = " magic-public-key " )
}
2019-01-28 20:16:51 +00:00
/// Set an ostatus link for this Webfinger
2020-03-16 02:13:11 +00:00
pub fn add_ostatus ( & mut self , template : & str ) -> & mut Self {
2019-01-27 23:48:14 +00:00
self . links . push ( Link {
rel : " http://ostatus.org/schema/1.0/subscribe " . to_owned ( ) ,
href : None ,
kind : None ,
template : Some ( template . to_owned ( ) ) ,
} ) ;
self
}
2019-01-28 20:16:51 +00:00
/// Get an ostatus link from this Webfinger
2019-01-27 20:45:44 +00:00
pub fn ostatus ( & self ) -> Option < & Link > {
self . links
. iter ( )
. find ( | l | l . rel = = " http://ostatus.org/schema/1.0/subscribe " )
}
2019-01-28 20:16:51 +00:00
/// Turn this Webfinger into an actix web HttpResponse
2019-01-27 23:48:14 +00:00
pub fn respond ( self ) -> HttpResponse {
HttpResponse ::Ok ( )
. content_type ( " application/jrd+json " )
2021-02-10 23:49:15 +00:00
. json ( & self )
2019-01-27 23:48:14 +00:00
}
2019-01-28 20:16:51 +00:00
/// Fetch a webfinger with subject `user` from a given `domain`
///
2020-03-16 01:03:35 +00:00
/// This method takes a `Client` so derivative works can provide their own configured clients
/// rather this library generating it's own http clients.
pub async fn fetch (
client : & Client ,
2019-01-27 20:45:44 +00:00
user : & str ,
domain : & str ,
2019-01-27 23:48:14 +00:00
https : bool ,
2020-03-16 01:03:35 +00:00
) -> Result < Self , FetchError > {
2019-01-27 20:45:44 +00:00
let url = format! (
2019-01-27 23:48:14 +00:00
" {}://{}/.well-known/webfinger?resource=acct:{} " ,
2019-01-28 00:29:01 +00:00
if https { " https " } else { " http " } ,
domain ,
user
2019-01-27 20:45:44 +00:00
) ;
2020-03-16 01:03:35 +00:00
let mut res = client
2019-05-12 17:19:49 +00:00
. get ( url )
2021-02-10 23:49:15 +00:00
. append_header ( ( " Accept " , " application/jrd+json " ) )
2019-05-12 17:19:49 +00:00
. send ( )
2020-03-16 01:03:35 +00:00
. await
. map_err ( | _ | FetchError ::Send ) ? ;
2019-01-27 20:45:44 +00:00
2020-03-16 01:03:35 +00:00
res . json ::< Webfinger > ( ) . await . map_err ( | _ | FetchError ::Parse )
2019-01-27 20:45:44 +00:00
}
}
2020-03-16 01:03:35 +00:00
#[ derive(Clone, Debug, thiserror::Error) ]
2019-05-12 17:19:49 +00:00
pub enum FetchError {
2020-03-16 01:03:35 +00:00
#[ error( " Failed to send request " ) ]
2019-05-12 17:19:49 +00:00
Send ,
2020-03-16 01:03:35 +00:00
#[ error( " Failed to parse response JSON " ) ]
2019-05-12 17:19:49 +00:00
Parse ,
}
2019-01-27 20:45:44 +00:00
#[ cfg(test) ]
mod tests {
2019-01-27 23:48:14 +00:00
use crate ::{ Webfinger , WebfingerQuery } ;
2019-01-27 20:45:44 +00:00
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}"}]}"# ;
2019-01-27 23:48:14 +00:00
const QUERIES : [ & 'static str ; 4 ] = [
r # "{"resource":"acct:asonix@asonix.dog"}"# ,
r # "{"resource":"asonix@asonix.dog"}"# ,
r # "{"resource":"acct:@asonix@asonix.dog"}"# ,
r # "{"resource":"@asonix@asonix.dog"}"# ,
] ;
2019-01-27 20:45:44 +00:00
#[ test ]
fn can_deserialize_sir_boops ( ) {
let webfinger : Result < Webfinger , _ > = 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 ( ) ) ;
}
2019-01-27 23:48:14 +00:00
#[ test ]
fn can_deserialize_queries ( ) {
for resource in & QUERIES {
let res : Result < WebfingerQuery , _ > = serde_json ::from_str ( resource ) ;
assert! ( res . is_ok ( ) ) ;
let query = res . unwrap ( ) ;
assert_eq! ( query . resource . account , " asonix " ) ;
assert_eq! ( query . resource . domain , " asonix.dog " ) ;
}
}
2019-01-27 20:45:44 +00:00
}