various changes

- only admin can edit main page
- adjust config values
- better text for default page
This commit is contained in:
Felix Ableitner 2024-02-08 12:20:01 +01:00
parent 0c45fe3eba
commit 0f0f83bc3a
15 changed files with 57 additions and 32 deletions

View File

@ -1,3 +1,3 @@
[setup] [federation]
admin_username = "ibis" # necessary for auth cookie to work
admin_password = "ibis" domain = "127.0.0.1:8080"

View File

@ -9,8 +9,8 @@ registration_open = true
# Details of the initial admin account # Details of the initial admin account
[setup] [setup]
admin_username = "admin" admin_username = "ibis"
admin_password = "hunter2" admin_password = "ibis"
[federation] [federation]
# Domain name of the instance, mandatory for federation # Domain name of the instance, mandatory for federation

View File

@ -6,12 +6,13 @@ use crate::backend::error::MyResult;
use crate::backend::federation::activities::create_article::CreateArticle; use crate::backend::federation::activities::create_article::CreateArticle;
use crate::backend::federation::activities::submit_article_update; use crate::backend::federation::activities::submit_article_update;
use crate::backend::utils::generate_article_version; use crate::backend::utils::generate_article_version;
use crate::common::validation::can_edit_article;
use crate::common::LocalUserView;
use crate::common::{ApiConflict, ResolveObject}; use crate::common::{ApiConflict, ResolveObject};
use crate::common::{ArticleView, DbArticle, DbEdit}; use crate::common::{ArticleView, DbArticle, DbEdit};
use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData}; use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData};
use crate::common::{DbInstance, SearchArticleData}; use crate::common::{DbInstance, SearchArticleData};
use crate::common::{GetArticleData, ListArticlesData}; use crate::common::{GetArticleData, ListArticlesData};
use crate::common::{LocalUserView, MAIN_PAGE_NAME};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::fetch::object_id::ObjectId;
use anyhow::anyhow; use anyhow::anyhow;
@ -91,12 +92,7 @@ pub(in crate::backend::api) async fn edit_article(
if edit_form.summary.is_empty() { if edit_form.summary.is_empty() {
return Err(anyhow!("No summary given").into()); return Err(anyhow!("No summary given").into());
} }
if original_article.article.local can_edit_article(&original_article.article, user.local_user.admin)?;
&& original_article.article.title == MAIN_PAGE_NAME
&& !user.local_user.admin
{
return Err(anyhow!("Only admin can edit main page").into());
}
// ensure trailing newline for clean diffs // ensure trailing newline for clean diffs
if !edit_form.new_text.ends_with('\n') { if !edit_form.new_text.ends_with('\n') {
edit_form.new_text.push('\n'); edit_form.new_text.push('\n');

View File

@ -26,9 +26,11 @@ pub struct IbisConfig {
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)] #[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)]
#[serde(default)] #[serde(default)]
pub struct IbisConfigSetup { pub struct IbisConfigSetup {
#[doku(example = "admin")] #[default("ibis")]
#[doku(example = "ibis")]
pub admin_username: String, pub admin_username: String,
#[doku(example = "hunter2")] #[default("ibis")]
#[doku(example = "ibis")]
pub admin_password: String, pub admin_password: String,
} }

View File

@ -1,6 +1,6 @@
use crate::backend::config::IbisConfig;
use crate::backend::database::schema::jwt_secret; use crate::backend::database::schema::jwt_secret;
use crate::backend::error::MyResult; use crate::backend::error::MyResult;
use crate::config::IbisConfig;
use diesel::PgConnection; use diesel::PgConnection;
use diesel::{QueryDsl, RunQueryDsl}; use diesel::{QueryDsl, RunQueryDsl};
use std::ops::Deref; use std::ops::Deref;

View File

@ -12,6 +12,7 @@ use activitypub_federation::{
traits::{ActivityHandler, Object}, traits::{ActivityHandler, Object},
}; };
use crate::common::validation::can_edit_article;
use crate::common::DbArticle; use crate::common::DbArticle;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
@ -67,7 +68,9 @@ impl ActivityHandler for UpdateLocalArticle {
self.actor.inner() self.actor.inner()
} }
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
let article = DbArticle::read_from_ap_id(&self.object.id, &data.db_connection)?;
can_edit_article(&article, false)?;
Ok(()) Ok(())
} }

