diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index dd4e6b9b2..bfaceeb02 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -198,12 +198,13 @@ mod tests { person::{Person, PersonForm}, secret::Secret, }; - use lemmy_utils::claims::Claims; + use lemmy_utils::{claims::Claims, settings::structs::Settings}; #[test] fn test_should_not_validate_user_token_after_password_change() { let conn = establish_unpooled_connection(); let secret = Secret::init(&conn).unwrap(); + let settings = Settings::init().unwrap(); let new_person = PersonForm { name: "Gerry9812".into(), @@ -220,7 +221,12 @@ mod tests { let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap(); - let jwt = Claims::jwt(inserted_local_user.id.0, &secret.jwt_secret).unwrap(); + let jwt = Claims::jwt( + inserted_local_user.id.0, + &secret.jwt_secret, + &settings.hostname, + ) + .unwrap(); let claims = Claims::decode(&jwt, &secret.jwt_secret).unwrap().claims; let check = check_validator_time(&inserted_local_user.validator_time, &claims); assert!(check.is_ok()); diff --git a/crates/api/src/local_user.rs b/crates/api/src/local_user.rs index f216df612..fe1c8b117 100644 --- a/crates/api/src/local_user.rs +++ b/crates/api/src/local_user.rs @@ -61,7 +61,6 @@ use lemmy_utils::{ claims::Claims, email::send_email, location_info, - settings::structs::Settings, utils::{generate_random_string, is_valid_display_name, is_valid_matrix_id, naive_from_unix}, ApiError, ConnectionId, @@ -107,6 +106,7 @@ impl Perform for Login { jwt: Claims::jwt( local_user_view.local_user.id.0, &context.secret().jwt_secret, + &context.settings().hostname, )?, }) } @@ -121,7 +121,7 @@ impl Perform for GetCaptcha { context: &Data, _websocket_id: Option, ) -> Result { - let captcha_settings = Settings::get().captcha; + let captcha_settings = context.settings().to_owned().captcha; if !captcha_settings.enabled { return Ok(GetCaptchaResponse { ok: None }); @@ -185,7 +185,10 @@ impl Perform for SaveUserSettings { } if let Some(Some(display_name)) = &display_name { - if !is_valid_display_name(display_name.trim()) { + if !is_valid_display_name( + display_name.trim(), + context.settings().actor_name_max_length, + ) { return Err(ApiError::err("invalid_username").into()); } } @@ -273,7 +276,11 @@ impl Perform for SaveUserSettings { // Return the jwt Ok(LoginResponse { - jwt: Claims::jwt(updated_local_user.id.0, &context.secret().jwt_secret)?, + jwt: Claims::jwt( + updated_local_user.id.0, + &context.secret().jwt_secret, + &context.settings().hostname, + )?, }) } } @@ -317,7 +324,11 @@ impl Perform for ChangePassword { // Return the jwt Ok(LoginResponse { - jwt: Claims::jwt(updated_local_user.id.0, &context.secret().jwt_secret)?, + jwt: Claims::jwt( + updated_local_user.id.0, + &context.secret().jwt_secret, + &context.settings().hostname, + )?, }) } } @@ -739,10 +750,16 @@ impl Perform for PasswordReset { // TODO no i18n support here. let email = &local_user_view.local_user.email.expect("email"); let subject = &format!("Password reset for {}", local_user_view.person.name); - let hostname = &Settings::get().get_protocol_and_hostname(); - let html = &format!("

Password Reset Request for {}


Click here to reset your password", local_user_view.person.name, hostname, &token); - send_email(subject, email, &local_user_view.person.name, html) - .map_err(|e| ApiError::err(&e))?; + let protocol_and_hostname = &context.settings().get_protocol_and_hostname(); + let html = &format!("

Password Reset Request for {}


Click here to reset your password", local_user_view.person.name, protocol_and_hostname, &token); + send_email( + subject, + email, + &local_user_view.person.name, + html, + context.settings(), + ) + .map_err(|e| ApiError::err(&e))?; Ok(PasswordResetResponse {}) } @@ -783,7 +800,11 @@ impl Perform for PasswordChange { // Return the jwt Ok(LoginResponse { - jwt: Claims::jwt(updated_local_user.id.0, &context.secret().jwt_secret)?, + jwt: Claims::jwt( + updated_local_user.id.0, + &context.secret().jwt_secret, + &context.settings().hostname, + )?, }) } } diff --git a/crates/api/src/site.rs b/crates/api/src/site.rs index 765fe0beb..1c601b264 100644 --- a/crates/api/src/site.rs +++ b/crates/api/src/site.rs @@ -181,7 +181,7 @@ impl Perform for Search { let community_actor_id = data .community_name .as_ref() - .map(|t| build_actor_id_from_shortname(EndpointType::Community, t).ok()) + .map(|t| build_actor_id_from_shortname(EndpointType::Community, t, context.settings()).ok()) .unwrap_or(None); let creator_id = data.creator_id; match search_type { @@ -483,7 +483,12 @@ impl Perform for TransferSite { admins.insert(0, creator_person); let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??; - let federated_instances = build_federated_instances(context.pool()).await?; + let federated_instances = build_federated_instances( + context.pool(), + &context.settings().federation, + &context.settings().hostname, + ) + .await?; Ok(GetSiteResponse { site_view: Some(site_view), diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 0eeb9cb33..c38d4e620 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -41,7 +41,7 @@ use lemmy_db_views_actor::{ use lemmy_utils::{ claims::Claims, email::send_email, - settings::structs::Settings, + settings::structs::{FederationConfig, Settings}, utils::MentionData, ApiError, LemmyError, @@ -72,9 +72,19 @@ pub async fn send_local_notifs( post: Post, pool: &DbPool, do_send_email: bool, + settings: &Settings, ) -> Result, LemmyError> { + let settings = settings.to_owned(); let ids = blocking(pool, move |conn| { - do_send_local_notifs(conn, &mentions, &comment, &person, &post, do_send_email) + do_send_local_notifs( + conn, + &mentions, + &comment, + &person, + &post, + do_send_email, + &settings, + ) }) .await?; @@ -88,13 +98,14 @@ fn do_send_local_notifs( person: &Person, post: &Post, do_send_email: bool, + settings: &Settings, ) -> Vec { let mut recipient_ids = Vec::new(); // Send the local mentions for mention in mentions .iter() - .filter(|m| m.is_local() && m.name.ne(&person.name)) + .filter(|m| m.is_local(&settings.hostname) && m.name.ne(&person.name)) .collect::>() { if let Ok(mention_user_view) = LocalUserView::read_from_name(conn, &mention.name) { @@ -120,6 +131,7 @@ fn do_send_local_notifs( "Mentioned by", "Person Mention", &comment.content, + settings, ) } } @@ -142,6 +154,7 @@ fn do_send_local_notifs( "Reply from", "Comment Reply", &comment.content, + settings, ) } } @@ -160,6 +173,7 @@ fn do_send_local_notifs( "Reply from", "Post Reply", &comment.content, + settings, ) } } @@ -174,6 +188,7 @@ pub fn send_email_to_user( subject_text: &str, body_text: &str, comment_content: &str, + settings: &Settings, ) { if local_user_view.person.banned || !local_user_view.local_user.send_notifications_to_email { return; @@ -182,18 +197,22 @@ pub fn send_email_to_user( if let Some(user_email) = &local_user_view.local_user.email { let subject = &format!( "{} - {} {}", - subject_text, - Settings::get().hostname, - local_user_view.person.name, + subject_text, settings.hostname, local_user_view.person.name, ); let html = &format!( "

{}


{} - {}

