feat: customize webmaster email

This commit is contained in:
Max Hohlfeld 2025-05-24 21:27:33 +02:00
parent 513e8983b9
commit a608204103
9 changed files with 59 additions and 16 deletions

2
.env
View File

@ -6,9 +6,11 @@ SQLX_OFFLINE=true
# 64 byte long openssl rand -base64 64 # 64 byte long openssl rand -base64 64
SECRET_KEY="changeInProdOrHandAb11111111111111111111111111111111111111111111" SECRET_KEY="changeInProdOrHandAb11111111111111111111111111111111111111111111"
HOSTNAME="localhost" HOSTNAME="localhost"
WEBMASTER_EMAIL="admin@example.com"
SERVER_ADDRESS="127.0.0.1" SERVER_ADDRESS="127.0.0.1"
SERVER_PORT="8080" SERVER_PORT="8080"
APP_ENVIRONMENT="development"
SMTP_SERVER="localhost" SMTP_SERVER="localhost"
SMTP_PORT="1025" SMTP_PORT="1025"
# SMTP_LOGIN="" # SMTP_LOGIN=""

View File

@ -17,6 +17,7 @@ pub struct Config {
pub smtp_login: Option<String>, pub smtp_login: Option<String>,
pub smtp_password: Option<String>, pub smtp_password: Option<String>,
pub smtp_tlstype: SmtpTlsType, pub smtp_tlstype: SmtpTlsType,
pub webmaster_email: String
} }
#[derive(Clone)] #[derive(Clone)]
@ -67,6 +68,7 @@ pub fn load_config(env: &Environment) -> Result<Config, anyhow::Error> {
smtp_login: env::var("SMTP_LOGIN").map(Some).unwrap_or(None), smtp_login: env::var("SMTP_LOGIN").map(Some).unwrap_or(None),
smtp_password: env::var("SMTP_PASSWORD").map(Some).unwrap_or(None), smtp_password: env::var("SMTP_PASSWORD").map(Some).unwrap_or(None),
smtp_tlstype: SmtpTlsType::from(env::var("SMTP_TLSTYPE")?), smtp_tlstype: SmtpTlsType::from(env::var("SMTP_TLSTYPE")?),
webmaster_email: env::var("WEBMASTER_EMAIL")?,
}; };
Ok(config) Ok(config)

View File

@ -6,14 +6,16 @@ use sqlx::PgPool;
use crate::{ use crate::{
models::PasswordReset, models::PasswordReset,
utils::{ApplicationError, TemplateResponse}, utils::{ApplicationError, Customization, TemplateResponse},
}; };
use super::ResetPasswordTemplate; use super::ResetPasswordTemplate;
#[derive(Template)] #[derive(Template)]
#[template(path = "user/forgot_password.html")] #[template(path = "user/forgot_password.html")]
struct ForgotPasswordTemplate {} struct ForgotPasswordTemplate<'a> {
webmaster_email: &'a str,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct TokenQuery { struct TokenQuery {
@ -25,6 +27,7 @@ pub async fn get(
user: Option<Identity>, user: Option<Identity>,
pool: web::Data<PgPool>, pool: web::Data<PgPool>,
query: web::Query<TokenQuery>, query: web::Query<TokenQuery>,
customization: web::Data<Customization>,
) -> Result<impl Responder, ApplicationError> { ) -> Result<impl Responder, ApplicationError> {
if user.is_some() { if user.is_some() {
return Ok(HttpResponse::Found() return Ok(HttpResponse::Found()
@ -45,6 +48,8 @@ pub async fn get(
} }
} }
let template = ForgotPasswordTemplate {}; let template = ForgotPasswordTemplate {
webmaster_email: &customization.webmaster_email,
};
Ok(template.to_response()?) Ok(template.to_response()?)
} }

View File

