diff --git a/Cargo.lock b/Cargo.lock index bb1cb961a..a88f51a09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3283,6 +3283,7 @@ dependencies = [ "governor", "indoc", "ipnetwork", + "lettre", "mas-iana", "mas-jose", "mas-keystore", diff --git a/crates/cli/src/util.rs b/crates/cli/src/util.rs index dee93caeb..a4ab8eba6 100644 --- a/crates/cli/src/util.rs +++ b/crates/cli/src/util.rs @@ -53,19 +53,25 @@ pub fn mailer_from_config( config: &EmailConfig, templates: &Templates, ) -> Result { - let from = config.from.parse()?; - let reply_to = config.reply_to.parse()?; + let from = config + .from + .parse() + .context("invalid email configuration: invalid 'from' address")?; + let reply_to = config + .reply_to + .parse() + .context("invalid email configuration: invalid 'reply_to' address")?; let transport = match config.transport() { EmailTransportKind::Blackhole => MailTransport::blackhole(), EmailTransportKind::Smtp => { // This should have been set ahead of time let hostname = config .hostname() - .context("invalid configuration: missing hostname")?; + .context("invalid email configuration: missing hostname")?; let mode = config .mode() - .context("invalid configuration: missing mode")?; + .context("invalid email configuration: missing mode")?; let credentials = match (config.username(), config.password()) { (Some(username), Some(password)) => Some(mas_email::SmtpCredentials::new( @@ -74,7 +80,7 @@ pub fn mailer_from_config( )), (None, None) => None, _ => { - anyhow::bail!("invalid configuration: missing username or password"); + anyhow::bail!("invalid email configuration: missing username or password"); } }; diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index e07215f3c..403e38c24 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -22,6 +22,7 @@ camino = { workspace = true, features = ["serde1"] } chrono.workspace = true figment.workspace = true ipnetwork = { version = "0.20.0", features = ["serde", "schemars"] } +lettre.workspace = true schemars.workspace = true ulid.workspace = true url.workspace = true diff --git a/crates/config/src/sections/email.rs b/crates/config/src/sections/email.rs index 24fdabd5c..23668de3c 100644 --- a/crates/config/src/sections/email.rs +++ b/crates/config/src/sections/email.rs @@ -6,8 +6,9 @@ #![allow(deprecated)] -use std::num::NonZeroU16; +use std::{num::NonZeroU16, str::FromStr}; +use lettre::message::Mailbox; use schemars::JsonSchema; use serde::{de::Error, Deserialize, Serialize}; @@ -199,6 +200,14 @@ impl ConfigurationSection for EmailConfig { EmailTransportKind::Blackhole => {} EmailTransportKind::Smtp => { + if let Err(e) = Mailbox::from_str(&self.from) { + return Err(error_on_field(figment::error::Error::custom(e), "from")); + } + + if let Err(e) = Mailbox::from_str(&self.reply_to) { + return Err(error_on_field(figment::error::Error::custom(e), "reply_to")); + } + match (self.username.is_some(), self.password.is_some()) { (true, true) | (false, false) => {} (true, false) => { @@ -237,6 +246,14 @@ impl ConfigurationSection for EmailConfig { EmailTransportKind::Sendmail => { let expected_fields = &["from", "reply_to", "transport", "command"]; + if let Err(e) = Mailbox::from_str(&self.from) { + return Err(error_on_field(figment::error::Error::custom(e), "from")); + } + + if let Err(e) = Mailbox::from_str(&self.reply_to) { + return Err(error_on_field(figment::error::Error::custom(e), "reply_to")); + } + if self.command.is_none() { return Err(missing_field("command")); }