Moving Settings to context

This commit is contained in:
Dessalines 2021-09-22 16:27:07 -04:00
parent 37e64684ce
commit 789b8e11c1
74 changed files with 858 additions and 392 deletions

View file

@ -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());

View file

@ -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<LemmyContext>,
_websocket_id: Option<ConnectionId>,
) -> Result<Self::Response, LemmyError> {
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,9 +750,15 @@ 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!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", local_user_view.person.name, hostname, &token);
send_email(subject, email, &local_user_view.person.name, html)
let protocol_and_hostname = &context.settings().get_protocol_and_hostname();
let html = &format!("<h1>Password Reset Request for {}</h1><br><a href={}/password_change/{}>Click here to reset your password</a>", 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,
)?,
})
}
}

View file

@ -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),

View file

@ -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<Vec<LocalUserId>, 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<LocalUserId> {
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::<Vec<&MentionData>>()
{
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!(
"<h1>{}</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
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<Option<FederatedInstances>, 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

View file

@ -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<Comment, LemmyError> {
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?;

View file

@ -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?;

View file

@ -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;

View file

@ -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?;

View file

@ -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)

View file

@ -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)

View file

@ -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;

View file

@ -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<Post, LemmyError> {
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?

View file

@ -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;

View file

@ -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));

View file

@ -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<PrivateMessage, LemmyError> {
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(),
);
}

View file

@ -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)

View file

@ -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)?;

View file

@ -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,

View file

@ -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)?;

View file

@ -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,
)?,
})
}
}

View file

@ -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)

View file

@ -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

View file

@ -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::<Vec<MentionData>>();
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<Person> = 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<Url, LemmyError> {
async fn fetch_webfinger_url(
mention: &MentionData,
client: &Client,
protocol_string: &str,
) -> Result<Url, LemmyError> {
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);

View file

@ -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])?;

View file

@ -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(())

View file

@ -56,6 +56,7 @@ impl BlockUserFromCommunity {
community: &Community,
target: &Person,
actor: &Person,
context: &LemmyContext,
) -> Result<BlockUserFromCommunity, LemmyError> {
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(())

View file

@ -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<Vec<Url>, 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(),
)

View file

@ -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?;

View file

@ -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?;

View file

@ -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,

View file

@ -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<String>,
context: &LemmyContext,
) -> Result<Delete, LemmyError> {
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<String>,
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);

View file

@ -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<String>,
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],

View file

@ -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?;

View file