@ -19,6 +19,7 @@ use tracing_panic::panic_hook;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{fmt, EnvFilter}; use tracing_subscriber::{fmt, EnvFilter};
use utils::Customization;
use crate::postgres_session_store::SqlxPostgresqlSessionStore; use crate::postgres_session_store::SqlxPostgresqlSessionStore;
use crate::utils::manage_commands::{handle_command, parse_args}; use crate::utils::manage_commands::{handle_command, parse_args};
@ -49,6 +50,9 @@ async fn main() -> anyhow::Result<()> {
let pool = PgPool::connect(&config.database_url).await?; let pool = PgPool::connect(&config.database_url).await?;
let mailer = Mailer::new(&config)?; let mailer = Mailer::new(&config)?;
let customization = Customization {
webmaster_email: config.webmaster_email.clone(),
};
handle_command(args.command, &pool, &mailer).await?; handle_command(args.command, &pool, &mailer).await?;
@ -56,10 +60,17 @@ async fn main() -> anyhow::Result<()> {
let port = config.server_port; let port = config.server_port;
info!("Starting server on http://{address}:{port}."); info!("Starting server on http://{address}:{port}.");
HttpServer::new(move || create_app(config.clone(), pool.clone(), mailer.clone())) HttpServer::new(move || {
.bind((address, port))? create_app(
.run() config.clone(),
.await?; pool.clone(),
mailer.clone(),
customization.clone(),
)
})
.bind((address, port))?
.run()
.await?;
Ok(()) Ok(())
} }
@ -68,6 +79,7 @@ pub fn create_app(
config: Config, config: Config,
pool: Pool<Postgres>, pool: Pool<Postgres>,
mailer: Mailer, mailer: Mailer,
customization: Customization,
) -> App< ) -> App<
impl ServiceFactory< impl ServiceFactory<
ServiceRequest, ServiceRequest,
@ -84,6 +96,7 @@ pub fn create_app(
App::new() App::new()
.app_data(web::Data::new(pool)) .app_data(web::Data::new(pool))
.app_data(web::Data::new(mailer)) .app_data(web::Data::new(mailer))
.app_data(web::Data::new(customization))
.configure(endpoints::init) .configure(endpoints::init)
.wrap(middleware::ErrorAppender) .wrap(middleware::ErrorAppender)
.wrap(TracingLogger::default()) .wrap(TracingLogger::default())

View File

@ -0,0 +1,4 @@
#[derive(Clone)]
pub struct Customization {
pub webmaster_email: String,
}

View File

@ -5,6 +5,7 @@ pub mod event_planning_template;
pub mod manage_commands; pub mod manage_commands;
pub mod password_change; pub mod password_change;
mod template_response_trait; mod template_response_trait;
mod app_customization;
pub mod token_generation; pub mod token_generation;
#[cfg(test)] #[cfg(test)]
@ -13,6 +14,7 @@ pub mod test_helper;
pub use application_error::ApplicationError; pub use application_error::ApplicationError;
pub use date_time_format::DateTimeFormat; pub use date_time_format::DateTimeFormat;
pub use template_response_trait::TemplateResponse; pub use template_response_trait::TemplateResponse;
pub use app_customization::Customization;
use chrono::{NaiveDate, Utc}; use chrono::{NaiveDate, Utc};

View File

@ -8,6 +8,7 @@ use actix_web::{
}; };
use rand::{distr::Alphanumeric, rng, Rng}; use rand::{distr::Alphanumeric, rng, Rng};
use crate::utils::Customization;
use crate::{create_app, mail::Mailer}; use crate::{create_app, mail::Mailer};
use brass_config::{load_config, Config, Environment}; use brass_config::{load_config, Config, Environment};
use regex::{Captures, Regex}; use regex::{Captures, Regex};
@ -29,10 +30,15 @@ impl DbTestContext {
Response = ServiceResponse<impl MessageBody>, Response = ServiceResponse<impl MessageBody>,
Error = actix_web::error::Error, Error = actix_web::error::Error,
> { > {
let customization = Customization {
webmaster_email: self.config.webmaster_email.clone(),
};
init_service(create_app( init_service(create_app(
self.config.clone(), self.config.clone(),
self.db_pool.clone(), self.db_pool.clone(),
Mailer::new_stub(), Mailer::new_stub(),
customization,
)) ))
.await .await
} }

View File

@ -6,7 +6,7 @@
<h1 class="title">Brass - Passwort zurücksetzen</h1> <h1 class="title">Brass - Passwort zurücksetzen</h1>
<article class="message is-info"> <article class="message is-info">
<div class="message-body"> <div class="message-body">
Gib deine E-Mail Adresse ein und erhalte einen Link zum Zurücksetzen deines Passworts, sofern ein Account mit dieser Adresse existiert. Bei Problemen wende dich an <a href="mailto:mail@example.com">mail@example.com</a>. Gib deine E-Mail Adresse ein und erhalte einen Link zum Zurücksetzen deines Passworts, sofern ein Account mit dieser Adresse existiert. Bei Problemen wende dich an <a href="mailto:{{ webmaster_email }}">{{ webmaster_email }}</a>.
</div> </div>
</article> </article>
<form class="box" hx-post="/reset-password"> <form class="box" hx-post="/reset-password">

View File

@ -5,7 +5,7 @@
<div class="container"> <div class="container">
<h1 class="title">Brass - Anmeldung</h1> <h1 class="title">Brass - Anmeldung</h1>
<form class="box" action="/login" method="post" hx-boost="true" hx-target-400="#error-message" <form class="box" action="/login" method="post" hx-boost="true" hx-target-400="#error-message"
hx-on:change="document.getElementById('error-message').innerHTML = ''"> hx-on:change="document.getElementById('error-message').innerHTML = ''">
{% if let Some(next) = next %} {% if let Some(next) = next %}
<input type="hidden" name="next" value="{{ next }}" /> <input type="hidden" name="next" value="{{ next }}" />
@ -28,13 +28,22 @@
<div id="error-message" class="mb-3 help is-danger"></div> <div id="error-message" class="mb-3 help is-danger"></div>
<div class="level"> <div class="level">
<button class="button is-primary level-left"> <div class="level-left">
<svg class="icon"> <button class="button is-primary level-item">
<use href="/static/feather-sprite.svg#log-in" /> <svg class="icon">
</svg> <use href="/static/feather-sprite.svg#log-in" />
<span>Anmelden</span> </svg>
</button> <span>Anmelden</span>
<a class="button is-info is-light level-right" hx-boost="true" href="/reset-password">Passwort vergessen</a> </button>
</div>
<div class="level-right level-item">
<a class="button is-info is-light" href="/reset-password">
<svg class="icon">
<use href="/static/feather-sprite.svg#help-circle" />
</svg>
<span>Passwort vergessen</span>
</a>
</div>
</div> </div>
</form> </form>
</div> </div>