Add SMTP mail, access-request notifications, and a shared request throttle
A new [mail] section configures an SMTP relay (SES-style STARTTLS or implicit TLS; the password is environment-only via DAILY_EPUB_MAIL__SMTP_PASS) and src/mail.rs wraps lettre in a small plain-text Mailer built once at server start. Each stored access request now emails mail.notify_to in a spawned task with the address, reason, time, and a link to /dashboard/users. POST /request-access shares the login endpoint's per-IP limiter (server.login_attempts per window). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QVPagF6jfDv78CC5Jv2wp4
This commit is contained in:
+9
-1
@@ -70,6 +70,8 @@ pub enum ServerError {
|
||||
},
|
||||
#[error("io error: {0}")]
|
||||
Io(#[from] std::io::Error),
|
||||
#[error("could not configure outbound mail: {0}")]
|
||||
Mail(#[source] anyhow::Error),
|
||||
}
|
||||
|
||||
/// Shared axum state.
|
||||
@@ -79,6 +81,8 @@ pub struct AppState {
|
||||
pub config: Arc<RwLock<Arc<Config>>>,
|
||||
pub config_path: Option<PathBuf>,
|
||||
pub web: Arc<crate::web::WebState>,
|
||||
/// Startup-built SMTP sender. Mail configuration changes require a restart.
|
||||
pub mailer: Option<crate::mail::Mailer>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AppState {
|
||||
@@ -102,6 +106,7 @@ impl AppState {
|
||||
started_at: Timestamp::now(),
|
||||
config_mtime: std::sync::Mutex::new(None),
|
||||
}),
|
||||
mailer: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +206,7 @@ pub async fn serve(
|
||||
// Not fatal for the OPDS routes, but every rating link would 500.
|
||||
tracing::warn!("server.hmac_secret is unset — rating links will be rejected");
|
||||
}
|
||||
let mailer = crate::mail::Mailer::from_config(&config.mail).map_err(ServerError::Mail)?;
|
||||
let addr = config.server.bind.clone();
|
||||
let listener = tokio::net::TcpListener::bind(&addr)
|
||||
.await
|
||||
@@ -234,7 +240,9 @@ pub async fn serve(
|
||||
tracing::info!("server.jobs_enabled is false; the Jobs page cannot start units");
|
||||
Arc::new(crate::web::DisabledRunner)
|
||||
};
|
||||
let app = router(AppState::with_jobs(db, config, config_path, jobs));
|
||||
let mut state = AppState::with_jobs(db, config, config_path, jobs);
|
||||
state.mailer = mailer;
|
||||
let app = router(state);
|
||||
axum::serve(
|
||||
listener,
|
||||
app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
|
||||
|
||||
Reference in New Issue
Block a user