inbox", body_text, local_user_view.person.name, comment_content, - Settings::get().get_protocol_and_hostname() + settings.get_protocol_and_hostname() ); - match send_email(subject, user_email, &local_user_view.person.name, html) { + match send_email( + subject, + user_email, + &local_user_view.person.name, + html, + settings, + ) { Ok(_o) => _o, Err(e) => error!("{}", e), }; @@ -392,15 +411,18 @@ pub async fn collect_moderated_communities( pub async fn build_federated_instances( pool: &DbPool, + federation_config: &FederationConfig, + hostname: &str, ) -> Result, LemmyError> { - if Settings::get().federation.enabled { + let federation = federation_config.to_owned(); + if federation.enabled { let distinct_communities = blocking(pool, move |conn| { Community::distinct_federated_communities(conn) }) .await??; - let allowed = Settings::get().federation.allowed_instances; - let blocked = Settings::get().federation.blocked_instances; + let allowed = federation.allowed_instances; + let blocked = federation.blocked_instances; let mut linked = distinct_communities .iter() @@ -412,7 +434,7 @@ pub async fn build_federated_instances( } if let Some(blocked) = blocked.as_ref() { - linked.retain(|a| !blocked.contains(a) && !a.eq(&Settings::get().hostname)); + linked.retain(|a| !blocked.contains(a) && !a.eq(hostname)); } // Sort and remove dupes diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index e9d00d73c..bb7728e9c 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -43,7 +43,8 @@ impl PerformCrud for CreateComment { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - let content_slurs_removed = remove_slurs(&data.content.to_owned()); + let content_slurs_removed = + remove_slurs(&data.content.to_owned(), &context.settings().slur_regex()); // Check for a community ban let post_id = data.post_id; @@ -92,10 +93,15 @@ impl PerformCrud for CreateComment { // Necessary to update the ap_id let inserted_comment_id = inserted_comment.id; + let protocol_and_hostname = context.settings().get_protocol_and_hostname(); + let updated_comment: Comment = blocking(context.pool(), move |conn| -> Result { - let apub_id = - generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?; + let apub_id = generate_apub_endpoint( + EndpointType::Comment, + &inserted_comment_id.to_string(), + &protocol_and_hostname, + )?; Ok(Comment::update_ap_id(conn, inserted_comment_id, apub_id)?) }) .await? @@ -119,6 +125,7 @@ impl PerformCrud for CreateComment { post, context.pool(), true, + context.settings(), ) .await?; diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 1c3afc016..c84ec621e 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -77,6 +77,7 @@ impl PerformCrud for DeleteComment { post, context.pool(), false, + context.settings(), ) .await?; @@ -171,6 +172,7 @@ impl PerformCrud for RemoveComment { post, context.pool(), false, + context.settings(), ) .await?; diff --git a/crates/api_crud/src/comment/read.rs b/crates/api_crud/src/comment/read.rs index 6362f7f76..f63104bc5 100644 --- a/crates/api_crud/src/comment/read.rs +++ b/crates/api_crud/src/comment/read.rs @@ -32,7 +32,7 @@ impl PerformCrud for GetComments { let community_actor_id = data .community_name .as_ref() - .map(|t| build_actor_id_from_shortname(EndpointType::Community, t).ok()) + .map(|t| build_actor_id_from_shortname(EndpointType::Community, t, context.settings()).ok()) .unwrap_or(None); let saved_only = data.saved_only; let page = data.page; diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 033676738..ab9b9faaf 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -55,7 +55,8 @@ impl PerformCrud for EditComment { } // Do the update - let content_slurs_removed = remove_slurs(&data.content.to_owned()); + let content_slurs_removed = + remove_slurs(&data.content.to_owned(), &context.settings().slur_regex()); let comment_id = data.comment_id; let updated_comment = blocking(context.pool(), move |conn| { Comment::update_content(conn, comment_id, &content_slurs_removed) @@ -82,6 +83,7 @@ impl PerformCrud for EditComment { orig_comment.post, context.pool(), false, + context.settings(), ) .await?; diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index 7ef98f4bf..1cc40d85f 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -53,16 +53,20 @@ impl PerformCrud for CreateCommunity { return Err(ApiError::err("only_admins_can_create_communities").into()); } - check_slurs(&data.name)?; - check_slurs(&data.title)?; - check_slurs_opt(&data.description)?; + check_slurs(&data.name, &context.settings().slur_regex())?; + check_slurs(&data.title, &context.settings().slur_regex())?; + check_slurs_opt(&data.description, &context.settings().slur_regex())?; - if !is_valid_actor_name(&data.name) { + if !is_valid_actor_name(&data.name, context.settings().actor_name_max_length) { return Err(ApiError::err("invalid_community_name").into()); } // Double check for duplicate community actor_ids - let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?; + let community_actor_id = generate_apub_endpoint( + EndpointType::Community, + &data.name, + &context.settings().get_protocol_and_hostname(), + )?; let actor_id_cloned = community_actor_id.to_owned(); let community_dupe = blocking(context.pool(), move |conn| { Community::read_from_apub_id(conn, &actor_id_cloned) diff --git a/crates/api_crud/src/community/read.rs b/crates/api_crud/src/community/read.rs index 0f1af1c7f..3bbac9241 100644 --- a/crates/api_crud/src/community/read.rs +++ b/crates/api_crud/src/community/read.rs @@ -35,7 +35,8 @@ impl PerformCrud for GetCommunity { Some(id) => id, None => { let name = data.name.to_owned().unwrap_or_else(|| "main".to_string()); - let community_actor_id = build_actor_id_from_shortname(EndpointType::Community, &name)?; + let community_actor_id = + build_actor_id_from_shortname(EndpointType::Community, &name, context.settings())?; blocking(context.pool(), move |conn| { Community::read_from_apub_id(conn, &community_actor_id) diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index 423058d73..ef94ad595 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -29,8 +29,8 @@ impl PerformCrud for EditCommunity { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - check_slurs_opt(&data.title)?; - check_slurs_opt(&data.description)?; + check_slurs_opt(&data.title, &context.settings().slur_regex())?; + check_slurs_opt(&data.description, &context.settings().slur_regex())?; // Verify its a mod (only mods can edit it) let community_id = data.community_id; diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index e0aa26fe3..524c305d3 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -41,8 +41,9 @@ impl PerformCrud for CreatePost { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - check_slurs(&data.name)?; - check_slurs_opt(&data.body)?; + let slur_regex = &context.settings().slur_regex(); + check_slurs(&data.name, slur_regex)?; + check_slurs_opt(&data.body, slur_regex)?; if !is_valid_post_title(&data.name) { return Err(ApiError::err("invalid_post_title").into()); @@ -52,7 +53,8 @@ impl PerformCrud for CreatePost { // Fetch post links and pictrs cached image let data_url = data.url.as_ref(); - let (metadata_res, pictrs_thumbnail) = fetch_site_data(context.client(), data_url).await; + let (metadata_res, pictrs_thumbnail) = + fetch_site_data(context.client(), context.settings(), data_url).await; let (embed_title, embed_description, embed_html) = metadata_res .map(|u| (u.title, u.description, u.html)) .unwrap_or((None, None, None)); @@ -86,8 +88,13 @@ impl PerformCrud for CreatePost { }; let inserted_post_id = inserted_post.id; + let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let updated_post = blocking(context.pool(), move |conn| -> Result { - let apub_id = generate_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string())?; + let apub_id = generate_apub_endpoint( + EndpointType::Post, + &inserted_post_id.to_string(), + &protocol_and_hostname, + )?; Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?) }) .await? diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index 84fbb0c83..413f641c1 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -135,7 +135,7 @@ impl PerformCrud for GetPosts { let community_actor_id = data .community_name .as_ref() - .map(|t| build_actor_id_from_shortname(EndpointType::Community, t).ok()) + .map(|t| build_actor_id_from_shortname(EndpointType::Community, t, context.settings()).ok()) .unwrap_or(None); let saved_only = data.saved_only; diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 071039e19..c1215830d 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -26,8 +26,9 @@ impl PerformCrud for EditPost { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - check_slurs_opt(&data.name)?; - check_slurs_opt(&data.body)?; + let slur_regex = &context.settings().slur_regex(); + check_slurs_opt(&data.name, slur_regex)?; + check_slurs_opt(&data.body, slur_regex)?; if let Some(name) = &data.name { if !is_valid_post_title(name) { @@ -52,7 +53,8 @@ impl PerformCrud for EditPost { // Fetch post links and Pictrs cached image let data_url = data.url.as_ref(); - let (metadata_res, pictrs_thumbnail) = fetch_site_data(context.client(), data_url).await; + let (metadata_res, pictrs_thumbnail) = + fetch_site_data(context.client(), context.settings(), data_url).await; let (embed_title, embed_description, embed_html) = metadata_res .map(|u| (u.title, u.description, u.html)) .unwrap_or((None, None, None)); diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index c41769f32..a0b12fb32 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -34,7 +34,8 @@ impl PerformCrud for CreatePrivateMessage { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - let content_slurs_removed = remove_slurs(&data.content.to_owned()); + let content_slurs_removed = + remove_slurs(&data.content.to_owned(), &context.settings().slur_regex()); check_person_block(local_user_view.person.id, data.recipient_id, context.pool()).await?; @@ -57,12 +58,14 @@ impl PerformCrud for CreatePrivateMessage { }; let inserted_private_message_id = inserted_private_message.id; + let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let updated_private_message = blocking( context.pool(), move |conn| -> Result { let apub_id = generate_apub_endpoint( EndpointType::PrivateMessage, &inserted_private_message_id.to_string(), + &protocol_and_hostname, )?; Ok(PrivateMessage::update_ap_id( conn, @@ -102,6 +105,7 @@ impl PerformCrud for CreatePrivateMessage { "Private Message from", "Private Message", &content_slurs_removed, + context.settings(), ); } diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 2f14ed3c7..1f7ec82ba 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -38,7 +38,7 @@ impl PerformCrud for EditPrivateMessage { } // Doing the update - let content_slurs_removed = remove_slurs(&data.content); + let content_slurs_removed = remove_slurs(&data.content, &context.settings().slur_regex()); let private_message_id = data.private_message_id; let updated_private_message = blocking(context.pool(), move |conn| { PrivateMessage::update_content(conn, private_message_id, &content_slurs_removed) diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 12a826507..8038f62cb 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -42,8 +42,8 @@ impl PerformCrud for CreateSite { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - check_slurs(&data.name)?; - check_slurs_opt(&data.description)?; + check_slurs(&data.name, &context.settings().slur_regex())?; + check_slurs_opt(&data.description, &context.settings().slur_regex())?; // Make sure user is an admin is_admin(&local_user_view)?; diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index e6bce66a7..1bed1e3bc 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -15,7 +15,7 @@ use lemmy_db_views_actor::{ person_block_view::PersonBlockView, person_view::PersonViewSafe, }; -use lemmy_utils::{settings::structs::Settings, version, ApiError, ConnectionId, LemmyError}; +use lemmy_utils::{version, ApiError, ConnectionId, LemmyError}; use lemmy_websocket::{messages::GetUsersOnline, LemmyContext}; use log::info; @@ -34,7 +34,7 @@ impl PerformCrud for GetSite { Ok(site_view) => Some(site_view), // If the site isn't created yet, check the setup Err(_) => { - if let Some(setup) = Settings::get().setup.as_ref() { + if let Some(setup) = context.settings().setup.as_ref() { let register = Register { username: setup.admin_username.to_owned(), email: setup.admin_email.to_owned(), @@ -132,7 +132,12 @@ impl PerformCrud for GetSite { None }; - let federated_instances = build_federated_instances(context.pool()).await?; + let federated_instances = build_federated_instances( + context.pool(), + &context.settings().federation, + &context.settings().hostname, + ) + .await?; Ok(GetSiteResponse { site_view, diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index 8e6c21186..b92cfd4a1 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -33,8 +33,8 @@ impl PerformCrud for EditSite { let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?; - check_slurs_opt(&data.name)?; - check_slurs_opt(&data.description)?; + check_slurs_opt(&data.name, &context.settings().slur_regex())?; + check_slurs_opt(&data.description, &context.settings().slur_regex())?; // Make sure user is an admin is_admin(&local_user_view)?; diff --git a/crates/api_crud/src/user/create.rs b/crates/api_crud/src/user/create.rs index cd4152edb..2b093e013 100644 --- a/crates/api_crud/src/user/create.rs +++ b/crates/api_crud/src/user/create.rs @@ -29,7 +29,6 @@ use lemmy_db_views_actor::person_view::PersonViewSafe; use lemmy_utils::{ apub::generate_actor_keypair, claims::Claims, - settings::structs::Settings, utils::{check_slurs, is_valid_actor_name}, ApiError, ConnectionId, @@ -69,7 +68,7 @@ impl PerformCrud for Register { .await??; // If its not the admin, check the captcha - if !no_admins && Settings::get().captcha.enabled { + if !no_admins && context.settings().captcha.enabled { let check = context .chat_server() .send(CheckCaptcha { @@ -88,13 +87,17 @@ impl PerformCrud for Register { } } - check_slurs(&data.username)?; + check_slurs(&data.username, &context.settings().slur_regex())?; let actor_keypair = generate_actor_keypair()?; - if !is_valid_actor_name(&data.username) { + if !is_valid_actor_name(&data.username, context.settings().actor_name_max_length) { return Err(ApiError::err("invalid_username").into()); } - let actor_id = generate_apub_endpoint(EndpointType::Person, &data.username)?; + let actor_id = generate_apub_endpoint( + EndpointType::Person, + &data.username, + &context.settings().get_protocol_and_hostname(), + )?; // We have to create both a person, and local_user @@ -164,6 +167,7 @@ impl PerformCrud for Register { let main_community_keypair = generate_actor_keypair()?; // Create the main community if it doesn't exist + let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let main_community = match blocking(context.pool(), move |conn| { Community::read(conn, CommunityId(2)) }) @@ -172,7 +176,11 @@ impl PerformCrud for Register { Ok(c) => c, Err(_e) => { let default_community_name = "main"; - let actor_id = generate_apub_endpoint(EndpointType::Community, default_community_name)?; + let actor_id = generate_apub_endpoint( + EndpointType::Community, + default_community_name, + &protocol_and_hostname, + )?; let community_form = CommunityForm { name: default_community_name.to_string(), title: "The Default Community".to_string(), @@ -219,7 +227,11 @@ impl PerformCrud for Register { // Return the jwt Ok(LoginResponse { - jwt: Claims::jwt(inserted_local_user.id.0, &context.secret().jwt_secret)?, + jwt: Claims::jwt( + inserted_local_user.id.0, + &context.secret().jwt_secret, + &context.settings().hostname, + )?, }) } } diff --git a/crates/api_crud/src/user/read.rs b/crates/api_crud/src/user/read.rs index 720cf3e34..bb45d4731 100644 --- a/crates/api_crud/src/user/read.rs +++ b/crates/api_crud/src/user/read.rs @@ -42,7 +42,8 @@ impl PerformCrud for GetPersonDetails { .username .to_owned() .unwrap_or_else(|| "admin".to_string()); - let actor_id = build_actor_id_from_shortname(EndpointType::Person, &name)?; + let actor_id = + build_actor_id_from_shortname(EndpointType::Person, &name, context.settings())?; let person = blocking(context.pool(), move |conn| { Person::read_from_apub_id(conn, &actor_id) diff --git a/crates/apub/src/activities/comment/create_or_update.rs b/crates/apub/src/activities/comment/create_or_update.rs index 9ce172004..cca9942f5 100644 --- a/crates/apub/src/activities/comment/create_or_update.rs +++ b/crates/apub/src/activities/comment/create_or_update.rs @@ -57,7 +57,10 @@ impl CreateOrUpdateComment { }) .await??; - let id = generate_activity_id(kind.clone())?; + let id = generate_activity_id( + kind.clone(), + &context.settings().get_protocol_and_hostname(), + )?; let maa = collect_non_local_mentions(comment, &community, context).await?; let create_or_update = CreateOrUpdateComment { @@ -87,7 +90,7 @@ impl ActivityHandler for CreateOrUpdateComment { let community = extract_community(&self.cc, context, request_counter).await?; let community_id = ObjectId::new(community.actor_id()); - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &community_id, context, request_counter).await?; verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; // TODO: should add a check that the correct community is in cc (probably needs changes to diff --git a/crates/apub/src/activities/comment/mod.rs b/crates/apub/src/activities/comment/mod.rs index 8f27e9bac..7d5259fdf 100644 --- a/crates/apub/src/activities/comment/mod.rs +++ b/crates/apub/src/activities/comment/mod.rs @@ -14,7 +14,6 @@ use lemmy_db_schema::{ }; use lemmy_utils::{ request::{retry, RecvError}, - settings::structs::Settings, utils::{scrape_text_for_mentions, MentionData}, LemmyError, }; @@ -41,7 +40,16 @@ async fn get_notif_recipients( // anyway. // TODO: for compatibility with other projects, it would be much better to read this from cc or tags let mentions = scrape_text_for_mentions(&comment.content); - send_local_notifs(mentions, comment.clone(), actor, post, context.pool(), true).await + send_local_notifs( + mentions, + comment.clone(), + actor, + post, + context.pool(), + true, + context.settings(), + ) + .await } pub struct MentionsAndAddresses { @@ -70,12 +78,18 @@ pub async fn collect_non_local_mentions( let mentions = scrape_text_for_mentions(&comment.content) .into_iter() // Filter only the non-local ones - .filter(|m| !m.is_local()) + .filter(|m| !m.is_local(&context.settings().hostname)) .collect::>(); for mention in &mentions { // TODO should it be fetching it every time? - if let Ok(actor_id) = fetch_webfinger_url(mention, context.client()).await { + if let Ok(actor_id) = fetch_webfinger_url( + mention, + context.client(), + context.settings().get_protocol_string(), + ) + .await + { let actor_id: ObjectId = ObjectId::new(actor_id); debug!("mention actor_id: {}", actor_id); addressed_ccs.push(actor_id.to_owned().to_string().parse()?); @@ -120,13 +134,14 @@ async fn get_comment_parent_creator( /// Turns a person id like `@name@example.com` into an apub ID, like `https://example.com/user/name`, /// using webfinger. -async fn fetch_webfinger_url(mention: &MentionData, client: &Client) -> Result { +async fn fetch_webfinger_url( + mention: &MentionData, + client: &Client, + protocol_string: &str, +) -> Result { let fetch_url = format!( "{}://{}/.well-known/webfinger?resource=acct:{}@{}", - Settings::get().get_protocol_string(), - mention.domain, - mention.name, - mention.domain + protocol_string, mention.domain, mention.name, mention.domain ); debug!("Fetching webfinger url: {}", &fetch_url); diff --git a/crates/apub/src/activities/community/add_mod.rs b/crates/apub/src/activities/community/add_mod.rs index a066211f3..6c6e56f48 100644 --- a/crates/apub/src/activities/community/add_mod.rs +++ b/crates/apub/src/activities/community/add_mod.rs @@ -55,7 +55,10 @@ impl AddMod { actor: &Person, context: &LemmyContext, ) -> Result<(), LemmyError> { - let id = generate_activity_id(AddType::Add)?; + let id = generate_activity_id( + AddType::Add, + &context.settings().get_protocol_and_hostname(), + )?; let add = AddMod { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], @@ -81,7 +84,7 @@ impl ActivityHandler for AddMod { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; verify_add_remove_moderator_target(&self.target, &self.cc[0])?; diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index 96797c8ff..87e51edaf 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -84,7 +84,10 @@ impl AnnounceActivity { object, cc: vec![community.followers_url()], kind: AnnounceType::Announce, - id: generate_activity_id(&AnnounceType::Announce)?, + id: generate_activity_id( + &AnnounceType::Announce, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }; @@ -100,7 +103,7 @@ impl ActivityHandler for AnnounceActivity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_community(&self.actor, context, request_counter).await?; self.object.verify(context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activities/community/block_user.rs b/crates/apub/src/activities/community/block_user.rs index f7e81f97c..8dddc1c73 100644 --- a/crates/apub/src/activities/community/block_user.rs +++ b/crates/apub/src/activities/community/block_user.rs @@ -56,6 +56,7 @@ impl BlockUserFromCommunity { community: &Community, target: &Person, actor: &Person, + context: &LemmyContext, ) -> Result { Ok(BlockUserFromCommunity { actor: ObjectId::new(actor.actor_id()), @@ -63,7 +64,10 @@ impl BlockUserFromCommunity { object: ObjectId::new(target.actor_id()), cc: [ObjectId::new(community.actor_id())], kind: BlockType::Block, - id: generate_activity_id(BlockType::Block)?, + id: generate_activity_id( + BlockType::Block, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }) @@ -75,7 +79,7 @@ impl BlockUserFromCommunity { actor: &Person, context: &LemmyContext, ) -> Result<(), LemmyError> { - let block = BlockUserFromCommunity::new(community, target, actor)?; + let block = BlockUserFromCommunity::new(community, target, actor, context)?; let block_id = block.id.clone(); let activity = AnnouncableActivities::BlockUserFromCommunity(block); @@ -91,7 +95,7 @@ impl ActivityHandler for BlockUserFromCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; Ok(()) diff --git a/crates/apub/src/activities/community/mod.rs b/crates/apub/src/activities/community/mod.rs index ba80ff4ad..776ef8701 100644 --- a/crates/apub/src/activities/community/mod.rs +++ b/crates/apub/src/activities/community/mod.rs @@ -1,7 +1,7 @@ use crate::{check_is_apub_id_valid, CommunityType}; use itertools::Itertools; use lemmy_db_schema::source::community::Community; -use lemmy_utils::{settings::structs::Settings, LemmyError}; +use lemmy_utils::LemmyError; use lemmy_websocket::LemmyContext; use url::Url; @@ -19,14 +19,16 @@ async fn list_community_follower_inboxes( ) -> Result, LemmyError> { Ok( vec![ - community.get_follower_inboxes(context.pool()).await?, + community + .get_follower_inboxes(context.pool(), context.settings()) + .await?, additional_inboxes, ] .iter() .flatten() .unique() - .filter(|inbox| inbox.host_str() != Some(&Settings::get().hostname)) - .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok()) + .filter(|inbox| inbox.host_str() != Some(&context.settings().hostname)) + .filter(|inbox| check_is_apub_id_valid(inbox, false, context.settings()).is_ok()) .map(|inbox| inbox.to_owned()) .collect(), ) diff --git a/crates/apub/src/activities/community/remove_mod.rs b/crates/apub/src/activities/community/remove_mod.rs index 4960076c1..5edb14b2c 100644 --- a/crates/apub/src/activities/community/remove_mod.rs +++ b/crates/apub/src/activities/community/remove_mod.rs @@ -57,7 +57,10 @@ impl RemoveMod { actor: &Person, context: &LemmyContext, ) -> Result<(), LemmyError> { - let id = generate_activity_id(RemoveType::Remove)?; + let id = generate_activity_id( + RemoveType::Remove, + &context.settings().get_protocol_and_hostname(), + )?; let remove = RemoveMod { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], @@ -83,7 +86,7 @@ impl ActivityHandler for RemoveMod { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; if let Some(target) = &self.target { verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; diff --git a/crates/apub/src/activities/community/undo_block_user.rs b/crates/apub/src/activities/community/undo_block_user.rs index eec906821..a078afe30 100644 --- a/crates/apub/src/activities/community/undo_block_user.rs +++ b/crates/apub/src/activities/community/undo_block_user.rs @@ -52,9 +52,12 @@ impl UndoBlockUserFromCommunity { actor: &Person, context: &LemmyContext, ) -> Result<(), LemmyError> { - let block = BlockUserFromCommunity::new(community, target, actor)?; + let block = BlockUserFromCommunity::new(community, target, actor, context)?; - let id = generate_activity_id(UndoType::Undo)?; + let id = generate_activity_id( + UndoType::Undo, + &context.settings().get_protocol_and_hostname(), + )?; let undo = UndoBlockUserFromCommunity { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], @@ -79,7 +82,7 @@ impl ActivityHandler for UndoBlockUserFromCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; self.object.verify(context, request_counter).await?; diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index a6da1c9f3..52da00554 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -55,7 +55,10 @@ impl UpdateCommunity { actor: &Person, context: &LemmyContext, ) -> Result<(), LemmyError> { - let id = generate_activity_id(UpdateType::Update)?; + let id = generate_activity_id( + UpdateType::Update, + &context.settings().get_protocol_and_hostname(), + )?; let update = UpdateCommunity { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], @@ -79,7 +82,7 @@ impl ActivityHandler for UpdateCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; verify_mod_action(&self.actor, self.cc[0].clone(), context).await?; Ok(()) @@ -96,8 +99,12 @@ impl ActivityHandler for UpdateCommunity { }) .await??; - let updated_community = - Group::from_apub_to_form(&self.object, &community.actor_id.clone().into()).await?; + let updated_community = Group::from_apub_to_form( + &self.object, + &community.actor_id.clone().into(), + context.settings(), + ) + .await?; let cf = CommunityForm { name: updated_community.name, title: updated_community.title, diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 8e8bd942f..8caa551d8 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -87,7 +87,7 @@ impl ActivityHandler for Delete { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_delete_activity( &self.object, self, @@ -138,6 +138,7 @@ impl Delete { community: &Community, object_id: Url, summary: Option, + context: &LemmyContext, ) -> Result { Ok(Delete { actor: ObjectId::new(actor.actor_id()), @@ -146,7 +147,10 @@ impl Delete { cc: [ObjectId::new(community.actor_id())], kind: DeleteType::Delete, summary, - id: generate_activity_id(DeleteType::Delete)?, + id: generate_activity_id( + DeleteType::Delete, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }) @@ -158,7 +162,7 @@ impl Delete { summary: Option, context: &LemmyContext, ) -> Result<(), LemmyError> { - let delete = Delete::new(actor, community, object_id, summary)?; + let delete = Delete::new(actor, community, object_id, summary, context)?; let delete_id = delete.id.clone(); let activity = AnnouncableActivities::Delete(delete); diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 2dbbf9c46..b5ff2df5d 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -59,7 +59,7 @@ impl ActivityHandler for UndoDelete { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; self.object.verify(context, request_counter).await?; verify_delete_activity( &self.object.object, @@ -106,9 +106,12 @@ impl UndoDelete { summary: Option, context: &LemmyContext, ) -> Result<(), LemmyError> { - let object = Delete::new(actor, community, object_id, summary)?; + let object = Delete::new(actor, community, object_id, summary, context)?; - let id = generate_activity_id(UndoType::Undo)?; + let id = generate_activity_id( + UndoType::Undo, + &context.settings().get_protocol_and_hostname(), + )?; let undo = UndoDelete { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], diff --git a/crates/apub/src/activities/following/accept.rs b/crates/apub/src/activities/following/accept.rs index a7472bc1c..c0210e962 100644 --- a/crates/apub/src/activities/following/accept.rs +++ b/crates/apub/src/activities/following/accept.rs @@ -61,7 +61,10 @@ impl AcceptFollowCommunity { to: ObjectId::new(person.actor_id()), object: follow, kind: AcceptType::Accept, - id: generate_activity_id(AcceptType::Accept)?, + id: generate_activity_id( + AcceptType::Accept, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }; @@ -77,7 +80,7 @@ impl ActivityHandler for AcceptFollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_urls_match(self.to.inner(), self.object.actor())?; verify_urls_match(self.actor(), self.object.to.inner())?; verify_community(&self.actor, context, request_counter).await?; diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 8af3d3889..236b27a8f 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -48,13 +48,17 @@ impl FollowCommunity { pub(in crate::activities::following) fn new( actor: &Person, community: &Community, + context: &LemmyContext, ) -> Result { Ok(FollowCommunity { actor: ObjectId::new(actor.actor_id()), to: ObjectId::new(community.actor_id()), object: ObjectId::new(community.actor_id()), kind: FollowType::Follow, - id: generate_activity_id(FollowType::Follow)?, + id: generate_activity_id( + FollowType::Follow, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }) @@ -74,7 +78,7 @@ impl FollowCommunity { }) .await?; - let follow = FollowCommunity::new(actor, community)?; + let follow = FollowCommunity::new(actor, community, context)?; let inbox = vec![community.inbox_url.clone().into()]; send_activity_new(context, &follow, &follow.id, actor, inbox, true).await } @@ -87,7 +91,7 @@ impl ActivityHandler for FollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_urls_match(self.to.inner(), self.object.inner())?; verify_person(&self.actor, context, request_counter).await?; Ok(()) diff --git a/crates/apub/src/activities/following/undo.rs b/crates/apub/src/activities/following/undo.rs index f35b3095f..0ff09ddd7 100644 --- a/crates/apub/src/activities/following/undo.rs +++ b/crates/apub/src/activities/following/undo.rs @@ -49,13 +49,16 @@ impl UndoFollowCommunity { community: &Community, context: &LemmyContext, ) -> Result<(), LemmyError> { - let object = FollowCommunity::new(actor, community)?; + let object = FollowCommunity::new(actor, community, context)?; let undo = UndoFollowCommunity { actor: ObjectId::new(actor.actor_id()), to: ObjectId::new(community.actor_id()), object, kind: UndoType::Undo, - id: generate_activity_id(UndoType::Undo)?, + id: generate_activity_id( + UndoType::Undo, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }; @@ -71,7 +74,7 @@ impl ActivityHandler for UndoFollowCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_urls_match(self.to.inner(), self.object.object.inner())?; verify_urls_match(self.actor(), self.object.actor())?; verify_person(&self.actor, context, request_counter).await?; diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 3cfaca859..e72f1e1dc 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -91,8 +91,8 @@ async fn verify_community( Ok(()) } -fn verify_activity(activity: &dyn ActivityFields) -> Result<(), LemmyError> { - check_is_apub_id_valid(activity.actor(), false)?; +fn verify_activity(activity: &dyn ActivityFields, settings: &Settings) -> Result<(), LemmyError> { + check_is_apub_id_valid(activity.actor(), false, settings)?; verify_domains_match(activity.id_unchecked(), activity.actor())?; Ok(()) } @@ -146,13 +146,13 @@ fn verify_add_remove_moderator_target( /// Generate a unique ID for an activity, in the format: /// `http(s)://example.com/receive/create/202daf0a-1489-45df-8d2e-c8a3173fed36` -fn generate_activity_id(kind: T) -> Result +fn generate_activity_id(kind: T, protocol_and_hostname: &str) -> Result where T: ToString, { let id = format!( "{}/activities/{}/{}", - Settings::get().get_protocol_and_hostname(), + protocol_and_hostname, kind.to_string().to_lowercase(), Uuid::new_v4() ); diff --git a/crates/apub/src/activities/post/create_or_update.rs b/crates/apub/src/activities/post/create_or_update.rs index 8a48da5f8..1a0a9c884 100644 --- a/crates/apub/src/activities/post/create_or_update.rs +++ b/crates/apub/src/activities/post/create_or_update.rs @@ -59,7 +59,10 @@ impl CreateOrUpdatePost { }) .await??; - let id = generate_activity_id(kind.clone())?; + let id = generate_activity_id( + kind.clone(), + &context.settings().get_protocol_and_hostname(), + )?; let create_or_update = CreateOrUpdatePost { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], @@ -83,7 +86,7 @@ impl ActivityHandler for CreateOrUpdatePost { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; let community = self.cc[0].dereference(context, request_counter).await?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; match self.kind { diff --git a/crates/apub/src/activities/private_message/create_or_update.rs b/crates/apub/src/activities/private_message/create_or_update.rs index 83f4824b1..b51e5d7ca 100644 --- a/crates/apub/src/activities/private_message/create_or_update.rs +++ b/crates/apub/src/activities/private_message/create_or_update.rs @@ -42,7 +42,10 @@ impl CreateOrUpdatePrivateMessage { let recipient = blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; - let id = generate_activity_id(kind.clone())?; + let id = generate_activity_id( + kind.clone(), + &context.settings().get_protocol_and_hostname(), + )?; let create_or_update = CreateOrUpdatePrivateMessage { context: lemmy_context(), id: id.clone(), @@ -63,7 +66,7 @@ impl ActivityHandler for CreateOrUpdatePrivateMessage { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person(&self.actor, context, request_counter).await?; verify_domains_match(self.actor.inner(), self.object.id_unchecked())?; self.object.verify(context, request_counter).await?; diff --git a/crates/apub/src/activities/private_message/delete.rs b/crates/apub/src/activities/private_message/delete.rs index 82aad3177..9fb346ec7 100644 --- a/crates/apub/src/activities/private_message/delete.rs +++ b/crates/apub/src/activities/private_message/delete.rs @@ -39,13 +39,17 @@ impl DeletePrivateMessage { pub(in crate::activities::private_message) fn new( actor: &Person, pm: &PrivateMessage, + context: &LemmyContext, ) -> Result { Ok(DeletePrivateMessage { actor: ObjectId::new(actor.actor_id()), to: ObjectId::new(actor.actor_id()), object: pm.ap_id.clone().into(), kind: DeleteType::Delete, - id: generate_activity_id(DeleteType::Delete)?, + id: generate_activity_id( + DeleteType::Delete, + &context.settings().get_protocol_and_hostname(), + )?, context: lemmy_context(), unparsed: Default::default(), }) @@ -55,7 +59,7 @@ impl DeletePrivateMessage { pm: &PrivateMessage, context: &LemmyContext, ) -> Result<(), LemmyError> { - let delete = DeletePrivateMessage::new(actor, pm)?; + let delete = DeletePrivateMessage::new(actor, pm, context)?; let delete_id = delete.id.clone(); let recipient_id = pm.recipient_id; @@ -73,7 +77,7 @@ impl ActivityHandler for DeletePrivateMessage { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person(&self.actor, context, request_counter).await?; verify_domains_match(self.actor.inner(), &self.object)?; Ok(()) diff --git a/crates/apub/src/activities/private_message/undo_delete.rs b/crates/apub/src/activities/private_message/undo_delete.rs index 2dc9d7242..759084b0b 100644 --- a/crates/apub/src/activities/private_message/undo_delete.rs +++ b/crates/apub/src/activities/private_message/undo_delete.rs @@ -50,8 +50,11 @@ impl UndoDeletePrivateMessage { let recipient = blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??; - let object = DeletePrivateMessage::new(actor, pm)?; - let id = generate_activity_id(UndoType::Undo)?; + let object = DeletePrivateMessage::new(actor, pm, context)?; + let id = generate_activity_id( + UndoType::Undo, + &context.settings().get_protocol_and_hostname(), + )?; let undo = UndoDeletePrivateMessage { actor: ObjectId::new(actor.actor_id()), to: ObjectId::new(recipient.actor_id()), @@ -73,7 +76,7 @@ impl ActivityHandler for UndoDeletePrivateMessage { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person(&self.actor, context, request_counter).await?; verify_urls_match(self.actor(), self.object.actor())?; verify_domains_match(self.actor(), &self.object.object)?; diff --git a/crates/apub/src/activities/send/community.rs b/crates/apub/src/activities/send/community.rs index 29aa9dba8..7f9c19e8c 100644 --- a/crates/apub/src/activities/send/community.rs +++ b/crates/apub/src/activities/send/community.rs @@ -4,7 +4,7 @@ use lemmy_api_common::blocking; use lemmy_db_queries::DbPool; use lemmy_db_schema::source::community::Community; use lemmy_db_views_actor::community_follower_view::CommunityFollowerView; -use lemmy_utils::LemmyError; +use lemmy_utils::{settings::structs::Settings, LemmyError}; use url::Url; impl ActorType for Community { @@ -40,7 +40,11 @@ impl CommunityType for Community { } /// For a given community, returns the inboxes of all followers. - async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError> { + async fn get_follower_inboxes( + &self, + pool: &DbPool, + settings: &Settings, + ) -> Result, LemmyError> { let id = self.id; let follows = blocking(pool, move |conn| { @@ -54,7 +58,7 @@ impl CommunityType for Community { .map(|i| i.into_inner()) .unique() // Don't send to blocked instances - .filter(|inbox| check_is_apub_id_valid(inbox, false).is_ok()) + .filter(|inbox| check_is_apub_id_valid(inbox, false, settings).is_ok()) .collect(); Ok(inboxes) diff --git a/crates/apub/src/activities/undo_remove.rs b/crates/apub/src/activities/undo_remove.rs index eaf3684de..10e3fe6e3 100644 --- a/crates/apub/src/activities/undo_remove.rs +++ b/crates/apub/src/activities/undo_remove.rs @@ -43,7 +43,7 @@ impl ActivityHandler for UndoRemovePostCommentOrCommunity { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; self.object.verify(context, request_counter).await?; verify_delete_activity( diff --git a/crates/apub/src/activities/voting/undo_vote.rs b/crates/apub/src/activities/voting/undo_vote.rs index e11d29602..b306692aa 100644 --- a/crates/apub/src/activities/voting/undo_vote.rs +++ b/crates/apub/src/activities/voting/undo_vote.rs @@ -64,8 +64,11 @@ impl UndoVote { }) .await??; - let object = Vote::new(object, actor, &community, kind.clone())?; - let id = generate_activity_id(UndoType::Undo)?; + let object = Vote::new(object, actor, &community, kind.clone(), context)?; + let id = generate_activity_id( + UndoType::Undo, + &context.settings().get_protocol_and_hostname(), + )?; let undo_vote = UndoVote { actor: ObjectId::new(actor.actor_id()), to: [PublicUrl::Public], @@ -88,7 +91,7 @@ impl ActivityHandler for UndoVote { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; verify_urls_match(self.actor(), self.object.actor())?; self.object.verify(context, request_counter).await?; diff --git a/crates/apub/src/activities/voting/vote.rs b/crates/apub/src/activities/voting/vote.rs index 69a9c3ba9..acec272a2 100644 --- a/crates/apub/src/activities/voting/vote.rs +++ b/crates/apub/src/activities/voting/vote.rs @@ -77,6 +77,7 @@ impl Vote { actor: &Person, community: &Community, kind: VoteType, + context: &LemmyContext, ) -> Result { Ok(Vote { actor: ObjectId::new(actor.actor_id()), @@ -84,7 +85,7 @@ impl Vote { object: ObjectId::new(object.ap_id()), cc: [ObjectId::new(community.actor_id())], kind: kind.clone(), - id: generate_activity_id(kind)?, + id: generate_activity_id(kind, &context.settings().get_protocol_and_hostname())?, context: lemmy_context(), unparsed: Default::default(), }) @@ -101,7 +102,7 @@ impl Vote { Community::read(conn, community_id) }) .await??; - let vote = Vote::new(object, actor, &community, kind)?; + let vote = Vote::new(object, actor, &community, kind, context)?; let vote_id = vote.id.clone(); let activity = AnnouncableActivities::Vote(vote); @@ -116,7 +117,7 @@ impl ActivityHandler for Vote { context: &LemmyContext, request_counter: &mut i32, ) -> Result<(), LemmyError> { - verify_activity(self)?; + verify_activity(self, context.settings())?; verify_person_in_community(&self.actor, &self.cc[0], context, request_counter).await?; Ok(()) } diff --git a/crates/apub/src/activity_queue.rs b/crates/apub/src/activity_queue.rs index 01959c7d7..f05c4fce4 100644 --- a/crates/apub/src/activity_queue.rs +++ b/crates/apub/src/activity_queue.rs @@ -16,7 +16,7 @@ use background_jobs::{ WorkerConfig, }; use lemmy_db_schema::source::community::Community; -use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; +use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; use log::{info, warn}; use reqwest::Client; @@ -56,7 +56,7 @@ pub(crate) async fn send_activity_new( where T: Serialize, { - if !Settings::get().federation.enabled || inboxes.is_empty() { + if !context.settings().federation.enabled || inboxes.is_empty() { return Ok(()); } @@ -64,7 +64,7 @@ where // Don't send anything to ourselves // TODO: this should be a debug assert - let hostname = Settings::get().get_hostname_without_port()?; + let hostname = context.settings().get_hostname_without_port()?; let inboxes: Vec<&Url> = inboxes .iter() .filter(|i| i.domain().expect("valid inbox url") != hostname) diff --git a/crates/apub/src/fetcher/community.rs b/crates/apub/src/fetcher/community.rs index 99dd3f85e..6bffe04bd 100644 --- a/crates/apub/src/fetcher/community.rs +++ b/crates/apub/src/fetcher/community.rs @@ -73,8 +73,13 @@ pub(crate) async fn fetch_community_outbox( outbox: &Url, recursion_counter: &mut i32, ) -> Result<(), LemmyError> { - let outbox = - fetch_remote_object::(context.client(), outbox, recursion_counter).await?; + let outbox = fetch_remote_object::( + context.client(), + context.settings(), + outbox, + recursion_counter, + ) + .await?; let outbox_activities = outbox.items().context(location_info!())?.clone(); let mut outbox_activities = outbox_activities.many().context(location_info!())?; if outbox_activities.len() > 20 { @@ -98,9 +103,13 @@ async fn fetch_community_mods( recursion_counter: &mut i32, ) -> Result, LemmyError> { if let Some(mods_url) = &group.moderators { - let mods = - fetch_remote_object::(context.client(), mods_url, recursion_counter) - .await?; + let mods = fetch_remote_object::( + context.client(), + context.settings(), + mods_url, + recursion_counter, + ) + .await?; let mods = mods .items() .map(|i| i.as_many()) diff --git a/crates/apub/src/fetcher/fetch.rs b/crates/apub/src/fetcher/fetch.rs index 1e6f8de89..29c7f9df6 100644 --- a/crates/apub/src/fetcher/fetch.rs +++ b/crates/apub/src/fetcher/fetch.rs @@ -1,6 +1,6 @@ use crate::{check_is_apub_id_valid, APUB_JSON_CONTENT_TYPE}; use anyhow::anyhow; -use lemmy_utils::{request::retry, LemmyError}; +use lemmy_utils::{request::retry, settings::structs::Settings, LemmyError}; use log::info; use reqwest::Client; use serde::Deserialize; @@ -18,6 +18,7 @@ static MAX_REQUEST_NUMBER: i32 = 25; /// timeouts etc. pub(in crate::fetcher) async fn fetch_remote_object( client: &Client, + settings: &Settings, url: &Url, recursion_counter: &mut i32, ) -> Result @@ -28,7 +29,7 @@ where if *recursion_counter > MAX_REQUEST_NUMBER { return Err(anyhow!("Maximum recursion depth reached").into()); } - check_is_apub_id_valid(url, false)?; + check_is_apub_id_valid(url, false, settings)?; let timeout = Duration::from_secs(60); diff --git a/crates/apub/src/fetcher/objects.rs b/crates/apub/src/fetcher/objects.rs new file mode 100644 index 000000000..3453fd389 --- /dev/null +++ b/crates/apub/src/fetcher/objects.rs @@ -0,0 +1,107 @@ +use crate::{ + fetcher::fetch::fetch_remote_object, + objects::{comment::Note, post::Page, FromApub}, + PostOrComment, +}; +use anyhow::anyhow; +use diesel::result::Error::NotFound; +use lemmy_api_common::blocking; +use lemmy_db_queries::{ApubObject, Crud}; +use lemmy_db_schema::source::{comment::Comment, post::Post}; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; +use log::debug; +use url::Url; + +/// Gets a post by its apub ID. If it exists locally, it is returned directly. Otherwise it is +/// pulled from its apub ID, inserted and returned. +/// +/// The parent community is also pulled if necessary. Comments are not pulled. +pub(crate) async fn get_or_fetch_and_insert_post( + post_ap_id: &Url, + context: &LemmyContext, + recursion_counter: &mut i32, +) -> Result { + let post_ap_id_owned = post_ap_id.to_owned(); + let post = blocking(context.pool(), move |conn| { + Post::read_from_apub_id(conn, &post_ap_id_owned.into()) + }) + .await?; + + match post { + Ok(p) => Ok(p), + Err(NotFound {}) => { + debug!("Fetching and creating remote post: {}", post_ap_id); + let page = fetch_remote_object::( + context.client(), + context.settings(), + post_ap_id, + recursion_counter, + ) + .await?; + let post = Post::from_apub(&page, context, post_ap_id, recursion_counter).await?; + + Ok(post) + } + Err(e) => Err(e.into()), + } +} + +/// Gets a comment by its apub ID. If it exists locally, it is returned directly. Otherwise it is +/// pulled from its apub ID, inserted and returned. +/// +/// The parent community, post and comment are also pulled if necessary. +pub(crate) async fn get_or_fetch_and_insert_comment( + comment_ap_id: &Url, + context: &LemmyContext, + recursion_counter: &mut i32, +) -> Result { + let comment_ap_id_owned = comment_ap_id.to_owned(); + let comment = blocking(context.pool(), move |conn| { + Comment::read_from_apub_id(conn, &comment_ap_id_owned.into()) + }) + .await?; + + match comment { + Ok(p) => Ok(p), + Err(NotFound {}) => { + debug!( + "Fetching and creating remote comment and its parents: {}", + comment_ap_id + ); + let comment = fetch_remote_object::( + context.client(), + context.settings(), + comment_ap_id, + recursion_counter, + ) + .await?; + let comment = Comment::from_apub(&comment, context, comment_ap_id, recursion_counter).await?; + + let post_id = comment.post_id; + let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??; + if post.locked { + return Err(anyhow!("Post is locked").into()); + } + + Ok(comment) + } + Err(e) => Err(e.into()), + } +} + +pub(crate) async fn get_or_fetch_and_insert_post_or_comment( + ap_id: &Url, + context: &LemmyContext, + recursion_counter: &mut i32, +) -> Result { + Ok( + match get_or_fetch_and_insert_post(ap_id, context, recursion_counter).await { + Ok(p) => PostOrComment::Post(Box::new(p)), + Err(_) => { + let c = get_or_fetch_and_insert_comment(ap_id, context, recursion_counter).await?; + PostOrComment::Comment(Box::new(c)) + } + }, + ) +} diff --git a/crates/apub/src/fetcher/person.rs b/crates/apub/src/fetcher/person.rs new file mode 100644 index 000000000..887e3c276 --- /dev/null +++ b/crates/apub/src/fetcher/person.rs @@ -0,0 +1,80 @@ +use crate::{ + fetcher::{fetch::fetch_remote_object, is_deleted, should_refetch_actor}, + objects::{person::Person as ApubPerson, FromApub}, +}; +use anyhow::anyhow; +use diesel::result::Error::NotFound; +use lemmy_api_common::blocking; +use lemmy_db_queries::{source::person::Person_, ApubObject}; +use lemmy_db_schema::source::person::Person; +use lemmy_utils::LemmyError; +use lemmy_websocket::LemmyContext; +use log::debug; +use url::Url; + +/// Get a person from its apub ID. +/// +/// If it exists locally and `!should_refetch_actor()`, it is returned directly from the database. +/// Otherwise it is fetched from the remote instance, stored and returned. +pub(crate) async fn get_or_fetch_and_upsert_person( + apub_id: &Url, + context: &LemmyContext, + recursion_counter: &mut i32, +) -> Result { + let apub_id_owned = apub_id.to_owned(); + let person = blocking(context.pool(), move |conn| { + Person::read_from_apub_id(conn, &apub_id_owned.into()) + }) + .await?; + + match person { + // If its older than a day, re-fetch it + Ok(u) if !u.local && should_refetch_actor(u.last_refreshed_at) => { + debug!("Fetching and updating from remote person: {}", apub_id); + let person = fetch_remote_object::( + context.client(), + context.settings(), + apub_id, + recursion_counter, + ) + .await; + + if is_deleted(&person) { + // TODO: use Person::update_deleted() once implemented + blocking(context.pool(), move |conn| { + Person::delete_account(conn, u.id) + }) + .await??; + return Err(anyhow!("Person was deleted by remote instance").into()); + } else if person.is_err() { + return Ok(u); + } + + let person = Person::from_apub(&person?, context, apub_id, recursion_counter).await?; + + let person_id = person.id; + blocking(context.pool(), move |conn| { + Person::mark_as_updated(conn, person_id) + }) + .await??; + + Ok(person) + } + Ok(u) => Ok(u), + Err(NotFound {}) => { + debug!("Fetching and creating remote person: {}", apub_id); + let person = fetch_remote_object::( + context.client(), + context.settings(), + apub_id, + recursion_counter, + ) + .await?; + + let person = Person::from_apub(&person, context, apub_id, recursion_counter).await?; + + Ok(person) + } + Err(e) => Err(e.into()), + } +} diff --git a/crates/apub/src/fetcher/search.rs b/crates/apub/src/fetcher/search.rs index 6a3cc14f0..da152f2f9 100644 --- a/crates/apub/src/fetcher/search.rs +++ b/crates/apub/src/fetcher/search.rs @@ -45,7 +45,14 @@ pub async fn search_by_apub_id( // remote actor, use webfinger to resolve url if name.contains('@') { let (name, domain) = name.splitn(2, '@').collect_tuple().expect("invalid query"); - webfinger_resolve_actor(name, domain, kind, context.client()).await? + webfinger_resolve_actor( + name, + domain, + kind, + context.client(), + context.settings().get_protocol_string(), + ) + .await? } // local actor, read from database and return else { diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index 47182259f..347f56428 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -23,7 +23,7 @@ use lemmy_api_common::blocking; use lemmy_apub_lib::{ActivityFields, ActivityHandler}; use lemmy_db_queries::{source::activity::Activity_, DbPool}; use lemmy_db_schema::source::activity::Activity; -use lemmy_utils::{location_info, settings::structs::Settings, LemmyError}; +use lemmy_utils::{location_info, LemmyError}; use lemmy_websocket::LemmyContext; use log::{info, trace}; use serde::{Deserialize, Serialize}; @@ -98,10 +98,10 @@ where if is_activity_already_known(context.pool(), activity.id_unchecked()).await? { return Ok(HttpResponse::Ok().finish()); } - check_is_apub_id_valid(activity.actor(), false)?; + check_is_apub_id_valid(activity.actor(), false, context.settings())?; info!("Verifying activity {}", activity.id_unchecked().to_string()); activity.verify(context, request_counter).await?; - assert_activity_not_local(&activity)?; + assert_activity_not_local(&activity, &context.settings().hostname)?; // Log the activity, so we avoid receiving and parsing it twice. Note that this could still happen // if we receive the same activity twice in very quick succession. @@ -151,7 +151,7 @@ pub(crate) async fn get_activity( info: web::Path, context: web::Data, ) -> Result, LemmyError> { - let settings = Settings::get(); + let settings = context.settings(); let activity_id = Url::parse(&format!( "{}/activities/{}/{}", settings.get_protocol_and_hostname(), @@ -187,10 +187,13 @@ pub(crate) async fn is_activity_already_known( } } -fn assert_activity_not_local(activity: &T) -> Result<(), LemmyError> { +fn assert_activity_not_local( + activity: &T, + hostname: &str, +) -> Result<(), LemmyError> { let activity_domain = activity.id_unchecked().domain().context(location_info!())?; - if activity_domain == Settings::get().hostname { + if activity_domain == hostname { return Err( anyhow!( "Error: received activity which was sent by local instance: {:?}", diff --git a/crates/apub/src/http/routes.rs b/crates/apub/src/http/routes.rs index cd6b11486..612cc4586 100644 --- a/crates/apub/src/http/routes.rs +++ b/crates/apub/src/http/routes.rs @@ -24,9 +24,9 @@ use sha2::{Digest, Sha256}; static APUB_JSON_CONTENT_TYPE_LONG: &str = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""; -pub fn config(cfg: &mut web::ServiceConfig) { - if Settings::get().federation.enabled { - println!("federation enabled, host is {}", Settings::get().hostname); +pub fn config(cfg: &mut web::ServiceConfig, settings: &Settings) { + if settings.federation.enabled { + println!("federation enabled, host is {}", settings.hostname); let digest_verifier = VerifyDigest::new(Sha256::new()); let header_guard_accept = guard::Any(guard::Header("Accept", APUB_JSON_CONTENT_TYPE)) diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index e7065752a..027da7f7a 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -37,8 +37,8 @@ static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json"; pub(crate) fn check_is_apub_id_valid( apub_id: &Url, use_strict_allowlist: bool, + settings: &Settings, ) -> Result<(), LemmyError> { - let settings = Settings::get(); let domain = apub_id.domain().context(location_info!())?.to_string(); let local_instance = settings.get_hostname_without_port()?; @@ -62,22 +62,22 @@ pub(crate) fn check_is_apub_id_valid( return Err(anyhow!("invalid hostname {}: {}", host, apub_id).into()); } - if apub_id.scheme() != Settings::get().get_protocol_string() { + if apub_id.scheme() != settings.get_protocol_string() { return Err(anyhow!("invalid apub id scheme {}: {}", apub_id.scheme(), apub_id).into()); } // TODO: might be good to put the part above in one method, and below in another // (which only gets called in apub::objects) // -> no that doesnt make sense, we still need the code below for blocklist and strict allowlist - if let Some(blocked) = Settings::get().federation.blocked_instances { + if let Some(blocked) = settings.to_owned().federation.blocked_instances { if blocked.contains(&domain) { return Err(anyhow!("{} is in federation blocklist", domain).into()); } } - if let Some(mut allowed) = Settings::get().federation.allowed_instances { + if let Some(mut allowed) = settings.to_owned().federation.allowed_instances { // Only check allowlist if this is a community, or strict allowlist is enabled. - let strict_allowlist = Settings::get().federation.strict_allowlist; + let strict_allowlist = settings.to_owned().federation.strict_allowlist; if use_strict_allowlist || strict_allowlist { // need to allow this explicitly because apub receive might contain objects from our local // instance. @@ -128,7 +128,11 @@ trait ActorType { #[async_trait::async_trait(?Send)] pub trait CommunityType { fn followers_url(&self) -> Url; - async fn get_follower_inboxes(&self, pool: &DbPool) -> Result, LemmyError>; + async fn get_follower_inboxes( + &self, + pool: &DbPool, + settings: &Settings, + ) -> Result, LemmyError>; } pub enum EndpointType { @@ -160,12 +164,9 @@ fn generate_apub_endpoint_for_domain( pub fn generate_apub_endpoint( endpoint_type: EndpointType, name: &str, + protocol_and_hostname: &str, ) -> Result { - generate_apub_endpoint_for_domain( - endpoint_type, - name, - &Settings::get().get_protocol_and_hostname(), - ) + generate_apub_endpoint_for_domain(endpoint_type, name, protocol_and_hostname) } pub fn generate_followers_url(actor_id: &DbUrl) -> Result { @@ -200,6 +201,7 @@ fn generate_moderators_url(community_id: &DbUrl) -> Result { pub fn build_actor_id_from_shortname( endpoint_type: EndpointType, short_name: &str, + settings: &Settings, ) -> Result { let split = short_name.split('@').collect::>(); @@ -207,9 +209,9 @@ pub fn build_actor_id_from_shortname( // If there's no @, its local let domain = if split.len() == 1 { - Settings::get().get_protocol_and_hostname() + settings.get_protocol_and_hostname() } else { - format!("{}://{}", Settings::get().get_protocol_string(), split[1]) + format!("{}://{}", settings.get_protocol_string(), split[1]) }; generate_apub_endpoint_for_domain(endpoint_type, name, &domain) diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index f33448710..e27817d6f 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -222,7 +222,7 @@ impl FromApub for Comment { } let content = ¬e.source.content; - let content_slurs_removed = remove_slurs(content); + let content_slurs_removed = remove_slurs(content, &context.settings().slur_regex()); let form = CommentForm { creator_id: creator.id, diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index dcd00e6d5..7238c0c42 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -24,6 +24,7 @@ use lemmy_db_schema::{ source::community::{Community, CommunityForm}, }; use lemmy_utils::{ + settings::structs::Settings, utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html}, LemmyError, }; @@ -74,6 +75,7 @@ impl Group { pub(crate) async fn from_apub_to_form( group: &Group, expected_domain: &Url, + settings: &Settings, ) -> Result { let actor_id = Some(group.id(expected_domain)?.clone().into()); let name = group.preferred_username.clone(); @@ -81,9 +83,10 @@ impl Group { let description = group.source.clone().map(|s| s.content); let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into()); - check_slurs(&name)?; - check_slurs(&title)?; - check_slurs_opt(&description)?; + let slur_regex = &settings.slur_regex(); + check_slurs(&name, slur_regex)?; + check_slurs(&title, slur_regex)?; + check_slurs_opt(&description, slur_regex)?; Ok(CommunityForm { name, @@ -175,7 +178,7 @@ impl FromApub for Community { expected_domain: &Url, request_counter: &mut i32, ) -> Result { - let form = Group::from_apub_to_form(group, expected_domain).await?; + let form = Group::from_apub_to_form(group, expected_domain, context.settings()).await?; let community = blocking(context.pool(), move |conn| Community::upsert(conn, &form)).await??; update_community_mods(group, &community, context, request_counter).await?; diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 04af848ff..2c563b9dc 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -150,10 +150,12 @@ impl FromApub for DbPerson { UserTypes::Service => true, }; - check_slurs(&name)?; - check_slurs_opt(&display_name)?; - check_slurs_opt(&bio)?; - check_is_apub_id_valid(&person.id, false)?; + let slur_regex = &context.settings().slur_regex(); + check_slurs(&name, slur_regex)?; + check_slurs_opt(&display_name, slur_regex)?; + check_slurs_opt(&bio, slur_regex)?; + + check_is_apub_id_valid(&person.id, false, context.settings())?; let person_form = PersonForm { name, diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 341b428b6..0cad7ec49 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -100,7 +100,7 @@ impl Page { ) -> Result<(), LemmyError> { let community = extract_community(&self.to, context, request_counter).await?; - check_slurs(&self.name)?; + check_slurs(&self.name, &context.settings().slur_regex())?; verify_domains_match(self.attributed_to.inner(), &self.id)?; verify_person_in_community( &self.attributed_to, @@ -191,7 +191,7 @@ impl FromApub for Post { let thumbnail_url: Option = page.image.clone().map(|i| i.url); let (metadata_res, pictrs_thumbnail) = if let Some(url) = &page.url { - fetch_site_data(context.client(), Some(url)).await + fetch_site_data(context.client(), context.settings(), Some(url)).await } else { (None, thumbnail_url) }; @@ -199,7 +199,10 @@ impl FromApub for Post { .map(|u| (u.title, u.description, u.html)) .unwrap_or((None, None, None)); - let body_slurs_removed = page.source.as_ref().map(|s| remove_slurs(&s.content)); + let body_slurs_removed = page + .source + .as_ref() + .map(|s| remove_slurs(&s.content, &context.settings().slur_regex())); let form = PostForm { name: page.name.clone(), url: page.url.clone().map(|u| u.into()), diff --git a/crates/apub_lib/src/webfinger.rs b/crates/apub_lib/src/webfinger.rs index ebd49ea67..a5395d099 100644 --- a/crates/apub_lib/src/webfinger.rs +++ b/crates/apub_lib/src/webfinger.rs @@ -1,7 +1,6 @@ use anyhow::anyhow; use lemmy_utils::{ request::{retry, RecvError}, - settings::structs::Settings, LemmyError, }; use log::debug; @@ -38,6 +37,7 @@ pub async fn webfinger_resolve_actor( domain: &str, webfinger_type: WebfingerType, client: &Client, + protocol_string: &str, ) -> Result { let webfinger_type = match webfinger_type { WebfingerType::Person => "acct", @@ -45,11 +45,7 @@ pub async fn webfinger_resolve_actor( }; let fetch_url = format!( "{}://{}/.well-known/webfinger?resource={}:{}@{}", - Settings::get().get_protocol_string(), - domain, - webfinger_type, - name, - domain + protocol_string, domain, webfinger_type, name, domain ); debug!("Fetching webfinger url: {}", &fetch_url); diff --git a/crates/routes/src/feeds.rs b/crates/routes/src/feeds.rs index 3592ef18b..66e23ee3e 100644 --- a/crates/routes/src/feeds.rs +++ b/crates/routes/src/feeds.rs @@ -19,12 +19,7 @@ use lemmy_db_views::{ site_view::SiteView, }; use lemmy_db_views_actor::person_mention_view::{PersonMentionQueryBuilder, PersonMentionView}; -use lemmy_utils::{ - claims::Claims, - settings::structs::Settings, - utils::markdown_to_html, - LemmyError, -}; +use lemmy_utils::{claims::Claims, utils::markdown_to_html, LemmyError}; use lemmy_websocket::LemmyContext; use rss::{ extension::dublincore::DublinCoreExtensionBuilder, @@ -98,7 +93,7 @@ async fn get_feed_data( }) .await??; - let items = create_post_items(posts)?; + let items = create_post_items(posts, &context.settings().get_protocol_and_hostname())?; let mut channel_builder = ChannelBuilder::default(); channel_builder @@ -108,7 +103,7 @@ async fn get_feed_data( site_view.site.name, listing_type.to_string() )) - .link(Settings::get().get_protocol_and_hostname()) + .link(context.settings().get_protocol_and_hostname()) .items(items); if let Some(site_desc) = site_view.site.description { @@ -142,12 +137,19 @@ async fn get_feed( }; let jwt_secret = context.secret().jwt_secret.to_owned(); + let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let builder = blocking(context.pool(), move |conn| match request_type { - RequestType::User => get_feed_user(conn, &sort_type, param), - RequestType::Community => get_feed_community(conn, &sort_type, param), - RequestType::Front => get_feed_front(conn, &jwt_secret, &sort_type, param), - RequestType::Inbox => get_feed_inbox(conn, &jwt_secret, param), + RequestType::User => get_feed_user(conn, &sort_type, ¶m, &protocol_and_hostname), + RequestType::Community => get_feed_community(conn, &sort_type, ¶m, &protocol_and_hostname), + RequestType::Front => get_feed_front( + conn, + &jwt_secret, + &sort_type, + ¶m, + &protocol_and_hostname, + ), + RequestType::Inbox => get_feed_inbox(conn, &jwt_secret, ¶m, &protocol_and_hostname), }) .await? .map_err(ErrorBadRequest)?; @@ -172,10 +174,11 @@ fn get_sort_type(info: web::Query) -> Result { fn get_feed_user( conn: &PgConnection, sort_type: &SortType, - user_name: String, + user_name: &str, + protocol_and_hostname: &str, ) -> Result { let site_view = SiteView::read(conn)?; - let person = Person::find_by_name(conn, &user_name)?; + let person = Person::find_by_name(conn, user_name)?; let posts = PostQueryBuilder::create(conn) .listing_type(ListingType::All) @@ -183,7 +186,7 @@ fn get_feed_user( .creator_id(person.id) .list()?; - let items = create_post_items(posts)?; + let items = create_post_items(posts, protocol_and_hostname)?; let mut channel_builder = ChannelBuilder::default(); channel_builder @@ -198,10 +201,11 @@ fn get_feed_user( fn get_feed_community( conn: &PgConnection, sort_type: &SortType, - community_name: String, + community_name: &str, + protocol_and_hostname: &str, ) -> Result { let site_view = SiteView::read(conn)?; - let community = Community::read_from_name(conn, &community_name)?; + let community = Community::read_from_name(conn, community_name)?; let posts = PostQueryBuilder::create(conn) .listing_type(ListingType::All) @@ -209,7 +213,7 @@ fn get_feed_community( .community_id(community.id) .list()?; - let items = create_post_items(posts)?; + let items = create_post_items(posts, protocol_and_hostname)?; let mut channel_builder = ChannelBuilder::default(); channel_builder @@ -229,10 +233,11 @@ fn get_feed_front( conn: &PgConnection, jwt_secret: &str, sort_type: &SortType, - jwt: String, + jwt: &str, + protocol_and_hostname: &str, ) -> Result { let site_view = SiteView::read(conn)?; - let local_user_id = LocalUserId(Claims::decode(&jwt, jwt_secret)?.claims.sub); + let local_user_id = LocalUserId(Claims::decode(jwt, jwt_secret)?.claims.sub); let local_user = LocalUser::read(conn, local_user_id)?; let posts = PostQueryBuilder::create(conn) @@ -243,13 +248,13 @@ fn get_feed_front( .sort(*sort_type) .list()?; - let items = create_post_items(posts)?; + let items = create_post_items(posts, protocol_and_hostname)?; let mut channel_builder = ChannelBuilder::default(); channel_builder .namespaces(RSS_NAMESPACE.to_owned()) .title(&format!("{} - Subscribed", site_view.site.name)) - .link(Settings::get().get_protocol_and_hostname()) + .link(protocol_and_hostname) .items(items); if let Some(site_desc) = site_view.site.description { @@ -262,10 +267,11 @@ fn get_feed_front( fn get_feed_inbox( conn: &PgConnection, jwt_secret: &str, - jwt: String, + jwt: &str, + protocol_and_hostname: &str, ) -> Result { let site_view = SiteView::read(conn)?; - let local_user_id = LocalUserId(Claims::decode(&jwt, jwt_secret)?.claims.sub); + let local_user_id = LocalUserId(Claims::decode(jwt, jwt_secret)?.claims.sub); let local_user = LocalUser::read(conn, local_user_id)?; let person_id = local_user.person_id; let show_bot_accounts = local_user.show_bot_accounts; @@ -285,16 +291,13 @@ fn get_feed_inbox( .sort(sort) .list()?; - let items = create_reply_and_mention_items(replies, mentions)?; + let items = create_reply_and_mention_items(replies, mentions, protocol_and_hostname)?; let mut channel_builder = ChannelBuilder::default(); channel_builder .namespaces(RSS_NAMESPACE.to_owned()) .title(&format!("{} - Inbox", site_view.site.name)) - .link(format!( - "{}/inbox", - Settings::get().get_protocol_and_hostname() - )) + .link(format!("{}/inbox", protocol_and_hostname,)) .items(items); if let Some(site_desc) = site_view.site.description { @@ -307,21 +310,21 @@ fn get_feed_inbox( fn create_reply_and_mention_items( replies: Vec, mentions: Vec, + protocol_and_hostname: &str, ) -> Result, LemmyError> { let mut reply_items: Vec = replies .iter() .map(|r| { let reply_url = format!( "{}/post/{}/comment/{}", - Settings::get().get_protocol_and_hostname(), - r.post.id, - r.comment.id + protocol_and_hostname, r.post.id, r.comment.id ); build_item( &r.creator.name, &r.comment.published, &reply_url, &r.comment.content, + protocol_and_hostname, ) }) .collect::, LemmyError>>()?; @@ -331,15 +334,14 @@ fn create_reply_and_mention_items( .map(|m| { let mention_url = format!( "{}/post/{}/comment/{}", - Settings::get().get_protocol_and_hostname(), - m.post.id, - m.comment.id + protocol_and_hostname, m.post.id, m.comment.id ); build_item( &m.creator.name, &m.comment.published, &mention_url, &m.comment.content, + protocol_and_hostname, ) }) .collect::, LemmyError>>()?; @@ -353,14 +355,11 @@ fn build_item( published: &NaiveDateTime, url: &str, content: &str, + protocol_and_hostname: &str, ) -> Result { let mut i = ItemBuilder::default(); i.title(format!("Reply from {}", creator_name)); - let author_url = format!( - "{}/u/{}", - Settings::get().get_protocol_and_hostname(), - creator_name - ); + let author_url = format!("{}/u/{}", protocol_and_hostname, creator_name); i.author(format!( "/u/{} (link)", creator_name, author_url @@ -381,7 +380,10 @@ fn build_item( Ok(i.build().map_err(|e| anyhow!(e))?) } -fn create_post_items(posts: Vec) -> Result, LemmyError> { +fn create_post_items( + posts: Vec, + protocol_and_hostname: &str, +) -> Result, LemmyError> { let mut items: Vec = Vec::new(); for p in posts { @@ -395,11 +397,7 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { let dt = DateTime::::from_utc(p.post.published, Utc); i.pub_date(dt.to_rfc2822()); - let post_url = format!( - "{}/post/{}", - Settings::get().get_protocol_and_hostname(), - p.post.id - ); + let post_url = format!("{}/post/{}", protocol_and_hostname, p.post.id); i.link(post_url.to_owned()); i.comments(post_url.to_owned()); let guid = GuidBuilder::default() @@ -409,11 +407,7 @@ fn create_post_items(posts: Vec) -> Result, LemmyError> { .map_err(|e| anyhow!(e))?; i.guid(guid); - let community_url = format!( - "{}/c/{}", - Settings::get().get_protocol_and_hostname(), - p.community.name - ); + let community_url = format!("{}/c/{}", protocol_and_hostname, p.community.name); // TODO add images let mut description = format!("submitted by {} to {}
{} points | {} comments", diff --git a/crates/routes/src/images.rs b/crates/routes/src/images.rs index 323e2889c..035514f21 100644 --- a/crates/routes/src/images.rs +++ b/crates/routes/src/images.rs @@ -2,7 +2,7 @@ use actix_http::http::header::ACCEPT_ENCODING; use actix_web::{body::BodyStream, http::StatusCode, web::Data, *}; use anyhow::anyhow; use awc::Client; -use lemmy_utils::{claims::Claims, rate_limit::RateLimit, settings::structs::Settings, LemmyError}; +use lemmy_utils::{claims::Claims, rate_limit::RateLimit, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -58,7 +58,13 @@ async fn upload( return Ok(HttpResponse::Unauthorized().finish()); }; - let mut client_req = client.request_from(format!("{}/image", pictrs_url()?), req.head()); + let mut client_req = client.request_from( + format!( + "{}/image", + pictrs_url(context.settings().to_owned().pictrs_url)? + ), + req.head(), + ); // remove content-encoding header so that pictrs doesnt send gzipped response client_req.headers_mut().remove(ACCEPT_ENCODING); @@ -81,17 +87,28 @@ async fn full_res( web::Query(params): web::Query, req: HttpRequest, client: web::Data, + context: web::Data, ) -> Result { let name = &filename.into_inner(); // If there are no query params, the URL is original + let pictrs_url_settings = context.settings().to_owned().pictrs_url; let url = if params.format.is_none() && params.thumbnail.is_none() { - format!("{}/image/original/{}", pictrs_url()?, name,) + format!( + "{}/image/original/{}", + pictrs_url(pictrs_url_settings)?, + name, + ) } else { // Use jpg as a default when none is given let format = params.format.unwrap_or_else(|| "jpg".to_string()); - let mut url = format!("{}/image/process.{}?src={}", pictrs_url()?, format, name,); + let mut url = format!( + "{}/image/process.{}?src={}", + pictrs_url(pictrs_url_settings)?, + format, + name, + ); if let Some(size) = params.thumbnail { url = format!("{}&thumbnail={}", url, size,); @@ -137,10 +154,16 @@ async fn delete( components: web::Path<(String, String)>, req: HttpRequest, client: web::Data, + context: web::Data, ) -> Result { let (token, file) = components.into_inner(); - let url = format!("{}/image/delete/{}/{}", pictrs_url()?, &token, &file); + let url = format!( + "{}/image/delete/{}/{}", + pictrs_url(context.settings().to_owned().pictrs_url)?, + &token, + &file + ); let mut client_req = client.request_from(url, req.head()); client_req.headers_mut().remove(ACCEPT_ENCODING); @@ -158,8 +181,6 @@ async fn delete( Ok(HttpResponse::build(res.status()).body(BodyStream::new(res))) } -fn pictrs_url() -> Result { - Settings::get() - .pictrs_url - .ok_or_else(|| anyhow!("images_disabled").into()) +fn pictrs_url(pictrs_url: Option) -> Result { + pictrs_url.ok_or_else(|| anyhow!("images_disabled").into()) } diff --git a/crates/routes/src/nodeinfo.rs b/crates/routes/src/nodeinfo.rs index 86d625900..9e327efae 100644 --- a/crates/routes/src/nodeinfo.rs +++ b/crates/routes/src/nodeinfo.rs @@ -2,7 +2,7 @@ use actix_web::{body::Body, error::ErrorBadRequest, *}; use anyhow::anyhow; use lemmy_api_common::blocking; use lemmy_db_views::site_view::SiteView; -use lemmy_utils::{settings::structs::Settings, version, LemmyError}; +use lemmy_utils::{version, LemmyError}; use lemmy_websocket::LemmyContext; use serde::{Deserialize, Serialize}; use url::Url; @@ -13,13 +13,15 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route("/.well-known/nodeinfo", web::get().to(node_info_well_known)); } -async fn node_info_well_known() -> Result, LemmyError> { +async fn node_info_well_known( + context: web::Data, +) -> Result, LemmyError> { let node_info = NodeInfoWellKnown { links: NodeInfoWellKnownLinks { rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?, href: Url::parse(&format!( "{}/nodeinfo/2.0.json", - Settings::get().get_protocol_and_hostname() + &context.settings().get_protocol_and_hostname(), ))?, }, }; @@ -31,7 +33,7 @@ async fn node_info(context: web::Data) -> Result, context: web::Data, ) -> Result { - let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX + let community_regex_parsed = context + .settings() + .webfinger_community_regex() .captures(&info.resource) .map(|c| c.get(1)) .flatten(); - let username_regex_parsed = WEBFINGER_USERNAME_REGEX + let username_regex_parsed = context + .settings() + .webfinger_username_regex() .captures(&info.resource) .map(|c| c.get(1)) .flatten(); diff --git a/crates/utils/src/claims.rs b/crates/utils/src/claims.rs index 2b6ce9854..d68b4119d 100644 --- a/crates/utils/src/claims.rs +++ b/crates/utils/src/claims.rs @@ -1,4 +1,4 @@ -use crate::{settings::structs::Settings, LemmyError}; +use crate::LemmyError; use chrono::Utc; use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation}; use serde::{Deserialize, Serialize}; @@ -24,10 +24,10 @@ impl Claims { Ok(decode::(jwt, &key, &v)?) } - pub fn jwt(local_user_id: i32, jwt_secret: &str) -> Result { + pub fn jwt(local_user_id: i32, jwt_secret: &str, hostname: &str) -> Result { let my_claims = Claims { sub: local_user_id, - iss: Settings::get().hostname, + iss: hostname.to_string(), iat: Utc::now().timestamp(), }; diff --git a/crates/utils/src/email.rs b/crates/utils/src/email.rs index 3e5307421..77f83d020 100644 --- a/crates/utils/src/email.rs +++ b/crates/utils/src/email.rs @@ -18,9 +18,10 @@ pub fn send_email( to_email: &str, to_username: &str, html: &str, + settings: &Settings, ) -> Result<(), String> { - let email_config = Settings::get().email.ok_or("no_email_setup")?; - let domain = Settings::get().hostname; + let email_config = settings.email.to_owned().ok_or("no_email_setup")?; + let domain = settings.hostname.to_owned(); let (smtp_server, smtp_port) = { let email_and_port = email_config.smtp_server.split(':').collect::>(); diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index f1093fd58..ad539f8b0 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -17,9 +17,8 @@ mod test; pub mod utils; pub mod version; -use crate::settings::structs::Settings; use http::StatusCode; -use regex::Regex; + use std::fmt; use thiserror::Error; @@ -88,16 +87,3 @@ impl actix_web::error::ResponseError for LemmyError { } } } - -lazy_static! { - pub static ref WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new(&format!( - "^group:([a-z0-9_]{{3,}})@{}$", - Settings::get().hostname - )) - .expect("compile webfinger regex"); - pub static ref WEBFINGER_USERNAME_REGEX: Regex = Regex::new(&format!( - "^acct:([a-z0-9_]{{3,}})@{}$", - Settings::get().hostname - )) - .expect("compile webfinger regex"); -} diff --git a/crates/utils/src/rate_limit/mod.rs b/crates/utils/src/rate_limit/mod.rs index ca7f124cd..209e7acb5 100644 --- a/crates/utils/src/rate_limit/mod.rs +++ b/crates/utils/src/rate_limit/mod.rs @@ -1,9 +1,4 @@ -use crate::{ - settings::structs::{RateLimitConfig, Settings}, - utils::get_ip, - IpAddr, - LemmyError, -}; +use crate::{settings::structs::RateLimitConfig, utils::get_ip, IpAddr, LemmyError}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use futures::future::{ok, Ready}; use rate_limiter::{RateLimitType, RateLimiter}; @@ -22,11 +17,13 @@ pub struct RateLimit { // it might be reasonable to use a std::sync::Mutex here, since we don't need to lock this // across await points pub rate_limiter: Arc>, + pub rate_limit_config: RateLimitConfig, } #[derive(Debug, Clone)] pub struct RateLimited { rate_limiter: Arc>, + rate_limit_config: RateLimitConfig, type_: RateLimitType, } @@ -55,6 +52,7 @@ impl RateLimit { fn kind(&self, type_: RateLimitType) -> RateLimited { RateLimited { rate_limiter: self.rate_limiter.clone(), + rate_limit_config: self.rate_limit_config.clone(), type_, } } @@ -71,7 +69,8 @@ impl RateLimited { { // Does not need to be blocking because the RwLock in settings never held across await points, // and the operation here locks only long enough to clone - let rate_limit: RateLimitConfig = Settings::get().rate_limit.unwrap_or_default(); + // let rate_limit: RateLimitConfig = Settings::get().rate_limit.unwrap_or_default(); + let rate_limit = self.rate_limit_config; // before { diff --git a/crates/utils/src/request.rs b/crates/utils/src/request.rs index f1655710e..f143b1781 100644 --- a/crates/utils/src/request.rs +++ b/crates/utils/src/request.rs @@ -120,9 +120,10 @@ pub(crate) struct PictrsFile { pub(crate) async fn fetch_pictrs( client: &Client, + settings: &Settings, image_url: &Url, ) -> Result { - if let Some(pictrs_url) = Settings::get().pictrs_url { + if let Some(pictrs_url) = settings.pictrs_url.to_owned() { is_image_content_type(client, image_url).await?; let fetch_url = format!( @@ -152,6 +153,7 @@ pub(crate) async fn fetch_pictrs( /// Returns the SiteMetadata, and a Pictrs URL, if there is a picture associated pub async fn fetch_site_data( client: &Client, + settings: &Settings, url: Option<&Url>, ) -> (Option, Option) { match &url { @@ -166,16 +168,16 @@ pub async fn fetch_site_data( Some(metadata_res) => match &metadata_res.image { // Metadata, with image // Try to generate a small thumbnail if there's a full sized one from post-links - Some(metadata_image) => fetch_pictrs(client, metadata_image) + Some(metadata_image) => fetch_pictrs(client, settings, metadata_image) .await .map(|r| r.files[0].file.to_owned()), // Metadata, but no image - None => fetch_pictrs(client, url) + None => fetch_pictrs(client, settings, url) .await .map(|r| r.files[0].file.to_owned()), }, // No metadata, try to fetch the URL as an image - None => fetch_pictrs(client, url) + None => fetch_pictrs(client, settings, url) .await .map(|r| r.files[0].file.to_owned()), }; @@ -185,7 +187,7 @@ pub async fn fetch_site_data( .map(|p| { Url::parse(&format!( "{}/pictrs/image/{}", - Settings::get().get_protocol_and_hostname(), + settings.get_protocol_and_hostname(), p )) .ok() diff --git a/crates/utils/src/settings/mod.rs b/crates/utils/src/settings/mod.rs index d4776326f..e46f751a6 100644 --- a/crates/utils/src/settings/mod.rs +++ b/crates/utils/src/settings/mod.rs @@ -1,23 +1,25 @@ use crate::{location_info, settings::structs::Settings, LemmyError}; use anyhow::{anyhow, Context}; use deser_hjson::from_str; -use std::{env, fs, io::Error, sync::RwLock}; +use regex::{Regex, RegexBuilder}; +use std::{env, fs, io::Error}; pub mod structs; static CONFIG_FILE: &str = "config/config.hjson"; -lazy_static! { - static ref SETTINGS: RwLock = - RwLock::new(Settings::init().expect("Failed to load settings file")); -} +// static mut WEBFINGER_COMMUNITY_REGEX: Regex = Regex::new("") +// .expect("compile webfinger regex"); +// static mut WEBFINGER_USERNAME_REGEX: Regex = Regex::new("") +// .expect("compile webfinger regex"); impl Settings { /// Reads config from configuration file. /// /// Note: The env var `LEMMY_DATABASE_URL` is parsed in /// `lemmy_db_queries/src/lib.rs::get_database_url_from_env()` - fn init() -> Result { + /// Warning: Only call this once. + pub fn init() -> Result { // Read the config file let config = from_str::(&Self::read_config_file()?)?; @@ -28,11 +30,6 @@ impl Settings { Ok(config) } - /// Returns the config as a struct. - pub fn get() -> Self { - SETTINGS.read().expect("read config").to_owned() - } - pub fn get_database_url(&self) -> String { let conf = &self.database; format!( @@ -81,15 +78,30 @@ impl Settings { pub fn save_config_file(data: &str) -> Result { fs::write(CONFIG_FILE, data)?; - - // Reload the new settings - // From https://stackoverflow.com/questions/29654927/how-do-i-assign-a-string-to-a-mutable-static-variable/47181804#47181804 - let mut new_settings = SETTINGS.write().expect("write config"); - *new_settings = match Settings::init() { - Ok(c) => c, - Err(e) => panic!("{}", e), - }; - Ok(Self::read_config_file()?) } + + // TODO for these regexes: we can't use lazy_static here anymore, since the settings must be + // initialized first. + pub fn webfinger_community_regex(&self) -> Regex { + Regex::new(&format!("^group:([a-z0-9_]{{3,}})@{}$", self.hostname)) + .expect("compile webfinger regex") + } + + pub fn webfinger_username_regex(&self) -> Regex { + Regex::new(&format!("^acct:([a-z0-9_]{{3,}})@{}$", self.hostname)) + .expect("compile webfinger regex") + } + + pub fn slur_regex(&self) -> Regex { + let mut slurs = r"(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)".to_string(); + if let Some(additional_slurs) = &self.additional_slurs { + slurs.push('|'); + slurs.push_str(additional_slurs); + }; + RegexBuilder::new(&slurs) + .case_insensitive(true) + .build() + .expect("compile regex") + } } diff --git a/crates/utils/src/test.rs b/crates/utils/src/test.rs index 33bf38700..a59d04129 100644 --- a/crates/utils/src/test.rs +++ b/crates/utils/src/test.rs @@ -1,12 +1,15 @@ -use crate::utils::{ - is_valid_actor_name, - is_valid_display_name, - is_valid_matrix_id, - is_valid_post_title, - remove_slurs, - scrape_text_for_mentions, - slur_check, - slurs_vec_to_str, +use crate::{ + settings::structs::Settings, + utils::{ + is_valid_actor_name, + is_valid_display_name, + is_valid_matrix_id, + is_valid_post_title, + remove_slurs, + scrape_text_for_mentions, + slur_check, + slurs_vec_to_str, + }, }; #[test] @@ -21,23 +24,28 @@ fn test_mentions_regex() { #[test] fn test_valid_actor_name() { - assert!(is_valid_actor_name("Hello_98")); - assert!(is_valid_actor_name("ten")); - assert!(!is_valid_actor_name("Hello-98")); - assert!(!is_valid_actor_name("a")); - assert!(!is_valid_actor_name("")); + let actor_name_max_length = Settings::init().unwrap().actor_name_max_length; + assert!(is_valid_actor_name("Hello_98", actor_name_max_length)); + assert!(is_valid_actor_name("ten", actor_name_max_length)); + assert!(!is_valid_actor_name("Hello-98", actor_name_max_length)); + assert!(!is_valid_actor_name("a", actor_name_max_length)); + assert!(!is_valid_actor_name("", actor_name_max_length)); } #[test] fn test_valid_display_name() { - assert!(is_valid_display_name("hello @there")); - assert!(!is_valid_display_name("@hello there")); + let actor_name_max_length = Settings::init().unwrap().actor_name_max_length; + assert!(is_valid_display_name("hello @there", actor_name_max_length)); + assert!(!is_valid_display_name( + "@hello there", + actor_name_max_length + )); // Make sure zero-space with an @ doesn't work - assert!(!is_valid_display_name(&format!( - "{}@my name is", - '\u{200b}' - ))); + assert!(!is_valid_display_name( + &format!("{}@my name is", '\u{200b}'), + actor_name_max_length + )); } #[test] @@ -57,11 +65,12 @@ fn test_valid_matrix_id() { #[test] fn test_slur_filter() { + let slur_regex = Settings::init().unwrap().slur_regex(); let test = "faggot test kike tranny cocksucker retardeds. Capitalized Niggerz. This is a bunch of other safe text."; let slur_free = "No slurs here"; assert_eq!( - remove_slurs(test), + remove_slurs(test, &slur_regex), "*removed* test *removed* *removed* *removed* *removed*. Capitalized *removed*. This is a bunch of other safe text." .to_string() ); @@ -76,9 +85,9 @@ fn test_slur_filter() { ]; let has_slurs_err_str = "No slurs - Niggerz, cocksucker, faggot, kike, retardeds, tranny"; - assert_eq!(slur_check(test), Err(has_slurs_vec)); - assert_eq!(slur_check(slur_free), Ok(())); - if let Err(slur_vec) = slur_check(test) { + assert_eq!(slur_check(test, &slur_regex), Err(has_slurs_vec)); + assert_eq!(slur_check(slur_free, &slur_regex), Ok(())); + if let Err(slur_vec) = slur_check(test, &slur_regex) { assert_eq!(&slurs_vec_to_str(slur_vec), has_slurs_err_str); } } diff --git a/crates/utils/src/utils.rs b/crates/utils/src/utils.rs index 0fb1f7888..97384fc2f 100644 --- a/crates/utils/src/utils.rs +++ b/crates/utils/src/utils.rs @@ -1,22 +1,13 @@ -use crate::{settings::structs::Settings, ApiError, IpAddr}; +use crate::{ApiError, IpAddr}; use actix_web::dev::ConnectionInfo; use chrono::{DateTime, FixedOffset, NaiveDateTime}; use itertools::Itertools; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use regex::{Regex, RegexBuilder}; +use regex::Regex; use url::Url; lazy_static! { static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").expect("compile regex"); - static ref SLUR_REGEX: Regex = { - let mut slurs = r"(fag(g|got|tard)?\b|cock\s?sucker(s|ing)?|ni((g{2,}|q)+|[gq]{2,})[e3r]+(s|z)?|mudslime?s?|kikes?|\bspi(c|k)s?\b|\bchinks?|gooks?|bitch(es|ing|y)?|whor(es?|ing)|\btr(a|@)nn?(y|ies?)|\b(b|re|r)tard(ed)?s?)".to_string(); - if let Some(additional_slurs) = Settings::get().additional_slurs { - slurs.push('|'); - slurs.push_str(&additional_slurs); - }; - RegexBuilder::new(&slurs).case_insensitive(true).build().expect("compile regex") - }; - static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").expect("compile regex"); // TODO keep this old one, it didn't work with port well tho @@ -37,12 +28,12 @@ pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime { DateTime::::from_utc(datetime, FixedOffset::east(0)) } -pub fn remove_slurs(test: &str) -> String { - SLUR_REGEX.replace_all(test, "*removed*").to_string() +pub fn remove_slurs(test: &str, slur_regex: &Regex) -> String { + slur_regex.replace_all(test, "*removed*").to_string() } -pub(crate) fn slur_check(test: &str) -> Result<(), Vec<&str>> { - let mut matches: Vec<&str> = SLUR_REGEX.find_iter(test).map(|mat| mat.as_str()).collect(); +pub(crate) fn slur_check<'a>(test: &'a str, slur_regex: &'a Regex) -> Result<(), Vec<&'a str>> { + let mut matches: Vec<&str> = slur_regex.find_iter(test).map(|mat| mat.as_str()).collect(); // Unique matches.sort_unstable(); @@ -55,17 +46,17 @@ pub(crate) fn slur_check(test: &str) -> Result<(), Vec<&str>> { } } -pub fn check_slurs(text: &str) -> Result<(), ApiError> { - if let Err(slurs) = slur_check(text) { +pub fn check_slurs(text: &str, slur_regex: &Regex) -> Result<(), ApiError> { + if let Err(slurs) = slur_check(text, slur_regex) { Err(ApiError::err(&slurs_vec_to_str(slurs))) } else { Ok(()) } } -pub fn check_slurs_opt(text: &Option) -> Result<(), ApiError> { +pub fn check_slurs_opt(text: &Option, slur_regex: &Regex) -> Result<(), ApiError> { match text { - Some(t) => check_slurs(t), + Some(t) => check_slurs(t, slur_regex), None => Ok(()), } } @@ -96,8 +87,8 @@ pub struct MentionData { } impl MentionData { - pub fn is_local(&self) -> bool { - Settings::get().hostname.eq(&self.domain) + pub fn is_local(&self, hostname: &str) -> bool { + hostname.eq(&self.domain) } pub fn full_name(&self) -> String { format!("@{}@{}", &self.name, &self.domain) @@ -115,17 +106,16 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec { out.into_iter().unique().collect() } -pub fn is_valid_actor_name(name: &str) -> bool { - name.chars().count() <= Settings::get().actor_name_max_length - && VALID_ACTOR_NAME_REGEX.is_match(name) +pub fn is_valid_actor_name(name: &str, actor_name_max_length: usize) -> bool { + name.chars().count() <= actor_name_max_length && VALID_ACTOR_NAME_REGEX.is_match(name) } // Can't do a regex here, reverse lookarounds not supported -pub fn is_valid_display_name(name: &str) -> bool { +pub fn is_valid_display_name(name: &str, actor_name_max_length: usize) -> bool { !name.starts_with('@') && !name.starts_with('\u{200b}') && name.chars().count() >= 3 - && name.chars().count() <= Settings::get().actor_name_max_length + && name.chars().count() <= actor_name_max_length } pub fn is_valid_matrix_id(matrix_id: &str) -> bool { diff --git a/crates/websocket/src/chat_server.rs b/crates/websocket/src/chat_server.rs index ee3121e69..7ab65aad2 100644 --- a/crates/websocket/src/chat_server.rs +++ b/crates/websocket/src/chat_server.rs @@ -18,6 +18,7 @@ use lemmy_db_schema::{source::secret::Secret, CommunityId, LocalUserId, PostId}; use lemmy_utils::{ location_info, rate_limit::RateLimit, + settings::structs::Settings, ApiError, ConnectionId, IpAddr, @@ -71,6 +72,9 @@ pub struct ChatServer { /// The DB Pool pub(super) pool: Pool>, + /// The Settings + pub(super) settings: Settings, + /// The Secrets pub(super) secret: Secret, @@ -98,6 +102,7 @@ pub struct SessionInfo { /// And manages available rooms. Peers send messages to other peers in same /// room through `ChatServer`. impl ChatServer { + #![allow(clippy::too_many_arguments)] pub fn startup( pool: Pool>, rate_limiter: RateLimit, @@ -105,6 +110,7 @@ impl ChatServer { message_handler_crud: MessageHandlerCrudType, client: Client, activity_queue: QueueHandle, + settings: Settings, secret: Secret, ) -> ChatServer { ChatServer { @@ -121,6 +127,7 @@ impl ChatServer { message_handler_crud, client, activity_queue, + settings, secret, } } @@ -457,6 +464,7 @@ impl ChatServer { chat_server: ctx.address(), client: self.client.to_owned(), activity_queue: self.activity_queue.to_owned(), + settings: self.settings.to_owned(), secret: self.secret.to_owned(), }; let message_handler_crud = self.message_handler_crud; diff --git a/crates/websocket/src/lib.rs b/crates/websocket/src/lib.rs index 99712d9f7..a91a66812 100644 --- a/crates/websocket/src/lib.rs +++ b/crates/websocket/src/lib.rs @@ -6,7 +6,7 @@ use actix::Addr; use background_jobs::QueueHandle; use lemmy_db_queries::DbPool; use lemmy_db_schema::source::secret::Secret; -use lemmy_utils::LemmyError; +use lemmy_utils::{settings::structs::Settings, LemmyError}; use reqwest::Client; use serde::Serialize; @@ -21,6 +21,7 @@ pub struct LemmyContext { pub chat_server: Addr, pub client: Client, pub activity_queue: QueueHandle, + pub settings: Settings, pub secret: Secret, } @@ -30,6 +31,7 @@ impl LemmyContext { chat_server: Addr, client: Client, activity_queue: QueueHandle, + settings: Settings, secret: Secret, ) -> LemmyContext { LemmyContext { @@ -37,6 +39,7 @@ impl LemmyContext { chat_server, client, activity_queue, + settings, secret, } } @@ -52,6 +55,9 @@ impl LemmyContext { pub fn activity_queue(&self) -> &QueueHandle { &self.activity_queue } + pub fn settings(&self) -> &Settings { + &self.settings + } pub fn secret(&self) -> &Secret { &self.secret } @@ -64,6 +70,7 @@ impl Clone for LemmyContext { chat_server: self.chat_server.clone(), client: self.client.clone(), activity_queue: self.activity_queue.clone(), + settings: self.settings.clone(), secret: self.secret.clone(), } } diff --git a/src/code_migrations.rs b/src/code_migrations.rs index cebf12936..1f9381986 100644 --- a/src/code_migrations.rs +++ b/src/code_migrations.rs @@ -24,22 +24,28 @@ use lemmy_db_schema::{ private_message::PrivateMessage, }, }; -use lemmy_utils::{apub::generate_actor_keypair, settings::structs::Settings, LemmyError}; +use lemmy_utils::{apub::generate_actor_keypair, LemmyError}; use log::info; -pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), LemmyError> { - user_updates_2020_04_02(conn)?; - community_updates_2020_04_02(conn)?; - post_updates_2020_04_03(conn)?; - comment_updates_2020_04_03(conn)?; - private_message_updates_2020_05_05(conn)?; - post_thumbnail_url_updates_2020_07_27(conn)?; +pub fn run_advanced_migrations( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { + user_updates_2020_04_02(conn, protocol_and_hostname)?; + community_updates_2020_04_02(conn, protocol_and_hostname)?; + post_updates_2020_04_03(conn, protocol_and_hostname)?; + comment_updates_2020_04_03(conn, protocol_and_hostname)?; + private_message_updates_2020_05_05(conn, protocol_and_hostname)?; + post_thumbnail_url_updates_2020_07_27(conn, protocol_and_hostname)?; apub_columns_2021_02_02(conn)?; Ok(()) } -fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { +fn user_updates_2020_04_02( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { use lemmy_db_schema::schema::person::dsl::*; info!("Running user_updates_2020_04_02"); @@ -55,7 +61,11 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { let form = PersonForm { name: cperson.name.to_owned(), - actor_id: Some(generate_apub_endpoint(EndpointType::Person, &cperson.name)?), + actor_id: Some(generate_apub_endpoint( + EndpointType::Person, + &cperson.name, + protocol_and_hostname, + )?), private_key: Some(Some(keypair.private_key)), public_key: Some(Some(keypair.public_key)), last_refreshed_at: Some(naive_now()), @@ -70,7 +80,10 @@ fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { Ok(()) } -fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { +fn community_updates_2020_04_02( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { use lemmy_db_schema::schema::community::dsl::*; info!("Running community_updates_2020_04_02"); @@ -83,7 +96,11 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { for ccommunity in &incorrect_communities { let keypair = generate_actor_keypair()?; - let community_actor_id = generate_apub_endpoint(EndpointType::Community, &ccommunity.name)?; + let community_actor_id = generate_apub_endpoint( + EndpointType::Community, + &ccommunity.name, + protocol_and_hostname, + )?; let form = CommunityForm { name: ccommunity.name.to_owned(), @@ -114,7 +131,10 @@ fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), LemmyError> { Ok(()) } -fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { +fn post_updates_2020_04_03( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { use lemmy_db_schema::schema::post::dsl::*; info!("Running post_updates_2020_04_03"); @@ -126,7 +146,11 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { .load::(conn)?; for cpost in &incorrect_posts { - let apub_id = generate_apub_endpoint(EndpointType::Post, &cpost.id.to_string())?; + let apub_id = generate_apub_endpoint( + EndpointType::Post, + &cpost.id.to_string(), + protocol_and_hostname, + )?; Post::update_ap_id(conn, cpost.id, apub_id)?; } @@ -135,7 +159,10 @@ fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { Ok(()) } -fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { +fn comment_updates_2020_04_03( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { use lemmy_db_schema::schema::comment::dsl::*; info!("Running comment_updates_2020_04_03"); @@ -147,7 +174,11 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { .load::(conn)?; for ccomment in &incorrect_comments { - let apub_id = generate_apub_endpoint(EndpointType::Comment, &ccomment.id.to_string())?; + let apub_id = generate_apub_endpoint( + EndpointType::Comment, + &ccomment.id.to_string(), + protocol_and_hostname, + )?; Comment::update_ap_id(conn, ccomment.id, apub_id)?; } @@ -156,7 +187,10 @@ fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), LemmyError> { Ok(()) } -fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyError> { +fn private_message_updates_2020_05_05( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { use lemmy_db_schema::schema::private_message::dsl::*; info!("Running private_message_updates_2020_05_05"); @@ -168,7 +202,11 @@ fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyEr .load::(conn)?; for cpm in &incorrect_pms { - let apub_id = generate_apub_endpoint(EndpointType::PrivateMessage, &cpm.id.to_string())?; + let apub_id = generate_apub_endpoint( + EndpointType::PrivateMessage, + &cpm.id.to_string(), + protocol_and_hostname, + )?; PrivateMessage::update_ap_id(conn, cpm.id, apub_id)?; } @@ -177,15 +215,15 @@ fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), LemmyEr Ok(()) } -fn post_thumbnail_url_updates_2020_07_27(conn: &PgConnection) -> Result<(), LemmyError> { +fn post_thumbnail_url_updates_2020_07_27( + conn: &PgConnection, + protocol_and_hostname: &str, +) -> Result<(), LemmyError> { use lemmy_db_schema::schema::post::dsl::*; info!("Running post_thumbnail_url_updates_2020_07_27"); - let domain_prefix = format!( - "{}/pictrs/image/", - Settings::get().get_protocol_and_hostname(), - ); + let domain_prefix = format!("{}/pictrs/image/", protocol_and_hostname,); let incorrect_thumbnails = post.filter(thumbnail_url.not_like("http%")); diff --git a/src/main.rs b/src/main.rs index 2d8426a65..e92e2d956 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ embed_migrations!(); #[actix_web::main] async fn main() -> Result<(), LemmyError> { env_logger::init(); - let settings = Settings::get(); + let settings = Settings::init().expect("Couldn't initialize settings."); // Set up the r2d2 connection pool let db_url = match get_database_url_from_env() { @@ -45,14 +45,13 @@ async fn main() -> Result<(), LemmyError> { // Initialize the secrets let conn = pool.get()?; - let secret = Secret::init(&conn).expect("Couldn't initialize secrets"); - - // TODO init settings + let secret = Secret::init(&conn).expect("Couldn't initialize secrets."); // Run the migrations from code + let protocol_and_hostname = settings.get_protocol_and_hostname(); blocking(&pool, move |conn| { embedded_migrations::run(conn)?; - run_advanced_migrations(conn)?; + run_advanced_migrations(conn, &protocol_and_hostname)?; Ok(()) as Result<(), LemmyError> }) .await??; @@ -65,6 +64,7 @@ async fn main() -> Result<(), LemmyError> { // Set up the rate limiter let rate_limiter = RateLimit { rate_limiter: Arc::new(Mutex::new(RateLimiter::default())), + rate_limit_config: settings.rate_limit.to_owned().unwrap_or_default(), }; println!( @@ -81,17 +81,20 @@ async fn main() -> Result<(), LemmyError> { |c, i, o, d| Box::pin(match_websocket_operation_crud(c, i, o, d)), Client::default(), activity_queue.clone(), + settings.clone(), secret.clone(), ) .start(); // Create Http server with websocket support + let settings_bind = settings.clone(); HttpServer::new(move || { let context = LemmyContext::create( pool.clone(), chat_server.to_owned(), Client::default(), activity_queue.to_owned(), + settings.to_owned(), secret.to_owned(), ); let rate_limiter = rate_limiter.clone(); @@ -100,13 +103,13 @@ async fn main() -> Result<(), LemmyError> { .app_data(Data::new(context)) // The routes .configure(|cfg| api_routes::config(cfg, &rate_limiter)) - .configure(lemmy_apub::http::routes::config) + .configure(|cfg| lemmy_apub::http::routes::config(cfg, &settings)) .configure(feeds::config) .configure(|cfg| images::config(cfg, &rate_limiter)) .configure(nodeinfo::config) - .configure(webfinger::config) + .configure(|cfg| webfinger::config(cfg, &settings)) }) - .bind((settings.bind, settings.port))? + .bind((settings_bind.bind, settings_bind.port))? .run() .await?;