diff --git a/docker/federation-test/docker-compose.yml b/docker/federation-test/docker-compose.yml index 63dbf27d3..de539237b 100644 --- a/docker/federation-test/docker-compose.yml +++ b/docker/federation-test/docker-compose.yml @@ -10,8 +10,9 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FRONT_END_DIR=/app/dist - - LEMMY_FEDERATION_ENABLED=true - - LEMMY_FEDERATED_INSTANCE=lemmy_beta:8541 + - LEMMY_FEDERATION__ENABLED=true + - LEMMY_FEDERATION__FOLLOWED_INSTANCES=lemmy_beta:8541 + - LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_PORT=8540 - RUST_BACKTRACE=1 restart: always @@ -36,8 +37,9 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FRONT_END_DIR=/app/dist - - LEMMY_FEDERATION_ENABLED=true - - LEMMY_FEDERATED_INSTANCE=lemmy_alpha:8540 + - LEMMY_FEDERATION__ENABLED=true + - LEMMY_FEDERATION__FOLLOWED_INSTANCES=lemmy_alpha:8540 + - LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_PORT=8541 - RUST_BACKTRACE=1 restart: always diff --git a/server/config/defaults.hjson b/server/config/defaults.hjson index 7eda46ec4..e048ced89 100644 --- a/server/config/defaults.hjson +++ b/server/config/defaults.hjson @@ -24,11 +24,6 @@ jwt_secret: "changeme" # The dir for the front end front_end_dir: "../ui/dist" - # whether to enable activitypub federation. this feature is in alpha, do not enable in production. - federation_enabled: false - // another instance to federate with. this should be a list, but it seems like lists cant be set from environment - // https://github.com/mehcode/config-rs/issues/117 - federated_instance: null # rate limits for various user actions, by user ip rate_limit: { # maximum number of messages created in interval @@ -44,6 +39,15 @@ # interval length for registration limit register_per_second: 3600 } + # settings related to activitypub federation + federation: { + # whether to enable activitypub federation. this feature is in alpha, do not enable in production. + enabled: false + # comma seperated list of instances to follow + followed_instances: "" + # whether tls is required for activitypub. only disable this for debugging, never for producion. + tls_enabled: true + } # # email sending configuration # email: { # # hostname of the smtp server diff --git a/server/src/api/community.rs b/server/src/api/community.rs index a07b15d72..dac8733b2 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -121,7 +121,7 @@ impl Perform for Oper { let data: &GetCommunity = &self.data; if data.name.is_some() - && Settings::get().federation_enabled + && Settings::get().federation.enabled && data.name.as_ref().unwrap().contains('@') { return get_remote_community(data.name.as_ref().unwrap()); @@ -344,7 +344,7 @@ impl Perform for Oper { let data: &ListCommunities = &self.data; let local_only = data.local_only.unwrap_or(false); - if Settings::get().federation_enabled && !local_only { + if Settings::get().federation.enabled && !local_only { return Ok(ListCommunitiesResponse { communities: get_all_communities()?, }); diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 1602626be..651d5769e 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -221,7 +221,7 @@ impl Perform for Oper { fn perform(&self, conn: &PgConnection) -> Result { let data: &GetPosts = &self.data; - if Settings::get().federation_enabled { + if Settings::get().federation.enabled { // TODO: intercept here (but the type is wrong) //get_remote_community_posts(get_posts.community_id.unwrap()) } diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index b7202d57f..97eb21565 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -38,5 +38,9 @@ fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url { } pub fn get_apub_protocol_string() -> &'static str { - "http" + if Settings::get().federation.tls_enabled { + "https" + } else { + "http" + } } diff --git a/server/src/apub/puller.rs b/server/src/apub/puller.rs index e7b5d6d7d..e318a68ec 100644 --- a/server/src/apub/puller.rs +++ b/server/src/apub/puller.rs @@ -2,6 +2,7 @@ extern crate reqwest; use crate::api::community::{GetCommunityResponse, ListCommunitiesResponse}; use crate::api::post::GetPostsResponse; +use crate::apub::get_apub_protocol_string; use crate::db::community_view::CommunityView; use crate::db::post_view::PostView; use crate::naive_now; @@ -16,10 +17,15 @@ use log::warn; use serde::Deserialize; fn fetch_node_info(domain: &str) -> Result { - let well_known: NodeInfoWellKnown = - reqwest::get(&format!("http://{}/.well-known/nodeinfo", domain))?.json()?; - Ok(reqwest::get(&well_known.links.href)?.json()?) + let well_known_uri = format!( + "{}://{}/.well-known/nodeinfo", + get_apub_protocol_string(), + domain + ); + let well_known = fetch_remote_object::(&well_known_uri)?; + Ok(fetch_remote_object::(&well_known.links.href)?) } + fn fetch_communities_from_instance(domain: &str) -> Result, Error> { let node_info = fetch_node_info(domain)?; if node_info.software.name != "lemmy" { @@ -54,6 +60,9 @@ fn fetch_remote_object(uri: &str) -> Result where Response: for<'de> Deserialize<'de>, { + if Settings::get().federation.tls_enabled && !uri.starts_with("https") { + return Err(format_err!("Activitypub uri is insecure: {}", uri)); + } // TODO: should cache responses here when we are in production // TODO: this function should return a future // TODO: in production mode, fail if protocol is not https @@ -179,11 +188,12 @@ pub fn get_remote_community(identifier: &str) -> Result Vec { - match Settings::get().federated_instance.clone() { - Some(f) => vec![f, Settings::get().hostname.clone()], - None => vec![Settings::get().hostname.clone()], - } +pub fn get_following_instances() -> Vec<&'static str> { + Settings::get() + .federation + .followed_instances + .split(',') + .collect() } pub fn get_all_communities() -> Result, Error> { diff --git a/server/src/routes/federation.rs b/server/src/routes/federation.rs index fbe8d795a..06a88e2b6 100644 --- a/server/src/routes/federation.rs +++ b/server/src/routes/federation.rs @@ -9,7 +9,7 @@ use diesel::r2d2::{ConnectionManager, Pool}; use diesel::PgConnection; pub fn config(cfg: &mut web::ServiceConfig) { - if Settings::get().federation_enabled { + if Settings::get().federation.enabled { println!("federation enabled, host is {}", Settings::get().hostname); cfg .route( diff --git a/server/src/routes/nodeinfo.rs b/server/src/routes/nodeinfo.rs index 901db9d31..abfae1eda 100644 --- a/server/src/routes/nodeinfo.rs +++ b/server/src/routes/nodeinfo.rs @@ -40,7 +40,7 @@ async fn node_info( Ok(site_view) => site_view, Err(_) => return Err(format_err!("not_found")), }; - let protocols = if Settings::get().federation_enabled { + let protocols = if Settings::get().federation.enabled { vec!["activitypub".to_string()] } else { vec![] diff --git a/server/src/routes/webfinger.rs b/server/src/routes/webfinger.rs index a078b487e..33e3a48e6 100644 --- a/server/src/routes/webfinger.rs +++ b/server/src/routes/webfinger.rs @@ -15,7 +15,7 @@ pub struct Params { } pub fn config(cfg: &mut web::ServiceConfig) { - if Settings::get().federation_enabled { + if Settings::get().federation.enabled { cfg.route( ".well-known/webfinger", web::get().to(get_webfinger_response), diff --git a/server/src/settings.rs b/server/src/settings.rs index 3d32f3f38..3928f74e1 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -17,8 +17,7 @@ pub struct Settings { pub front_end_dir: String, pub rate_limit: RateLimitConfig, pub email: Option, - pub federation_enabled: bool, - pub federated_instance: Option, + pub federation: Federation, } #[derive(Debug, Deserialize)] @@ -50,6 +49,13 @@ pub struct Database { pub pool_size: u32, } +#[derive(Debug, Deserialize)] +pub struct Federation { + pub enabled: bool, + pub followed_instances: String, + pub tls_enabled: bool, +} + lazy_static! { static ref SETTINGS: Settings = { match Settings::init() {