diff --git a/Cargo.lock b/Cargo.lock index 7c709ffa6..038dcd469 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1566,9 +1566,9 @@ dependencies = [ [[package]] name = "http-signature-normalization" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee917294413cec0db93a8af6ecfa63730c1d2bb604bd1da69ba75b342fb23f21" +checksum = "cb3a020c37b48d2258910fae9c9b4f8455651f56abfdde1ae68a9397b2765c31" dependencies = [ "chrono", "thiserror", @@ -1592,6 +1592,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "http-signature-normalization-reqwest" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc26a68f8963e26453c7fdea9e016e2e31a48ca018a9223f96afe2cca1a4bd1" +dependencies = [ + "base64 0.12.3", + "bytes", + "chrono", + "futures", + "http", + "http-signature-normalization", + "reqwest", + "sha2", + "thiserror", + "tokio", +] + [[package]] name = "httparse" version = "1.3.4" @@ -1866,6 +1884,7 @@ dependencies = [ "futures", "http", "http-signature-normalization-actix", + "http-signature-normalization-reqwest", "itertools", "lazy_static", "lemmy_db", @@ -1896,6 +1915,7 @@ dependencies = [ "chrono", "diesel", "lazy_static", + "lemmy_utils", "log", "regex", "serde 1.0.116", diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index 5017c0fa4..2ee3045c7 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -1,4 +1,4 @@ -jest.setTimeout(120000); +jest.setTimeout(180000); import { alpha, beta, @@ -21,6 +21,7 @@ import { registerUser, API, delay, + longDelay, } from './shared'; import { Comment, @@ -35,7 +36,7 @@ beforeAll(async () => { await followBeta(alpha); await followBeta(gamma); let search = await searchForBetaCommunity(alpha); - await delay(10000); + await longDelay(); postRes = await createPost( alpha, search.communities.filter(c => c.local == false)[0].id @@ -66,7 +67,7 @@ test('Create a comment', async () => { expect(commentRes.comment.community_local).toBe(false); expect(commentRes.comment.creator_local).toBe(true); expect(commentRes.comment.score).toBe(1); - await delay(); + await longDelay(); // Make sure that comment is liked on beta let searchBeta = await searchComment(beta, commentRes.comment); @@ -147,7 +148,7 @@ test('Remove a comment from admin and community on the same instance', async () // The beta admin removes it (the community lives on beta) let removeCommentRes = await removeComment(beta, true, betaCommentId); expect(removeCommentRes.comment.removed).toBe(true); - await delay(); + await longDelay(); // Make sure that comment is removed on alpha (it gets pushed since an admin from beta removed it) let refetchedPost = await getPost(alpha, postRes.post.id); @@ -155,7 +156,7 @@ test('Remove a comment from admin and community on the same instance', async () let unremoveCommentRes = await removeComment(beta, false, betaCommentId); expect(unremoveCommentRes.comment.removed).toBe(false); - await delay(); + await longDelay(); // Make sure that comment is unremoved on beta let refetchedPost2 = await getPost(alpha, postRes.post.id); @@ -210,7 +211,7 @@ test('Unlike a comment', async () => { test('Federated comment like', async () => { let commentRes = await createComment(alpha, postRes.post.id); - await delay(); + await longDelay(); // Find the comment on beta let searchBeta = await searchComment(beta, commentRes.comment); @@ -218,7 +219,7 @@ test('Federated comment like', async () => { let like = await likeComment(beta, 1, betaComment); expect(like.comment.score).toBe(2); - await delay(); + await longDelay(); // Get the post from alpha, check the likes let post = await getPost(alpha, postRes.post.id); @@ -241,7 +242,7 @@ test('Reply to a comment', async () => { expect(replyRes.comment.creator_local).toBe(true); expect(replyRes.comment.parent_id).toBe(betaComment.id); expect(replyRes.comment.score).toBe(1); - await delay(); + await longDelay(); // Make sure that comment is seen on alpha // TODO not sure why, but a searchComment back to alpha, for the ap_id of betas @@ -310,7 +311,7 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t expect(commentRes.comment.community_local).toBe(false); expect(commentRes.comment.creator_local).toBe(true); expect(commentRes.comment.score).toBe(1); - await delay(); + await longDelay(); // Make sure alpha sees it let alphaPost2 = await getPost(alpha, alphaPost.post.id); @@ -319,6 +320,7 @@ test('A and G subscribe to B (center) A posts, G mentions B, it gets announced t expect(alphaPost2.comments[0].creator_local).toBe(false); expect(alphaPost2.comments[0].score).toBe(1); assertCommentFederation(alphaPost2.comments[0], commentRes.comment); + await delay(); // Make sure beta has mentions let mentionsRes = await getMentions(beta); @@ -381,7 +383,7 @@ test('Fetch in_reply_tos: A is unsubbed from B, B makes a post, and some embedde // Get the post from alpha let search = await searchPost(alpha, postRes.post); let alphaPostB = search.posts[0]; - await delay(); + await longDelay(); let alphaPost = await getPost(alpha, alphaPostB.id); expect(alphaPost.post.name).toBeDefined(); diff --git a/api_tests/src/follow.spec.ts b/api_tests/src/follow.spec.ts index e238dc636..e0389f871 100644 --- a/api_tests/src/follow.spec.ts +++ b/api_tests/src/follow.spec.ts @@ -7,6 +7,7 @@ import { checkFollowedCommunities, unfollowRemotes, delay, + longDelay, } from './shared'; beforeAll(async () => { @@ -24,10 +25,11 @@ test('Follow federated community', async () => { // Make sure the follow response went through expect(follow.community.local).toBe(false); expect(follow.community.name).toBe('main'); - await delay(); + await longDelay(); // Check it from local let followCheck = await checkFollowedCommunities(alpha); + await delay(); let remoteCommunityId = followCheck.communities.filter( c => c.community_local == false )[0].community_id; diff --git a/api_tests/src/post.spec.ts b/api_tests/src/post.spec.ts index e62b6cce7..b964e72eb 100644 --- a/api_tests/src/post.spec.ts +++ b/api_tests/src/post.spec.ts @@ -20,6 +20,7 @@ import { getPost, unfollowRemotes, delay, + longDelay, } from './shared'; import { Post, @@ -31,7 +32,7 @@ beforeAll(async () => { await followBeta(gamma); await followBeta(delta); await followBeta(epsilon); - await delay(10000); + await longDelay(); }); afterAll(async () => { @@ -67,7 +68,7 @@ test('Create a post', async () => { expect(postRes.post.community_local).toBe(false); expect(postRes.post.creator_local).toBe(true); expect(postRes.post.score).toBe(1); - await delay(); + await longDelay(); // Make sure that post is liked on beta let searchBeta = await searchPost(beta, postRes.post); @@ -104,7 +105,7 @@ test('Unlike a post', async () => { // Try to unlike it again, make sure it stays at 0 let unlike2 = await likePost(alpha, 0, postRes.post); expect(unlike2.post.score).toBe(0); - await delay(); + await longDelay(); // Make sure that post is unliked on beta let searchBeta = await searchPost(beta, postRes.post); @@ -284,31 +285,32 @@ test('Remove a post from admin and community on different instance', async () => test('Remove a post from admin and community on same instance', async () => { let search = await searchForBetaCommunity(alpha); let postRes = await createPost(alpha, search.communities[0].id); - await delay(); + await longDelay(); // Get the id for beta let searchBeta = await searchPost(beta, postRes.post); let betaPost = searchBeta.posts[0]; - await delay(); + await longDelay(); // The beta admin removes it (the community lives on beta) let removePostRes = await removePost(beta, true, betaPost); expect(removePostRes.post.removed).toBe(true); - await delay(); + await longDelay(); // Make sure lemmy alpha sees post is removed let alphaPost = await getPost(alpha, postRes.post.id); expect(alphaPost.post.removed).toBe(true); assertPostFederation(alphaPost.post, removePostRes.post); - await delay(); + await longDelay(); // Undelete let undeletedPost = await removePost(beta, false, betaPost); expect(undeletedPost.post.removed).toBe(false); - await delay(); + await longDelay(); // Make sure lemmy alpha sees post is undeleted let alphaPost2 = await getPost(alpha, postRes.post.id); + await delay(); expect(alphaPost2.post.removed).toBe(false); assertPostFederation(alphaPost2.post, undeletedPost.post); }); diff --git a/api_tests/src/private_message.spec.ts b/api_tests/src/private_message.spec.ts index 75d1568ff..3ae714880 100644 --- a/api_tests/src/private_message.spec.ts +++ b/api_tests/src/private_message.spec.ts @@ -10,6 +10,7 @@ import { deletePrivateMessage, unfollowRemotes, delay, + longDelay, } from './shared'; let recipient_id: number; @@ -17,7 +18,7 @@ let recipient_id: number; beforeAll(async () => { await setupLogins(); let follow = await followBeta(alpha); - await delay(10000); + await longDelay(); recipient_id = follow.community.creator_id; }); @@ -46,7 +47,7 @@ test('Update a private message', async () => { let pmRes = await createPrivateMessage(alpha, recipient_id); let pmUpdated = await updatePrivateMessage(alpha, pmRes.message.id); expect(pmUpdated.message.content).toBe(updatedContent); - await delay(); + await longDelay(); let betaPms = await listPrivateMessages(beta); expect(betaPms.messages[0].content).toBe(updatedContent); @@ -74,7 +75,7 @@ test('Delete a private message', async () => { pmRes.message.id ); expect(undeletedPmRes.message.deleted).toBe(false); - await delay(); + await longDelay(); let betaPms3 = await listPrivateMessages(beta); expect(betaPms3.messages.length).toBe(betaPms1.messages.length); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index bf75fa544..6aa5cfc4e 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -554,10 +554,15 @@ export async function followBeta(api: API): Promise { } } -export const delay = (millis: number = 1500) => - new Promise((resolve, _reject) => { +export function delay(millis: number = 500) { + return new Promise((resolve, _reject) => { setTimeout(_ => resolve(), millis); }); +} + +export function longDelay() { + return delay(10000); +} export function wrapper(form: any): string { return JSON.stringify(form); diff --git a/config/defaults.hjson b/config/defaults.hjson index 1da2a16cf..62fa98a6d 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -31,6 +31,8 @@ bind: "0.0.0.0" # port where lemmy should listen for incoming requests port: 8536 + # whether tls is required for activitypub. only disable this for debugging, never for producion. + tls_enabled: true # json web token for authorization between server and client jwt_secret: "changeme" # address where pictrs is available @@ -58,8 +60,6 @@ federation: { # whether to enable activitypub federation. this feature is in alpha, do not enable in production. enabled: false - # whether tls is required for activitypub. only disable this for debugging, never for producion. - tls_enabled: true # comma separated list of instances with which federation is allowed allowed_instances: "" # comma separated list of instances which are blocked from federating diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index b4181e5d2..8f01eadf7 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -43,7 +43,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8541 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha @@ -82,7 +82,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8551 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta @@ -121,7 +121,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8561 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma @@ -161,7 +161,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta - LEMMY_PORT=8571 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta @@ -201,7 +201,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha - LEMMY_PORT=8581 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon diff --git a/docker/travis/docker-compose.yml b/docker/travis/docker-compose.yml index e60f50333..565f7a000 100644 --- a/docker/travis/docker-compose.yml +++ b/docker/travis/docker-compose.yml @@ -8,7 +8,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8541 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha @@ -39,7 +39,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8551 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta @@ -70,7 +70,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon - LEMMY_PORT=8561 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma @@ -102,7 +102,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta - LEMMY_PORT=8571 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta @@ -134,7 +134,7 @@ services: - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy - LEMMY_JWT_SECRET=changeme - LEMMY_FEDERATION__ENABLED=true - - LEMMY_FEDERATION__TLS_ENABLED=false + - LEMMY_TLS_ENABLED=false - LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha - LEMMY_PORT=8581 - LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon diff --git a/lemmy_api/src/user.rs b/lemmy_api/src/user.rs index 60704e3c1..4324c9368 100644 --- a/lemmy_api/src/user.rs +++ b/lemmy_api/src/user.rs @@ -906,7 +906,7 @@ impl Perform for PasswordReset { // TODO no i18n support here. let user_email = &user.email.expect("email"); let subject = &format!("Password reset for {}", user.name); - let hostname = &format!("https://{}", Settings::get().hostname); //TODO add https for now. + let hostname = &Settings::get().get_protocol_and_hostname(); let html = &format!("

Password Reset Request for {}


Click here to reset your password", user.name, hostname, &token); match send_email(subject, user_email, &user.name, html) { Ok(_o) => _o, @@ -970,8 +970,6 @@ impl Perform for CreatePrivateMessage { let data: &CreatePrivateMessage = &self; let user = get_user_from_jwt(&data.auth, context.pool()).await?; - let hostname = &format!("https://{}", Settings::get().hostname); - let content_slurs_removed = remove_slurs(&data.content.to_owned()); let private_message_form = PrivateMessageForm { @@ -1027,7 +1025,9 @@ impl Perform for CreatePrivateMessage { ); let html = &format!( "

Private Message


{} - {}

inbox", - user.name, &content_slurs_removed, hostname + user.name, + &content_slurs_removed, + Settings::get().get_protocol_and_hostname() ); match send_email(subject, &email, &recipient_user.name, html) { Ok(_o) => _o, diff --git a/lemmy_apub/Cargo.toml b/lemmy_apub/Cargo.toml index 66dbbe4f1..eaad7be4d 100644 --- a/lemmy_apub/Cargo.toml +++ b/lemmy_apub/Cargo.toml @@ -34,6 +34,7 @@ percent-encoding = "2.1" openssl = "0.10" http = "0.2" http-signature-normalization-actix = { version = "0.4", default-features = false, features = ["sha-2"] } +http-signature-normalization-reqwest = { version = "0.1.3", default-features = false, features = ["sha-2"] } base64 = "0.12" tokio = "0.2" futures = "0.3" @@ -44,4 +45,4 @@ async-trait = "0.1" anyhow = "1.0" thiserror = "1.0" background-jobs = " 0.8" -reqwest = { version = "0.10", features = ["json"] } \ No newline at end of file +reqwest = { version = "0.10", features = ["json"] } diff --git a/lemmy_apub/src/activities.rs b/lemmy_apub/src/activities.rs index 3b1b12ab3..18781ef42 100644 --- a/lemmy_apub/src/activities.rs +++ b/lemmy_apub/src/activities.rs @@ -4,7 +4,7 @@ use activitystreams::{ object::AsObject, }; use lemmy_db::{community::Community, user::User_}; -use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError}; +use lemmy_utils::{settings::Settings, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{export::fmt::Debug, Serialize}; use url::{ParseError, Url}; @@ -40,9 +40,8 @@ where T: ToString, { let id = format!( - "{}://{}/activities/{}/{}", - get_apub_protocol_string(), - Settings::get().hostname, + "{}/activities/{}/{}", + Settings::get().get_protocol_and_hostname(), kind.to_string().to_lowercase(), Uuid::new_v4() ); diff --git a/lemmy_apub/src/activity_queue.rs b/lemmy_apub/src/activity_queue.rs index 80c92c2f9..ca0263320 100644 --- a/lemmy_apub/src/activity_queue.rs +++ b/lemmy_apub/src/activity_queue.rs @@ -1,10 +1,9 @@ -use crate::{check_is_apub_id_valid, extensions::signatures::sign, ActorType}; +use crate::{check_is_apub_id_valid, extensions::signatures::sign_and_send, ActorType}; use activitystreams::{ base::{Extends, ExtendsExt}, object::AsObject, }; use anyhow::{anyhow, Context, Error}; -use awc::Client; use background_jobs::{ create_server, memory_storage::Storage, @@ -16,8 +15,9 @@ use background_jobs::{ }; use lemmy_utils::{location_info, settings::Settings, LemmyError}; use log::warn; +use reqwest::Client; use serde::{Deserialize, Serialize}; -use std::{future::Future, pin::Pin}; +use std::{collections::BTreeMap, future::Future, pin::Pin}; use url::Url; pub fn send_activity( @@ -50,6 +50,7 @@ where actor_id: actor.actor_id()?, private_key: actor.private_key().context(location_info!())?, }; + activity_sender.queue::(message)?; Ok(()) @@ -74,27 +75,19 @@ impl ActixJob for SendActivityTask { fn run(self, state: Self::State) -> Self::Future { Box::pin(async move { for to_url in &self.to { - let request = state - .client - .post(to_url.as_str()) - .header("Content-Type", "application/json"); - - let signed = sign( - request, + let mut headers = BTreeMap::::new(); + headers.insert("Content-Type".into(), "application/json".into()); + let result = sign_and_send( + &state.client, + headers, + to_url, self.activity.clone(), &self.actor_id, self.private_key.to_owned(), ) .await; - let signed = match signed { - Ok(s) => s, - Err(e) => { - warn!("{}", e); - // dont return an error because retrying would probably not fix the signing - return Ok(()); - } - }; - if let Err(e) = signed.send().await { + + if let Err(e) = result { warn!("{}", e); return Err(anyhow!( "Failed to send activity {} to {}", @@ -103,7 +96,6 @@ impl ActixJob for SendActivityTask { )); } } - Ok(()) }) } diff --git a/lemmy_apub/src/community.rs b/lemmy_apub/src/community.rs index 8a41e8866..715b765b3 100644 --- a/lemmy_apub/src/community.rs +++ b/lemmy_apub/src/community.rs @@ -44,8 +44,8 @@ use lemmy_db::{ }; use lemmy_structs::blocking; use lemmy_utils::{ - apub::get_apub_protocol_string, location_info, + settings::Settings, utils::{check_slurs, check_slurs_opt, convert_datetime}, LemmyError, }; @@ -299,7 +299,7 @@ impl ActorType for Community { }; Ok(Url::parse(&format!( "{}://{}{}/inbox", - get_apub_protocol_string(), + Settings::get().get_protocol_string(), domain, port, ))?) diff --git a/lemmy_apub/src/extensions/signatures.rs b/lemmy_apub/src/extensions/signatures.rs index 5471e19e4..cdcb707a6 100644 --- a/lemmy_apub/src/extensions/signatures.rs +++ b/lemmy_apub/src/extensions/signatures.rs @@ -1,12 +1,11 @@ use crate::ActorType; use activitystreams::unparsed::UnparsedMutExt; use activitystreams_ext::UnparsedExtension; -use actix_web::{client::ClientRequest, HttpRequest}; +use actix_web::HttpRequest; use anyhow::{anyhow, Context}; -use http_signature_normalization_actix::{ - digest::{DigestClient, SignExt}, - Config, -}; +use http::{header::HeaderName, HeaderMap, HeaderValue}; +use http_signature_normalization_actix::Config as ConfigActix; +use http_signature_normalization_reqwest::prelude::{Config, SignExt}; use lemmy_utils::{location_info, LemmyError}; use log::debug; use openssl::{ @@ -14,24 +13,38 @@ use openssl::{ pkey::PKey, sign::{Signer, Verifier}, }; +use reqwest::{Client, Response}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; +use std::{collections::BTreeMap, str::FromStr}; use url::Url; lazy_static! { + static ref CONFIG2: ConfigActix = ConfigActix::new(); static ref HTTP_SIG_CONFIG: Config = Config::new(); } /// Signs request headers with the given keypair. -pub async fn sign( - request: ClientRequest, +pub async fn sign_and_send( + client: &Client, + headers: BTreeMap, + url: &Url, activity: String, actor_id: &Url, private_key: String, -) -> Result, LemmyError> { +) -> Result { let signing_key_id = format!("{}#main-key", actor_id); - let digest_client = request + let mut header_map = HeaderMap::new(); + for h in headers { + header_map.insert( + HeaderName::from_str(h.0.as_str())?, + HeaderValue::from_str(h.1.as_str())?, + ); + } + let response = client + .post(&url.to_string()) + .headers(header_map) .signature_with_digest( HTTP_SIG_CONFIG.clone(), signing_key_id, @@ -47,12 +60,12 @@ pub async fn sign( ) .await?; - Ok(digest_client) + Ok(response) } pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), LemmyError> { let public_key = actor.public_key().context(location_info!())?; - let verified = HTTP_SIG_CONFIG + let verified = CONFIG2 .begin_verify( request.method(), request.uri().path_and_query(), diff --git a/lemmy_apub/src/fetcher.rs b/lemmy_apub/src/fetcher.rs index 4ce4082cd..3f2109ecb 100644 --- a/lemmy_apub/src/fetcher.rs +++ b/lemmy_apub/src/fetcher.rs @@ -27,9 +27,9 @@ use lemmy_db::{ }; use lemmy_structs::{blocking, site::SearchResponse}; use lemmy_utils::{ - apub::get_apub_protocol_string, location_info, request::{retry, RecvError}, + settings::Settings, LemmyError, }; use lemmy_websocket::LemmyContext; @@ -117,7 +117,12 @@ pub async fn search_by_apub_id( return Err(anyhow!("Invalid search query: {}", query).into()); }; - let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name); + let url = format!( + "{}://{}{}", + Settings::get().get_protocol_string(), + instance, + name + ); Url::parse(&url)? } else { Url::parse(&query)? diff --git a/lemmy_apub/src/lib.rs b/lemmy_apub/src/lib.rs index 22eb9fbe0..3f37c5d3c 100644 --- a/lemmy_apub/src/lib.rs +++ b/lemmy_apub/src/lib.rs @@ -32,7 +32,6 @@ use chrono::NaiveDateTime; use lemmy_db::{activity::do_insert_activity, user::User_, DbPool}; use lemmy_structs::{blocking, WebFingerResponse}; use lemmy_utils::{ - apub::get_apub_protocol_string, location_info, request::{retry, RecvError}, settings::Settings, @@ -97,7 +96,7 @@ fn check_is_apub_id_valid(apub_id: &Url) -> Result<(), LemmyError> { }; } - if apub_id.scheme() != get_apub_protocol_string() { + if apub_id.scheme() != Settings::get().get_protocol_string() { return Err(anyhow!("invalid apub id scheme: {:?}", apub_id.scheme()).into()); } @@ -319,7 +318,7 @@ pub async fn fetch_webfinger_url( ) -> Result { let fetch_url = format!( "{}://{}/.well-known/webfinger?resource=acct:{}@{}", - get_apub_protocol_string(), + Settings::get().get_protocol_string(), mention.domain, mention.name, mention.domain diff --git a/lemmy_db/Cargo.toml b/lemmy_db/Cargo.toml index 814c169fa..904b16937 100644 --- a/lemmy_db/Cargo.toml +++ b/lemmy_db/Cargo.toml @@ -8,6 +8,7 @@ name = "lemmy_db" path = "src/lib.rs" [dependencies] +lemmy_utils = { path = "../lemmy_utils" } diesel = { version = "1.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] } chrono = { version = "0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } diff --git a/lemmy_db/src/user.rs b/lemmy_db/src/user.rs index 60fdebbba..83f0559ab 100644 --- a/lemmy_db/src/user.rs +++ b/lemmy_db/src/user.rs @@ -6,6 +6,7 @@ use crate::{ }; use bcrypt::{hash, DEFAULT_COST}; use diesel::{dsl::*, result::Error, *}; +use lemmy_utils::settings::Settings; use serde::Serialize; #[derive(Clone, Queryable, Identifiable, PartialEq, Debug, Serialize)] @@ -151,7 +152,12 @@ impl User_ { } pub fn get_profile_url(&self, hostname: &str) -> String { - format!("https://{}/u/{}", hostname, self.name) + format!( + "{}://{}/u/{}", + Settings::get().get_protocol_string(), + hostname, + self.name + ) } pub fn upsert(conn: &PgConnection, user_form: &UserForm) -> Result { diff --git a/lemmy_structs/src/lib.rs b/lemmy_structs/src/lib.rs index 3efe0bead..9b67866a9 100644 --- a/lemmy_structs/src/lib.rs +++ b/lemmy_structs/src/lib.rs @@ -77,7 +77,7 @@ fn do_send_local_notifs( do_send_email: bool, ) -> Vec { let mut recipient_ids = Vec::new(); - let hostname = &format!("https://{}", Settings::get().hostname); + let hostname = &Settings::get().get_protocol_and_hostname(); // Send the local mentions for mention in mentions diff --git a/lemmy_utils/src/apub.rs b/lemmy_utils/src/apub.rs index 08e7a4491..4f6ec22f0 100644 --- a/lemmy_utils/src/apub.rs +++ b/lemmy_utils/src/apub.rs @@ -35,14 +35,6 @@ pub enum EndpointType { PrivateMessage, } -pub fn get_apub_protocol_string() -> &'static str { - if Settings::get().federation.tls_enabled { - "https" - } else { - "http" - } -} - /// Generates the ActivityPub ID for a given object type and ID. pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url { let point = match endpoint_type { @@ -54,9 +46,8 @@ pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url { }; Url::parse(&format!( - "{}://{}/{}/{}", - get_apub_protocol_string(), - Settings::get().hostname, + "{}/{}/{}", + Settings::get().get_protocol_and_hostname(), point, name )) diff --git a/lemmy_utils/src/request.rs b/lemmy_utils/src/request.rs index 4aa70c6fd..cbd778c6e 100644 --- a/lemmy_utils/src/request.rs +++ b/lemmy_utils/src/request.rs @@ -1,4 +1,4 @@ -use crate::{apub::get_apub_protocol_string, settings::Settings, LemmyError}; +use crate::{settings::Settings, LemmyError}; use anyhow::anyhow; use log::error; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; @@ -149,9 +149,8 @@ pub async fn fetch_iframely_and_pictrs_data( // The full urls are necessary for federation let pictrs_thumbnail = if let Some(pictrs_hash) = pictrs_hash { Some(format!( - "{}://{}/pictrs/image/{}", - get_apub_protocol_string(), - Settings::get().hostname, + "{}/pictrs/image/{}", + Settings::get().get_protocol_and_hostname(), pictrs_hash )) } else { diff --git a/lemmy_utils/src/settings.rs b/lemmy_utils/src/settings.rs index 82bdceb21..d3775c4b4 100644 --- a/lemmy_utils/src/settings.rs +++ b/lemmy_utils/src/settings.rs @@ -12,6 +12,7 @@ pub struct Settings { pub hostname: String, pub bind: IpAddr, pub port: u16, + pub tls_enabled: bool, pub jwt_secret: String, pub pictrs_url: String, pub rate_limit: RateLimitConfig, @@ -68,7 +69,6 @@ pub struct DatabaseConfig { #[derive(Debug, Deserialize, Clone)] pub struct FederationConfig { pub enabled: bool, - pub tls_enabled: bool, pub allowed_instances: String, pub blocked_instances: String, } @@ -157,6 +157,21 @@ impl Settings { blocked_instances } + /// Returns either "http" or "https", depending on tls_enabled setting + pub fn get_protocol_string(&self) -> &'static str { + if self.tls_enabled { + "https" + } else { + "http" + } + } + + /// Returns something like `http://localhost` or `https://dev.lemmy.ml`, + /// with the correct protocol and hostname. + pub fn get_protocol_and_hostname(&self) -> String { + format!("{}://{}", self.get_protocol_string(), self.hostname) + } + pub fn save_config_file(data: &str) -> Result { fs::write(CONFIG_FILE, data)?; diff --git a/src/code_migrations.rs b/src/code_migrations.rs index bd3c74798..adc4ae49a 100644 --- a/src/code_migrations.rs +++ b/src/code_migrations.rs @@ -13,7 +13,7 @@ use lemmy_db::{ Crud, }; use lemmy_utils::{ - apub::{generate_actor_keypair, get_apub_protocol_string, make_apub_endpoint, EndpointType}, + apub::{generate_actor_keypair, make_apub_endpoint, EndpointType}, settings::Settings, LemmyError, }; @@ -206,9 +206,8 @@ fn post_thumbnail_url_updates_2020_07_27(conn: &PgConnection) -> Result<(), Lemm info!("Running post_thumbnail_url_updates_2020_07_27"); let domain_prefix = format!( - "{}://{}/pictrs/image/", - get_apub_protocol_string(), - Settings::get().hostname + "{}/pictrs/image/", + Settings::get().get_protocol_and_hostname(), ); let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%")); diff --git a/src/routes/feeds.rs b/src/routes/feeds.rs index 2c36ac233..f9111169a 100644 --- a/src/routes/feeds.rs +++ b/src/routes/feeds.rs @@ -71,7 +71,7 @@ fn get_feed_all_data(conn: &PgConnection, sort_type: &SortType) -> Result Result Result { let mut i = ItemBuilder::default(); i.title(format!("Reply from {}", creator_name)); - let author_url = format!("https://{}/u/{}", Settings::get().hostname, creator_name); + let author_url = format!( + "{}/u/{}", + Settings::get().get_protocol_and_hostname(), + creator_name + ); i.author(format!( "/u/{} (link)", creator_name, author_url @@ -306,7 +313,11 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { i.title(p.name); - let author_url = format!("https://{}/u/{}", Settings::get().hostname, p.creator_name); + let author_url = format!( + "{}/u/{}", + Settings::get().get_protocol_and_hostname(), + p.creator_name + ); i.author(format!( "/u/{} (link)", p.creator_name, author_url @@ -315,7 +326,11 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { let dt = DateTime::::from_utc(p.published, Utc); i.pub_date(dt.to_rfc2822()); - let post_url = format!("https://{}/post/{}", Settings::get().hostname, p.id); + let post_url = format!( + "{}/post/{}", + Settings::get().get_protocol_and_hostname(), + p.id + ); i.comments(post_url.to_owned()); let guid = GuidBuilder::default() .permalink(true) @@ -325,8 +340,8 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { i.guid(guid); let community_url = format!( - "https://{}/c/{}", - Settings::get().hostname, + "{}/c/{}", + Settings::get().get_protocol_and_hostname(), p.community_name ); diff --git a/src/routes/nodeinfo.rs b/src/routes/nodeinfo.rs index 984151c42..c41be0e94 100644 --- a/src/routes/nodeinfo.rs +++ b/src/routes/nodeinfo.rs @@ -3,7 +3,7 @@ use anyhow::anyhow; use lemmy_api::version; use lemmy_db::site_view::SiteView; use lemmy_structs::blocking; -use lemmy_utils::{apub::get_apub_protocol_string, settings::Settings, LemmyError}; +use lemmy_utils::{settings::Settings, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use url::Url; @@ -19,9 +19,8 @@ async fn node_info_well_known() -> Result, LemmyError> { links: NodeInfoWellKnownLinks { rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, href: Url::parse(&format!( - "{}://{}/nodeinfo/2.0.json", - get_apub_protocol_string(), - Settings::get().hostname + "{}/nodeinfo/2.0.json", + Settings::get().get_protocol_and_hostname() ))?, }, };