Compare commits

...

3 Commits

Author SHA1 Message Date
phundrak e08210e52d docs: clarify documentation
Publish Docker Images / coverage-and-sonar (push) Successful in 10m21s
Publish Docker Images / build-docker (push) Successful in 7m37s
Publish Docker Images / push-docker (push) Successful in 21s
2026-06-06 16:10:58 +02:00
phundrak 3679c7e8cd 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.
2026-06-06 16:10:58 +02:00
phundrak b5a83f100d chore(ci): add linting, formatting check, and auditing 2026-06-06 16:10:58 +02:00
5 changed files with 25 additions and 62 deletions
+14 -2
View File
@@ -40,9 +40,21 @@ jobs:
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
skipPush: ${{ github.event_name == 'pull_request' }} skipPush: ${{ github.event_name == 'pull_request' }}
- name: Format Check
shell: bash -c "nix develop --no-pure-eval --accept-flake-config --command {0}"
run: just format-check
- name: Audit
shell: bash -c "nix develop --no-pure-eval --accept-flake-config --command {0}"
run: just audit
- name: Lint
shell: bash -c "nix develop --no-pure-eval --accept-flake-config --command {0}"
run: just lint-report
- name: Coverage - name: Coverage
run: | shell: bash -c "nix develop --no-pure-eval --accept-flake-config --command {0}"
nix develop --no-pure-eval --accept-flake-config --command just coverage run: just coverage-ci
- name: Sonar analysis - name: Sonar analysis
uses: SonarSource/sonarqube-scan-action@v6 uses: SonarSource/sonarqube-scan-action@v6
+1 -1
View File
@@ -321,7 +321,7 @@ backend/
The contact form supports multiple SMTP configurations: The contact form supports multiple SMTP configurations:
- **Implicit TLS (SMTPS)** - typically port 465 - **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 - **Unencrypted** (for local dev) - with or without authentication
The `SmtpTransport` is built dynamically from `EmailSettings` based on The `SmtpTransport` is built dynamically from `EmailSettings` based on
+4
View File
@@ -24,6 +24,10 @@ build-release:
lint: lint:
cargo clippy --all-targets cargo clippy --all-targets
lint-report:
mkdir -p coverage
cargo clippy --all-targets --message-format=json > coverage/clippy.json
release-build: release-build:
cargo build --release cargo build --release
+1 -18
View File
@@ -62,7 +62,7 @@ impl TryFrom<&EmailSettings> for SmtpTransport {
Ok(builder.credentials(creds).build()) Ok(builder.credentials(creds).build())
} }
} }
Starttls::Opportunistic | Starttls::Always => { Starttls::Always => {
// STARTTLS - typically port 587 // STARTTLS - typically port 587
tracing::event!(target: "backend::contact", tracing::Level::DEBUG, "Using STARTTLS"); tracing::event!(target: "backend::contact", tracing::Level::DEBUG, "Using STARTTLS");
let creds = Credentials::new(settings.user.clone(), settings.password.clone()); let creds = Credentials::new(settings.user.clone(), settings.password.clone());
@@ -429,23 +429,6 @@ mod tests {
assert!(result.is_ok()); 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] #[test]
fn smtp_transport_no_encryption_with_credentials() { fn smtp_transport_no_encryption_with_credentials() {
let settings = EmailSettings { let settings = EmailSettings {
+5 -41
View File
@@ -163,8 +163,9 @@ impl EmailSettings {
/// ///
/// # Errors /// # Errors
/// ///
/// Returns a `ContactError` if the email address in the `from` field cannot be parsed /// Returns a `ContactError` if the email address in the `from`
/// into a valid mailbox. This can occur if: /// field of `recipient` cannot be parsed into a valid mailbox.
/// This can occur if:
/// - The email address format is invalid /// - The email address format is invalid
/// - The email address contains invalid characters /// - The email address contains invalid characters
/// - The email address structure is malformed /// - The email address structure is malformed
@@ -196,8 +197,6 @@ pub enum Starttls {
/// Never use STARTTLS (unencrypted connection) /// Never use STARTTLS (unencrypted connection)
#[default] #[default]
Never, Never,
/// Use STARTTLS if available (opportunistic encryption)
Opportunistic,
/// Always use STARTTLS (required encryption) /// Always use STARTTLS (required encryption)
Always, Always,
} }
@@ -208,10 +207,9 @@ impl TryFrom<&str> for Starttls {
fn try_from(value: &str) -> Result<Self, Self::Error> { fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() { match value.to_lowercase().as_str() {
"off" | "no" | "never" => Ok(Self::Never), "off" | "no" | "never" => Ok(Self::Never),
"opportunistic" => Ok(Self::Opportunistic),
"yes" | "always" => Ok(Self::Always), "yes" | "always" => Ok(Self::Always),
other => Err(format!( 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 +232,6 @@ impl std::fmt::Display for Starttls {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let self_str = match self { let self_str = match self {
Self::Never => "never", Self::Never => "never",
Self::Opportunistic => "opportunistic",
Self::Always => "always", Self::Always => "always",
}; };
write!(f, "{self_str}") write!(f, "{self_str}")
@@ -252,7 +249,7 @@ impl<'de> serde::Deserialize<'de> for Starttls {
type Value = Starttls; type Value = Starttls;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 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<E>(self, value: &str) -> Result<Starttls, E> fn visit_str<E>(self, value: &str) -> Result<Starttls, E>
@@ -434,13 +431,6 @@ mod tests {
assert_eq!(result, Starttls::Always); 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] #[test]
fn startls_deserialize_from_bool() { fn startls_deserialize_from_bool() {
let json = "true"; let json = "true";
@@ -482,18 +472,6 @@ mod tests {
assert_eq!(Starttls::try_from("Yes").unwrap(), Starttls::Always); 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] #[test]
fn startls_try_from_str_invalid() { fn startls_try_from_str_invalid() {
let result = Starttls::try_from("invalid"); let result = Starttls::try_from("invalid");
@@ -517,14 +495,6 @@ mod tests {
); );
} }
#[test]
fn startls_try_from_string_opportunistic() {
assert_eq!(
Starttls::try_from("opportunistic".to_string()).unwrap(),
Starttls::Opportunistic
);
}
#[test] #[test]
fn startls_try_from_string_invalid() { fn startls_try_from_string_invalid() {
let result = Starttls::try_from("invalid".to_string()); let result = Starttls::try_from("invalid".to_string());
@@ -553,12 +523,6 @@ mod tests {
assert_eq!(startls.to_string(), "always"); assert_eq!(startls.to_string(), "always");
} }
#[test]
fn startls_display_opportunistic() {
let startls = Starttls::Opportunistic;
assert_eq!(startls.to_string(), "opportunistic");
}
#[test] #[test]
fn rate_limit_settings_default() { fn rate_limit_settings_default() {
let settings = RateLimitSettings::default(); let settings = RateLimitSettings::default();