diff --git a/api_tests/src/user.spec.ts b/api_tests/src/user.spec.ts index f7f80aecb..d1d6144f5 100644 --- a/api_tests/src/user.spec.ts +++ b/api_tests/src/user.spec.ts @@ -74,6 +74,9 @@ test("Set some user settings, check that they are federated", async () => { test("Delete user", async () => { let user = await registerUser(alpha, alphaUrl); + let user_profile = await getMyUser(user); + let person_id = user_profile.local_user_view.person.id; + let actor_id = user_profile.local_user_view.person.actor_id; // make a local post and comment let alphaCommunity = (await resolveCommunity(user, "main@lemmy-alpha:8541")) @@ -101,6 +104,10 @@ test("Delete user", async () => { expect(remoteComment).toBeDefined(); await deleteUser(user); + await expect(getMyUser(user)).rejects.toStrictEqual(Error("incorrect_login")); + await expect(getPersonDetails(user, person_id)).rejects.toStrictEqual( + Error("not_found"), + ); // check that posts and comments are marked as deleted on other instances. // use get methods to avoid refetching from origin instance @@ -118,6 +125,9 @@ test("Delete user", async () => { (await getComments(alpha, remoteComment.post_id)).comments[0].comment .deleted, ).toBe(true); + await expect( + getPersonDetails(user, remoteComment.creator_id), + ).rejects.toStrictEqual(Error("not_found")); }); test("Requests with invalid auth should be treated as unauthenticated", async () => { diff --git a/config/defaults.hjson b/config/defaults.hjson index 21a76d7a5..9e24407cd 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -1,11 +1,7 @@ { # settings related to the postgresql database database: { - # Configure the database by specifying a URI - # - # This is the preferred method to specify database connection details since - # it is the most flexible. - # Connection URI pointing to a postgres instance + # Configure the database by specifying URI pointing to a postgres instance # # This example uses peer authentication to obviate the need for creating, # configuring, and managing passwords. @@ -14,25 +10,7 @@ # PostgreSQL's documentation. # # [0]: https://www.postgresql.org/docs/current/libpq-connect.html#id-1.7.3.8.3.6 - uri: "postgresql:///lemmy?user=lemmy&host=/var/run/postgresql" - - # or - - # Configure the database by specifying parts of a URI - # - # Note that specifying the `uri` field should be preferred since it provides - # greater control over how the connection is made. This merely exists for - # backwards-compatibility. - # Username to connect to postgres - user: "string" - # Password to connect to postgres - password: "string" - # Host where postgres is running - host: "string" - # Port where postgres can be accessed - port: 123 - # Name of the postgres database for lemmy - database: "string" + connection: "postgres://lemmy:password@localhost:5432/lemmy" # Maximum number of active sql connections pool_size: 30 } diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 8689d2563..547838fa7 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -110,7 +110,7 @@ pub async fn ban_from_community( ModBanFromCommunity::create(&mut context.pool(), &form).await?; - let person_view = PersonView::read(&mut context.pool(), data.person_id).await?; + let person_view = PersonView::read(&mut context.pool(), data.person_id, false).await?; ActivityChannel::submit_activity( SendActivityData::BanFromCommunity { diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index f929433f0..715bd206d 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -88,7 +88,7 @@ pub async fn ban_from_site( ModBan::create(&mut context.pool(), &form).await?; - let person_view = PersonView::read(&mut context.pool(), person.id).await?; + let person_view = PersonView::read(&mut context.pool(), person.id, false).await?; ban_nonlocal_user_from_local_communities( &local_user_view, diff --git a/crates/api/src/local_user/block.rs b/crates/api/src/local_user/block.rs index 80532e897..3aee554d4 100644 --- a/crates/api/src/local_user/block.rs +++ b/crates/api/src/local_user/block.rs @@ -48,7 +48,7 @@ pub async fn user_block_person( .with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?; } - let person_view = PersonView::read(&mut context.pool(), target_id).await?; + let person_view = PersonView::read(&mut context.pool(), target_id, false).await?; Ok(Json(BlockPersonResponse { person_view, blocked: data.block, diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index 7b3ed820e..99bf7aecc 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -123,8 +123,6 @@ pub fn is_admin(local_user_view: &LocalUserView) -> LemmyResult<()> { check_user_valid(&local_user_view.person)?; if !local_user_view.local_user.admin { Err(LemmyErrorType::NotAnAdmin)? - } else if local_user_view.person.banned { - Err(LemmyErrorType::Banned)? } else { Ok(()) } diff --git a/crates/api_crud/src/user/my_user.rs b/crates/api_crud/src/user/my_user.rs index 805c9dabb..f7a92eb99 100644 --- a/crates/api_crud/src/user/my_user.rs +++ b/crates/api_crud/src/user/my_user.rs @@ -1,5 +1,5 @@ use actix_web::web::{Data, Json}; -use lemmy_api_common::{context::LemmyContext, site::MyUserInfo}; +use lemmy_api_common::{context::LemmyContext, site::MyUserInfo, utils::check_user_valid}; use lemmy_db_schema::source::{ actor_language::LocalUserLanguage, community_block::CommunityBlock, @@ -15,6 +15,8 @@ pub async fn get_my_user( local_user_view: LocalUserView, context: Data, ) -> LemmyResult> { + check_user_valid(&local_user_view.person)?; + // Build the local user with parallel queries and add it to site response let person_id = local_user_view.person.id; let local_user_id = local_user_view.local_user.id; diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index fdcb6ba58..7f719fe71 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -4,7 +4,7 @@ use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, person::{GetPersonDetails, GetPersonDetailsResponse}, - utils::{check_private_instance, read_site_for_actor}, + utils::{check_private_instance, is_admin, read_site_for_actor}, }; use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView}; @@ -30,7 +30,12 @@ pub async fn read_person( // You don't need to return settings for the user, since this comes back with GetSite // `my_user` - let person_view = PersonView::read(&mut context.pool(), person_details_id).await?; + let is_admin = local_user_view + .as_ref() + .map(|l| is_admin(l).is_ok()) + .unwrap_or_default(); + let person_view = PersonView::read(&mut context.pool(), person_details_id, is_admin).await?; + let moderates = CommunityModeratorView::for_person( &mut context.pool(), person_details_id, diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index 04d489592..8d2cd384f 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -60,7 +60,7 @@ async fn convert_response( } }, SearchableObjects::PersonOrCommunity(pc) => match *pc { - UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id).await?), + UserOrCommunity::User(u) => res.person = Some(PersonView::read(pool, u.id, is_admin).await?), UserOrCommunity::Community(c) => { res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?) } diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index f6ce82d37..8bcf50ba3 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -188,7 +188,7 @@ impl CommunityView { let is_mod = CommunityModeratorView::check_is_community_moderator(pool, community_id, person_id).await; if is_mod.is_ok() - || PersonView::read(pool, person_id) + || PersonView::read(pool, person_id, false) .await .is_ok_and(|t| t.is_admin) { @@ -206,7 +206,7 @@ impl CommunityView { let is_mod_of_any = CommunityModeratorView::is_community_moderator_of_any(pool, person_id).await; if is_mod_of_any.is_ok() - || PersonView::read(pool, person_id) + || PersonView::read(pool, person_id, false) .await .is_ok_and(|t| t.is_admin) { diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 39d1ac27c..b90ab7811 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -58,12 +58,11 @@ fn post_to_person_sort_type(sort: PostSortType) -> PersonSortType { } fn queries<'a>( -) -> Queries, impl ListFn<'a, PersonView, ListMode>> { +) -> Queries, impl ListFn<'a, PersonView, ListMode>> { let all_joins = move |query: person::BoxedQuery<'a, Pg>| { query .inner_join(person_aggregates::table) .left_join(local_user::table) - .filter(person::deleted.eq(false)) .select(( person::all_columns, person_aggregates::all_columns, @@ -71,14 +70,17 @@ fn queries<'a>( )) }; - let read = move |mut conn: DbConn<'a>, person_id: PersonId| async move { - all_joins(person::table.find(person_id).into_boxed()) - .first(&mut conn) - .await + let read = move |mut conn: DbConn<'a>, params: (PersonId, bool)| async move { + let (person_id, is_admin) = params; + let mut query = all_joins(person::table.find(person_id).into_boxed()); + if !is_admin { + query = query.filter(person::deleted.eq(false)); + } + query.first(&mut conn).await }; let list = move |mut conn: DbConn<'a>, mode: ListMode| async move { - let mut query = all_joins(person::table.into_boxed()); + let mut query = all_joins(person::table.into_boxed()).filter(person::deleted.eq(false)); match mode { ListMode::Admins => { query = query @@ -135,8 +137,12 @@ fn queries<'a>( } impl PersonView { - pub async fn read(pool: &mut DbPool<'_>, person_id: PersonId) -> Result { - queries().read(pool, person_id).await + pub async fn read( + pool: &mut DbPool<'_>, + person_id: PersonId, + is_admin: bool, + ) -> Result { + queries().read(pool, (person_id, is_admin)).await } pub async fn admins(pool: &mut DbPool<'_>) -> Result, Error> { @@ -243,9 +249,13 @@ mod tests { ) .await?; - let read = PersonView::read(pool, data.alice.id).await; + let read = PersonView::read(pool, data.alice.id, false).await; assert!(read.is_err()); + // only admin can view deleted users + let read = PersonView::read(pool, data.alice.id, true).await; + assert!(read.is_ok()); + let list = PersonQuery { sort: Some(PostSortType::New), ..Default::default() @@ -303,10 +313,10 @@ mod tests { assert_length!(1, list); assert_eq!(list[0].person.id, data.alice.id); - let is_admin = PersonView::read(pool, data.alice.id).await?.is_admin; + let is_admin = PersonView::read(pool, data.alice.id, false).await?.is_admin; assert!(is_admin); - let is_admin = PersonView::read(pool, data.bob.id).await?.is_admin; + let is_admin = PersonView::read(pool, data.bob.id, false).await?.is_admin; assert!(!is_admin); cleanup(data, pool).await diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index d9db32428..7e4bb59db 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -113,7 +113,6 @@ pub enum LemmyErrorType { SystemErrLogin, CouldntSetAllRegistrationsAccepted, CouldntSetAllEmailVerified, - Banned, BlockedUrl, CouldntGetComments, CouldntGetPosts, @@ -328,9 +327,9 @@ cfg_if! { #[test] fn deserializes_no_message() -> LemmyResult<()> { - let err = LemmyError::from(LemmyErrorType::Banned).error_response(); + let err = LemmyError::from(LemmyErrorType::BlockedUrl).error_response(); let json = String::from_utf8(err.into_body().try_into_bytes().unwrap_or_default().to_vec())?; - assert_eq!(&json, "{\"error\":\"banned\"}"); + assert_eq!(&json, "{\"error\":\"blocked_url\"}"); Ok(()) } diff --git a/crates/utils/src/settings/mod.rs b/crates/utils/src/settings/mod.rs index d6f11c09b..72d986d2d 100644 --- a/crates/utils/src/settings/mod.rs +++ b/crates/utils/src/settings/mod.rs @@ -3,13 +3,11 @@ use anyhow::{anyhow, Context}; use deser_hjson::from_str; use regex::Regex; use std::{env, fs, io::Error, sync::LazyLock}; +use structs::{PictrsConfig, PictrsImageMode, Settings}; use url::Url; -use urlencoding::encode; pub mod structs; -use structs::{DatabaseConnection, PictrsConfig, PictrsImageMode, Settings}; - const DEFAULT_CONFIG_FILE: &str = "config/config.hjson"; #[allow(clippy::expect_used)] @@ -51,20 +49,9 @@ impl Settings { pub fn get_database_url(&self) -> String { if let Ok(url) = env::var("LEMMY_DATABASE_URL") { - return url; - } - match &self.database.connection { - DatabaseConnection::Uri { uri } => uri.clone(), - DatabaseConnection::Parts(parts) => { - format!( - "postgres://{}:{}@{}:{}/{}", - encode(&parts.user), - encode(&parts.password), - parts.host, - parts.port, - encode(&parts.database), - ) - } + url + } else { + self.database.connection.clone() } } diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index cbbbcbfe5..1aef9f79b 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -132,64 +132,24 @@ pub enum PictrsImageMode { #[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] #[serde(default)] pub struct DatabaseConfig { - #[serde(flatten, default)] - pub(crate) connection: DatabaseConnection, + /// Configure the database by specifying URI pointing to a postgres instance + /// + /// This example uses peer authentication to obviate the need for creating, + /// configuring, and managing passwords. + /// + /// For an explanation of how to use connection URIs, see [here][0] in + /// PostgreSQL's documentation. + /// + /// [0]: https://www.postgresql.org/docs/current/libpq-connect.html#id-1.7.3.8.3.6 + #[default("postgres://lemmy:password@localhost:5432/lemmy")] + #[doku(example = "postgresql:///lemmy?user=lemmy&host=/var/run/postgresql")] + pub(crate) connection: String, /// Maximum number of active sql connections #[default(30)] pub pool_size: usize, } -#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] -#[serde(untagged)] -pub enum DatabaseConnection { - /// Configure the database by specifying a URI - /// - /// This is the preferred method to specify database connection details since - /// it is the most flexible. - Uri { - /// Connection URI pointing to a postgres instance - /// - /// This example uses peer authentication to obviate the need for creating, - /// configuring, and managing passwords. - /// - /// For an explanation of how to use connection URIs, see [here][0] in - /// PostgreSQL's documentation. - /// - /// [0]: https://www.postgresql.org/docs/current/libpq-connect.html#id-1.7.3.8.3.6 - #[doku(example = "postgresql:///lemmy?user=lemmy&host=/var/run/postgresql")] - uri: String, - }, - - /// Configure the database by specifying parts of a URI - /// - /// Note that specifying the `uri` field should be preferred since it provides - /// greater control over how the connection is made. This merely exists for - /// backwards-compatibility. - #[default] - Parts(DatabaseConnectionParts), -} - -#[derive(Debug, Deserialize, Serialize, Clone, SmartDefault, Document)] -#[serde(default)] -pub struct DatabaseConnectionParts { - /// Username to connect to postgres - #[default("lemmy")] - pub(super) user: String, - /// Password to connect to postgres - #[default("password")] - pub(super) password: String, - #[default("localhost")] - /// Host where postgres is running - pub(super) host: String, - /// Port where postgres can be accessed - #[default(5432)] - pub(super) port: i32, - /// Name of the postgres database for lemmy - #[default("lemmy")] - pub(super) database: String, -} - #[derive(Debug, Deserialize, Serialize, Clone, Document, SmartDefault)] #[serde(deny_unknown_fields)] pub struct EmailConfig { diff --git a/docker/federation/lemmy_alpha.hjson b/docker/federation/lemmy_alpha.hjson index 84ef1a16e..a3bf2bb21 100644 --- a/docker/federation/lemmy_alpha.hjson +++ b/docker/federation/lemmy_alpha.hjson @@ -8,7 +8,7 @@ site_name: lemmy-alpha } database: { - host: postgres_alpha + connection: "postgres://lemmy:password@postgres_alpha:5432/lemmy" } pictrs: { api_key: "my-pictrs-key" diff --git a/docker/federation/lemmy_beta.hjson b/docker/federation/lemmy_beta.hjson index 1b4508a43..c026b2f71 100644 --- a/docker/federation/lemmy_beta.hjson +++ b/docker/federation/lemmy_beta.hjson @@ -8,7 +8,7 @@ site_name: lemmy-beta } database: { - host: postgres_beta + connection: "postgres://lemmy:password@postgres_beta:5432/lemmy" } pictrs: { api_key: "my-pictrs-key" diff --git a/docker/federation/lemmy_delta.hjson b/docker/federation/lemmy_delta.hjson index d05e4121f..acfddc304 100644 --- a/docker/federation/lemmy_delta.hjson +++ b/docker/federation/lemmy_delta.hjson @@ -8,6 +8,6 @@ site_name: lemmy-delta } database: { - host: postgres_delta + connection: "postgres://lemmy:password@postgres_delta:5432/lemmy" } } diff --git a/docker/federation/lemmy_epsilon.hjson b/docker/federation/lemmy_epsilon.hjson index c24baa9f8..a607353a6 100644 --- a/docker/federation/lemmy_epsilon.hjson +++ b/docker/federation/lemmy_epsilon.hjson @@ -8,7 +8,7 @@ site_name: lemmy-epsilon } database: { - host: postgres_epsilon + connection: "postgres://lemmy:password@postgres_epsilon:5432/lemmy" } pictrs: { api_key: "my-pictrs-key" diff --git a/docker/federation/lemmy_gamma.hjson b/docker/federation/lemmy_gamma.hjson index d7e5b6065..7db9a5065 100644 --- a/docker/federation/lemmy_gamma.hjson +++ b/docker/federation/lemmy_gamma.hjson @@ -8,7 +8,7 @@ site_name: lemmy-gamma } database: { - host: postgres_gamma + connection: "postgres://lemmy:password@postgres_gamma:5432/lemmy" } pictrs: { api_key: "my-pictrs-key" diff --git a/docker/lemmy.hjson b/docker/lemmy.hjson index 83adf9c0c..e28a49b6d 100644 --- a/docker/lemmy.hjson +++ b/docker/lemmy.hjson @@ -11,7 +11,7 @@ site_name: "lemmy-dev" } database: { - host: postgres + connection: "postgres://lemmy:password@postgres:5432/lemmy" } hostname: "localhost" diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index 52962877f..3406bf694 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -579,13 +579,13 @@ async fn build_update_instance_form( // This is the only kind of error that means the instance is dead return None; }; + let status = res.status(); + if status.is_client_error() || status.is_server_error() { + return None; + } // In this block, returning `None` is ignored, and only means not writing nodeinfo to db async { - if res.status().is_client_error() { - return None; - } - let node_info_url = res .json::() .await