@ -48,13 +48,17 @@ impl FollowCommunity {
pub(in crate::activities::following) fn new(
actor: &Person,
community: &Community,
context: &LemmyContext,
) -> Result<FollowCommunity, LemmyError> {
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(())

View file

@ -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?;

View file

@ -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<T>(kind: T) -> Result<Url, ParseError>
fn generate_activity_id<T>(kind: T, protocol_and_hostname: &str) -> Result<Url, ParseError>
where
T: ToString,
{
let id = format!(
"{}/activities/{}/{}",
Settings::get().get_protocol_and_hostname(),
protocol_and_hostname,
kind.to_string().to_lowercase(),
Uuid::new_v4()
);

View file

@ -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 {

View file

@ -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?;

View file

@ -39,13 +39,17 @@ impl DeletePrivateMessage {
pub(in crate::activities::private_message) fn new(
actor: &Person,
pm: &PrivateMessage,
context: &LemmyContext,
) -> Result<DeletePrivateMessage, LemmyError> {
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(())

View file

@ -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)?;

View file

@ -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<Vec<Url>, LemmyError> {
async fn get_follower_inboxes(
&self,
pool: &DbPool,
settings: &Settings,
) -> Result<Vec<Url>, 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)

View file

@ -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(

View file

@ -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?;

View file

@ -77,6 +77,7 @@ impl Vote {
actor: &Person,
community: &Community,
kind: VoteType,
context: &LemmyContext,
) -> Result<Vote, LemmyError> {
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(())
}

View file

@ -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<T>(
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)

View file

@ -73,8 +73,13 @@ pub(crate) async fn fetch_community_outbox(
outbox: &Url,
recursion_counter: &mut i32,
) -> Result<(), LemmyError> {
let outbox =
fetch_remote_object::<OrderedCollection>(context.client(), outbox, recursion_counter).await?;
let outbox = fetch_remote_object::<OrderedCollection>(
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,8 +103,12 @@ async fn fetch_community_mods(
recursion_counter: &mut i32,
) -> Result<Vec<Url>, LemmyError> {
if let Some(mods_url) = &group.moderators {
let mods =
fetch_remote_object::<OrderedCollection>(context.client(), mods_url, recursion_counter)
let mods = fetch_remote_object::<OrderedCollection>(
context.client(),
context.settings(),
mods_url,
recursion_counter,
)
.await?;
let mods = mods
.items()

View file

@ -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<Response>(
client: &Client,
settings: &Settings,
url: &Url,
recursion_counter: &mut i32,
) -> Result<Response, LemmyError>
@ -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);

View file

@ -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<Post, LemmyError> {
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::<Page>(
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<Comment, LemmyError> {
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::<Note>(
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<PostOrComment, LemmyError> {
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))
}
},
)
}

View file

@ -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<Person, LemmyError> {
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::<ApubPerson>(
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::<ApubPerson>(
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()),
}
}

View file

@ -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 {

View file

@ -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<ActivityQuery>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, 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<T: Debug + ActivityFields>(activity: &T) -> Result<(), LemmyError> {
fn assert_activity_not_local<T: Debug + ActivityFields>(
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: {:?}",

View file

@ -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))

View file

@ -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<Vec<Url>, LemmyError>;
async fn get_follower_inboxes(
&self,
pool: &DbPool,
settings: &Settings,
) -> Result<Vec<Url>, 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<DbUrl, ParseError> {
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<DbUrl, ParseError> {
@ -200,6 +201,7 @@ fn generate_moderators_url(community_id: &DbUrl) -> Result<DbUrl, LemmyError> {
pub fn build_actor_id_from_shortname(
endpoint_type: EndpointType,
short_name: &str,
settings: &Settings,
) -> Result<DbUrl, ParseError> {
let split = short_name.split('@').collect::<Vec<&str>>();
@ -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)

View file

@ -222,7 +222,7 @@ impl FromApub for Comment {
}
let content = &note.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,

View file

@ -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<CommunityForm, LemmyError> {
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<Community, LemmyError> {
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?;

View file

@ -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,

View file

@ -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<Url> = 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()),

View file

@ -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<Url, LemmyError> {
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);

View file

@ -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, &param, &protocol_and_hostname),
RequestType::Community => get_feed_community(conn, &sort_type, &param, &protocol_and_hostname),
RequestType::Front => get_feed_front(
conn,
&jwt_secret,
&sort_type,
&param,
&protocol_and_hostname,
),
RequestType::Inbox => get_feed_inbox(conn, &jwt_secret, &param, &protocol_and_hostname),
})
.await?
.map_err(ErrorBadRequest)?;
@ -172,10 +174,11 @@ fn get_sort_type(info: web::Query<Params>) -> Result<SortType, ParseError> {
fn get_feed_user(
conn: &PgConnection,
sort_type: &SortType,
user_name: String,
user_name: &str,
protocol_and_hostname: &str,
) -> Result<ChannelBuilder, LemmyError> {
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<ChannelBuilder, LemmyError> {
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<ChannelBuilder, LemmyError> {
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<ChannelBuilder, LemmyError> {
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<CommentView>,
mentions: Vec<PersonMentionView>,
protocol_and_hostname: &str,
) -> Result<Vec<Item>, LemmyError> {
let mut reply_items: Vec<Item> = 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::<Result<Vec<Item>, 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::<Result<Vec<Item>, LemmyError>>()?;
@ -353,14 +355,11 @@ fn build_item(
published: &NaiveDateTime,
url: &str,
content: &str,
protocol_and_hostname: &str,
) -> Result<Item, LemmyError> {
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/{} <a href=\"{}\">(link)</a>",
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<PostView>) -> Result<Vec<Item>, LemmyError> {
fn create_post_items(
posts: Vec<PostView>,
protocol_and_hostname: &str,
) -> Result<Vec<Item>, LemmyError> {
let mut items: Vec<Item> = Vec::new();
for p in posts {
@ -395,11 +397,7 @@ fn create_post_items(posts: Vec<PostView>) -> Result<Vec<Item>, LemmyError> {
let dt = DateTime::<Utc>::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<PostView>) -> Result<Vec<Item>, 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 <a href=\"{}\">{}</a> to <a href=\"{}\">{}</a><br>{} points | <a href=\"{}\">{} comments</a>",

View file

@ -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<PictrsParams>,
req: HttpRequest,
client: web::Data<Client>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
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<Client>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
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<String, LemmyError> {
Settings::get()
.pictrs_url
.ok_or_else(|| anyhow!("images_disabled").into())
fn pictrs_url(pictrs_url: Option<String>) -> Result<String, LemmyError> {
pictrs_url.ok_or_else(|| anyhow!("images_disabled").into())
}

View file

@ -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<HttpResponse<Body>, LemmyError> {
async fn node_info_well_known(
context: web::Data<LemmyContext>,
) -> Result<HttpResponse<Body>, 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<LemmyContext>) -> Result<HttpResponse, Err
.await?
.map_err(|_| ErrorBadRequest(LemmyError::from(anyhow!("not_found"))))?;
let protocols = if Settings::get().federation.enabled {
let protocols = if context.settings().federation.enabled {
vec!["activitypub".to_string()]
} else {
vec![]

View file

@ -4,12 +4,7 @@ use lemmy_api_common::blocking;
use lemmy_apub_lib::webfinger::{WebfingerLink, WebfingerResponse};
use lemmy_db_queries::source::{community::Community_, person::Person_};
use lemmy_db_schema::source::{community::Community, person::Person};
use lemmy_utils::{
settings::structs::Settings,
LemmyError,
WEBFINGER_COMMUNITY_REGEX,
WEBFINGER_USERNAME_REGEX,
};
use lemmy_utils::{settings::structs::Settings, LemmyError};
use lemmy_websocket::LemmyContext;
use serde::Deserialize;
@ -18,8 +13,8 @@ struct Params {
resource: String,
}
pub fn config(cfg: &mut web::ServiceConfig) {
if Settings::get().federation.enabled {
pub fn config(cfg: &mut web::ServiceConfig, settings: &Settings) {
if settings.federation.enabled {
cfg.route(
".well-known/webfinger",
web::get().to(get_webfinger_response),
@ -37,12 +32,16 @@ async fn get_webfinger_response(
info: Query<Params>,
context: web::Data<LemmyContext>,
) -> Result<HttpResponse, Error> {
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();

View file

@ -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::<Claims>(jwt, &key, &v)?)
}
pub fn jwt(local_user_id: i32, jwt_secret: &str) -> Result<Jwt, LemmyError> {
pub fn jwt(local_user_id: i32, jwt_secret: &str, hostname: &str) -> Result<Jwt, LemmyError> {
let my_claims = Claims {
sub: local_user_id,
iss: Settings::get().hostname,
iss: hostname.to_string(),
iat: Utc::now().timestamp(),
};

View file

@ -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::<Vec<&str>>();

View file

@ -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");
}

View file

@ -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<Mutex<RateLimiter>>,
pub rate_limit_config: RateLimitConfig,
}
#[derive(Debug, Clone)]
pub struct RateLimited {
rate_limiter: Arc<Mutex<RateLimiter>>,
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
{

View file

@ -120,9 +120,10 @@ pub(crate) struct PictrsFile {
pub(crate) async fn fetch_pictrs(
client: &Client,
settings: &Settings,
image_url: &Url,
) -> Result<PictrsResponse, LemmyError> {
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<SiteMetadata>, Option<Url>) {
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()

View file

@ -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<Settings> =
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<Self, LemmyError> {
/// Warning: Only call this once.
pub fn init() -> Result<Self, LemmyError> {
// Read the config file
let config = from_str::<Settings>(&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<String, LemmyError> {
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")
}
}

View file

@ -1,4 +1,6 @@
use crate::utils::{
use crate::{
settings::structs::Settings,
utils::{
is_valid_actor_name,
is_valid_display_name,
is_valid_matrix_id,
@ -7,6 +9,7 @@ use crate::utils::{
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);
}
}

View file

@ -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<FixedOffset> {
DateTime::<FixedOffset>::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<String>) -> Result<(), ApiError> {
pub fn check_slurs_opt(text: &Option<String>, 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<MentionData> {
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 {

View file

@ -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<ConnectionManager<PgConnection>>,
/// 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<ConnectionManager<PgConnection>>,
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;

View file

@ -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<ChatServer>,
pub client: Client,
pub activity_queue: QueueHandle,
pub settings: Settings,
pub secret: Secret,
}
@ -30,6 +31,7 @@ impl LemmyContext {
chat_server: Addr<ChatServer>,
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(),
}
}

View file

@ -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::<Post>(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::<Comment>(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::<PrivateMessage>(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%"));

View file

@ -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?;