From f772a7a274bf151d9069b0e0024e4a88d3c97dcd Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Sat, 6 Jun 2026 15:40:38 +0200 Subject: [PATCH] feat(starttls): remove opportunistic value This commit removes the `Opportunistic` value from the struct `StartTls`. This value was strictly equivalent to `Always` and could potentially cause confusion. --- README.md | 2 +- src/route/contact/mod.rs | 19 +------------------ src/settings.rs | 41 ++-------------------------------------- 3 files changed, 4 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index a6f2d98..fbe3e2d 100644 --- a/README.md +++ b/README.md @@ -321,7 +321,7 @@ backend/ The contact form supports multiple SMTP configurations: - **Implicit TLS (SMTPS)** - typically port 465 -- **STARTTLS (Always/Opportunistic)** - typically port 587 +- **STARTTLS (Always)** - typically port 587 - **Unencrypted** (for local dev) - with or without authentication The `SmtpTransport` is built dynamically from `EmailSettings` based on diff --git a/src/route/contact/mod.rs b/src/route/contact/mod.rs index 713b5b1..1f07a1e 100644 --- a/src/route/contact/mod.rs +++ b/src/route/contact/mod.rs @@ -62,7 +62,7 @@ impl TryFrom<&EmailSettings> for SmtpTransport { Ok(builder.credentials(creds).build()) } } - Starttls::Opportunistic | Starttls::Always => { + Starttls::Always => { // STARTTLS - typically port 587 tracing::event!(target: "backend::contact", tracing::Level::DEBUG, "Using STARTTLS"); let creds = Credentials::new(settings.user.clone(), settings.password.clone()); @@ -429,23 +429,6 @@ mod tests { assert!(result.is_ok()); } - #[test] - fn smtp_transport_starttls_opportunistic() { - let settings = EmailSettings { - host: "smtp.example.com".to_string(), - port: 587, - user: "user@example.com".to_string(), - password: "password".to_string(), - from: "from@example.com".to_string(), - recipient: "to@example.com".to_string(), - tls: false, - starttls: Starttls::Opportunistic, - }; - - let result = SmtpTransport::try_from(&settings); - assert!(result.is_ok()); - } - #[test] fn smtp_transport_no_encryption_with_credentials() { let settings = EmailSettings { diff --git a/src/settings.rs b/src/settings.rs index 45370ab..76df109 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -196,8 +196,6 @@ pub enum Starttls { /// Never use STARTTLS (unencrypted connection) #[default] Never, - /// Use STARTTLS if available (opportunistic encryption) - Opportunistic, /// Always use STARTTLS (required encryption) Always, } @@ -208,10 +206,9 @@ impl TryFrom<&str> for Starttls { fn try_from(value: &str) -> Result { match value.to_lowercase().as_str() { "off" | "no" | "never" => Ok(Self::Never), - "opportunistic" => Ok(Self::Opportunistic), "yes" | "always" => Ok(Self::Always), other => Err(format!( - "{other} is not a supported option. Use either `yes`, `no`, or `opportunistic`" + "{other} is not a supported option. Use either `yes` or `no`" )), } } @@ -234,7 +231,6 @@ impl std::fmt::Display for Starttls { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let self_str = match self { Self::Never => "never", - Self::Opportunistic => "opportunistic", Self::Always => "always", }; write!(f, "{self_str}") @@ -252,7 +248,7 @@ impl<'de> serde::Deserialize<'de> for Starttls { type Value = Starttls; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a string or boolean representing STARTTLS setting (e.g., 'yes', 'no', 'opportunistic', true, false)") + formatter.write_str("a string or boolean representing STARTTLS setting (e.g., 'yes', 'no', true, false)") } fn visit_str(self, value: &str) -> Result @@ -434,13 +430,6 @@ mod tests { assert_eq!(result, Starttls::Always); } - #[test] - fn startls_deserialize_from_string_opportunistic() { - let json = r#""opportunistic""#; - let result: Starttls = serde_json::from_str(json).unwrap(); - assert_eq!(result, Starttls::Opportunistic); - } - #[test] fn startls_deserialize_from_bool() { let json = "true"; @@ -482,18 +471,6 @@ mod tests { assert_eq!(Starttls::try_from("Yes").unwrap(), Starttls::Always); } - #[test] - fn startls_try_from_str_opportunistic() { - assert_eq!( - Starttls::try_from("opportunistic").unwrap(), - Starttls::Opportunistic - ); - assert_eq!( - Starttls::try_from("OPPORTUNISTIC").unwrap(), - Starttls::Opportunistic - ); - } - #[test] fn startls_try_from_str_invalid() { let result = Starttls::try_from("invalid"); @@ -517,14 +494,6 @@ mod tests { ); } - #[test] - fn startls_try_from_string_opportunistic() { - assert_eq!( - Starttls::try_from("opportunistic".to_string()).unwrap(), - Starttls::Opportunistic - ); - } - #[test] fn startls_try_from_string_invalid() { let result = Starttls::try_from("invalid".to_string()); @@ -553,12 +522,6 @@ mod tests { assert_eq!(startls.to_string(), "always"); } - #[test] - fn startls_display_opportunistic() { - let startls = Starttls::Opportunistic; - assert_eq!(startls.to_string(), "opportunistic"); - } - #[test] fn rate_limit_settings_default() { let settings = RateLimitSettings::default();