View File

@ -1,5 +1,5 @@
use crate::backend::config::IbisConfig;
use crate::backend::database::IbisData; use crate::backend::database::IbisData;
use crate::config::IbisConfig;
use activitypub_federation::activity_sending::SendActivityTask; use activitypub_federation::activity_sending::SendActivityTask;
use activitypub_federation::config::{Data, UrlVerifier}; use activitypub_federation::config::{Data, UrlVerifier};
use activitypub_federation::error::Error as ActivityPubError; use activitypub_federation::error::Error as ActivityPubError;

View File

@ -1,3 +1,4 @@
use crate::backend::config::IbisConfig;
use crate::backend::database::article::DbArticleForm; use crate::backend::database::article::DbArticleForm;
use crate::backend::database::instance::DbInstanceForm; use crate::backend::database::instance::DbInstanceForm;
use crate::backend::database::IbisData; use crate::backend::database::IbisData;
@ -7,7 +8,6 @@ use crate::backend::federation::routes::federation_routes;
use crate::backend::federation::VerifyUrlData; use crate::backend::federation::VerifyUrlData;
use crate::backend::utils::generate_activity_id; use crate::backend::utils::generate_activity_id;
use crate::common::{DbArticle, DbInstance, DbPerson, MAIN_PAGE_NAME}; use crate::common::{DbArticle, DbInstance, DbPerson, MAIN_PAGE_NAME};
use crate::config::IbisConfig;
use crate::frontend::app::App; use crate::frontend::app::App;
use activitypub_federation::config::{FederationConfig, FederationMiddleware}; use activitypub_federation::config::{FederationConfig, FederationMiddleware};
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
@ -33,6 +33,7 @@ use tower_http::cors::CorsLayer;
use tower_http::services::{ServeDir, ServeFile}; use tower_http::services::{ServeDir, ServeFile};
pub mod api; pub mod api;
pub mod config;
pub mod database; pub mod database;
pub mod error; pub mod error;
pub mod federation; pub mod federation;
@ -103,6 +104,11 @@ pub async fn start(config: IbisConfig) -> MyResult<()> {
Ok(()) Ok(())
} }
const MAIN_PAGE_DEFAULT_TEXT: &str = "Welcome to Ibis, the federated Wikipedia alternative!
This main page can only be edited by the admin. Use it as an introduction for new users, \
and to list interesting articles.";
fn setup(data: &IbisData) -> Result<(), Error> { fn setup(data: &IbisData) -> Result<(), Error> {
let domain = &data.config.federation.domain; let domain = &data.config.federation.domain;
let ap_id = ObjectId::parse(&format!("http://{domain}"))?; let ap_id = ObjectId::parse(&format!("http://{domain}"))?;
@ -124,7 +130,7 @@ fn setup(data: &IbisData) -> Result<(), Error> {
// Create the main page which is shown by default // Create the main page which is shown by default
let form = DbArticleForm { let form = DbArticleForm {
title: MAIN_PAGE_NAME.to_string(), title: MAIN_PAGE_NAME.to_string(),
text: "Hello world!".to_string(), text: MAIN_PAGE_DEFAULT_TEXT.to_string(),
ap_id: ObjectId::parse(&format!("http://{domain}/article/{MAIN_PAGE_NAME}"))?, ap_id: ObjectId::parse(&format!("http://{domain}/article/{MAIN_PAGE_NAME}"))?,
instance_id: instance.id, instance_id: instance.id,
local: true, local: true,

View File

@ -8,14 +8,17 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use url::{ParseError, Url}; use url::{ParseError, Url};
pub fn generate_activity_id(domain: &Url) -> Result<Url, ParseError> { pub fn generate_activity_id(domain: &Url) -> Result<Url, ParseError> {
let port = domain.port().unwrap(); let port = match domain.port() {
Some(p) => format!(":{p}"),
None => String::new(),
};
let domain = domain.host_str().unwrap(); let domain = domain.host_str().unwrap();
let id: String = thread_rng() let id: String = thread_rng()
.sample_iter(&Alphanumeric) .sample_iter(&Alphanumeric)
.take(7) .take(7)
.map(char::from) .map(char::from)
.collect(); .collect();
Url::parse(&format!("http://{}:{}/objects/{}", domain, port, id)) Url::parse(&format!("http://{}{}/objects/{}", domain, port, id))
} }
/// Starting from empty string, apply edits until the specified version is reached. If no version is /// Starting from empty string, apply edits until the specified version is reached. If no version is

View File

@ -1,3 +1,5 @@
pub mod validation;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};

10
src/common/validation.rs Normal file
View File

@ -0,0 +1,10 @@
use crate::common::{DbArticle, MAIN_PAGE_NAME};
use anyhow::anyhow;
use anyhow::Result;
pub fn can_edit_article(article: &DbArticle, is_admin: bool) -> Result<()> {
if article.local && article.title == MAIN_PAGE_NAME && !is_admin {
return Err(anyhow!("Only admin can edit main page"));
}
Ok(())
}

View File

@ -1,3 +1,4 @@
use crate::common::validation::can_edit_article;
use crate::common::ArticleView; use crate::common::ArticleView;
use crate::frontend::app::GlobalState; use crate::frontend::app::GlobalState;
use leptos::*; use leptos::*;
@ -9,15 +10,18 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
view! { view! {
<Suspense> <Suspense>
{move || article.get().map(|article| { {move || article.get().map(|article| {
let title = article.article.title; let title = article.article.title.clone();
view!{ view!{
<nav class="inner"> <nav class="inner">
<A href={format!("/article/{title}")}>"Read"</A> <A href={format!("/article/{title}")}>"Read"</A>
<A href={format!("/article/{title}/history")}>"History"</A> <A href={format!("/article/{title}/history")}>"History"</A>
<Show when=move || global_state.with(|state| state.my_profile.is_some())> <Show when=move || global_state.with(|state| {
<A href={format!("/article/{title}/edit")}>"Edit"</A> let is_admin = state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or(false);
</Show> state.my_profile.is_some() && can_edit_article(&article.article, is_admin).is_ok()
</nav> })>
<A href={format!("/article/{title}/edit")}>"Edit"</A>
</Show>
</nav>
}})} }})}
</Suspense> </Suspense>
} }

View File

@ -1,5 +1,4 @@
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub mod backend; pub mod backend;
pub mod common; pub mod common;
pub mod config;
pub mod frontend; pub mod frontend;

View File

@ -2,7 +2,7 @@
#[tokio::main] #[tokio::main]
pub async fn main() -> ibis_lib::backend::error::MyResult<()> { pub async fn main() -> ibis_lib::backend::error::MyResult<()> {
use config::Config; use config::Config;
use ibis_lib::config::IbisConfig; use ibis_lib::backend::config::IbisConfig;
use log::LevelFilter; use log::LevelFilter;
if std::env::args().collect::<Vec<_>>().get(1) == Some(&"--print-config".to_string()) { if std::env::args().collect::<Vec<_>>().get(1) == Some(&"--print-config".to_string()) {

View File

@ -1,6 +1,6 @@
use ibis_lib::backend::config::{IbisConfig, IbisConfigFederation};
use ibis_lib::backend::start; use ibis_lib::backend::start;
use ibis_lib::common::RegisterUserData; use ibis_lib::common::RegisterUserData;
use ibis_lib::config::{IbisConfig, IbisConfigFederation};
use ibis_lib::frontend::api::ApiClient; use ibis_lib::frontend::api::ApiClient;
use ibis_lib::frontend::error::MyResult; use ibis_lib::frontend::error::MyResult;
use reqwest::ClientBuilder; use reqwest::ClientBuilder;