diff --git a/docker-compose.yml b/docker-compose.yml index acebc5cb1bf0..034403a41cef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -63,13 +63,14 @@ services: # ===================================== # Email Configuration - # - SMTP_HOST= - # - SMTP_PORT=587 - # - SMTP_FROM_NAME=Mealie - # - SMTP_TLS=true - # - SMTP_FROM_EMAIL= - # - SMTP_USER= - # - SMTP_PASSWORD= + # SMTP_HOST= + # SMTP_PORT=587 + # SMTP_FROM_NAME=Mealie + # SMTP_AUTH_STRATEGY=TLS # Options: 'TLS', 'SSL', 'NONE' + # SMTP_FROM_EMAIL= + # SMTP_USER= + # SMTP_PASSWORD= + # postgres: # container_name: postgres # image: postgres diff --git a/docs/docs/documentation/getting-started/installation/backend-config.md b/docs/docs/documentation/getting-started/installation/backend-config.md index ff7e74e83a09..b565dae0aeb1 100644 --- a/docs/docs/documentation/getting-started/installation/backend-config.md +++ b/docs/docs/documentation/getting-started/installation/backend-config.md @@ -34,15 +34,15 @@ ### Email -| Variables | Default | Description | -| --------------- | :-----: | ------------------ | -| SMTP_HOST | None | Required For email | -| SMTP_PORT | 587 | Required For email | -| SMTP_FROM_NAME | Mealie | Required For email | -| SMTP_TLS | true | Required For email | -| SMTP_FROM_EMAIL | None | Required For email | -| SMTP_USER | None | Required For email | -| SMTP_PASSWORD | None | Required For email | +| Variables | Default | Description | +| ------------------ | :-----: | ------------------------------------------------- | +| SMTP_HOST | None | Required For email | +| SMTP_PORT | 587 | Required For email | +| SMTP_FROM_NAME | Mealie | Required For email | +| SMTP_AUTH_STRATEGY | TLS | Required For email, Options: 'TLS', 'SSL', 'NONE' | +| SMTP_FROM_EMAIL | None | Required For email | +| SMTP_USER | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' | +| SMTP_PASSWORD | None | Required if SMTP_AUTH_STRATEGY is 'TLS' or 'SSL' | ### Webworker Changing the webworker settings may cause unforeseen memory leak issues with Mealie. It's best to leave these at the defaults unless you begin to experience issues with multiple users. Exercise caution when changing these settings diff --git a/mealie/core/settings/settings.py b/mealie/core/settings/settings.py index fdee2b761578..704ab373eb2c 100644 --- a/mealie/core/settings/settings.py +++ b/mealie/core/settings/settings.py @@ -68,23 +68,39 @@ class AppSettings(BaseSettings): SMTP_HOST: Optional[str] SMTP_PORT: Optional[str] = "587" SMTP_FROM_NAME: Optional[str] = "Mealie" - SMTP_TLS: Optional[bool] = True SMTP_FROM_EMAIL: Optional[str] SMTP_USER: Optional[str] SMTP_PASSWORD: Optional[str] + SMTP_AUTH_STRATEGY: Optional[str] = "TLS" # Options: 'TLS', 'SSL', 'NONE' @property def SMTP_ENABLE(self) -> bool: - """Validates all SMTP variables are set""" - required = { + return AppSettings.validate_smtp( self.SMTP_HOST, self.SMTP_PORT, self.SMTP_FROM_NAME, - self.SMTP_TLS, self.SMTP_FROM_EMAIL, + self.SMTP_AUTH_STRATEGY, self.SMTP_USER, self.SMTP_PASSWORD, - } + ) + + @staticmethod + def validate_smtp( + host: str, + port: str, + from_name: str, + from_email: str, + strategy: str, + user: str | None = None, + password: str | None = None, + ) -> bool: + """Validates all SMTP variables are set""" + required = {host, port, from_name, from_email, strategy} + + if strategy.upper() in {"TLS", "SSL"}: + required.add(user) + required.add(password) return "" not in required and None not in required diff --git a/mealie/services/email/email_senders.py b/mealie/services/email/email_senders.py index ffd6686a7184..4bdb5707473f 100644 --- a/mealie/services/email/email_senders.py +++ b/mealie/services/email/email_senders.py @@ -24,8 +24,10 @@ class DefaultEmailSender(ABCEmailSender, BaseService): ) smtp_options: dict[str, str | bool] = {"host": self.settings.SMTP_HOST, "port": self.settings.SMTP_PORT} - if self.settings.SMTP_TLS: + if self.settings.SMTP_AUTH_STRATEGY.upper() == "TLS": smtp_options["tls"] = True + if self.settings.SMTP_AUTH_STRATEGY.upper() == "SSL": + smtp_options["ssl"] = True if self.settings.SMTP_USER: smtp_options["user"] = self.settings.SMTP_USER if self.settings.SMTP_PASSWORD: diff --git a/template.env b/template.env index 004a466be9ff..7ef4fa463e4f 100644 --- a/template.env +++ b/template.env @@ -29,7 +29,7 @@ LANG=en-US # SMTP_HOST="" # SMTP_PORT="" # SMTP_FROM_NAME="" -# SMTP_TLS="" +# SMTP_AUTH_STRATEGY="" # Options: 'TLS', 'SSL', 'NONE' # SMTP_FROM_EMAIL="" # SMTP_USER="" # SMTP_PASSWORD="" @@ -39,4 +39,3 @@ LDAP_AUTH_ENABLED=False LDAP_SERVER_URL=None LDAP_BIND_TEMPLATE=None LDAP_ADMIN_FILTER=None - diff --git a/tests/unit_tests/services_tests/test_email_service.py b/tests/unit_tests/services_tests/test_email_service.py index 3f09dddf0b88..576edeb4b1d6 100644 --- a/tests/unit_tests/services_tests/test_email_service.py +++ b/tests/unit_tests/services_tests/test_email_service.py @@ -28,7 +28,7 @@ class TestEmailSender(ABCEmailSender): def patch_env(monkeypatch): monkeypatch.setenv("SMTP_HOST", "email.mealie.io") monkeypatch.setenv("SMTP_PORT", "587") - monkeypatch.setenv("SMTP_TLS", "True") + monkeypatch.setenv("SMTP_AUTH_STRATEGY", "TLS") monkeypatch.setenv("SMTP_FROM_NAME", "Mealie") monkeypatch.setenv("SMTP_FROM_EMAIL", "mealie@mealie.io") monkeypatch.setenv("SMTP_USER", "mealie@mealie.io") diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 9c9dc6ed164d..aa62f40157e2 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -1,6 +1,10 @@ import re +from dataclasses import dataclass + +import pytest from mealie.core.config import get_app_settings +from mealie.core.settings.settings import AppSettings def test_non_default_settings(monkeypatch): @@ -36,29 +40,59 @@ def test_pg_connection_args(monkeypatch): assert app_settings.DB_URL == "postgresql://mealie:mealie@postgres:5432/mealie" -def test_smtp_enable(monkeypatch): - monkeypatch.setenv("SMTP_HOST", "") - monkeypatch.setenv("SMTP_PORT", "") - monkeypatch.setenv("SMTP_TLS", "true") - monkeypatch.setenv("SMTP_FROM_NAME", "") - monkeypatch.setenv("SMTP_FROM_EMAIL", "") - monkeypatch.setenv("SMTP_USER", "") - monkeypatch.setenv("SMTP_PASSWORD", "") +@dataclass(slots=True) +class SMTPValidationCase: + host: str + port: str + auth_strategy: str + from_name: str + from_email: str + user: str + password: str + is_valid: bool - get_app_settings.cache_clear() - app_settings = get_app_settings() - assert app_settings.SMTP_ENABLE is False +smtp_validation_cases = [ + ( + "bad_data_tls", + SMTPValidationCase("", "", "tls", "", "", "", "", False), + ), + ( + "bad_data_ssl", + SMTPValidationCase("", "", "ssl", "", "", "", "", False), + ), + ( + "no_auth", + SMTPValidationCase("email.mealie.io", "25", "none", "Mealie", "mealie@mealie.io", "", "", True), + ), + ( + "good_data_tls", + SMTPValidationCase( + "email.mealie.io", "587", "tls", "Mealie", "mealie@mealie.io", "mealie@mealie.io", "mealie-password", True + ), + ), + ( + "good_data_ssl", + SMTPValidationCase( + "email.mealie.io", "465", "tls", "Mealie", "mealie@mealie.io", "mealie@mealie.io", "mealie-password", True + ), + ), +] - monkeypatch.setenv("SMTP_HOST", "email.mealie.io") - monkeypatch.setenv("SMTP_PORT", "587") - monkeypatch.setenv("SMTP_TLS", "true") - monkeypatch.setenv("SMTP_FROM_NAME", "Mealie") - monkeypatch.setenv("SMTP_FROM_EMAIL", "mealie@mealie.io") - monkeypatch.setenv("SMTP_USER", "mealie@mealie.io") - monkeypatch.setenv("SMTP_PASSWORD", "mealie-password") +smtp_cases = [x[1] for x in smtp_validation_cases] +smtp_cases_ids = [x[0] for x in smtp_validation_cases] - get_app_settings.cache_clear() - app_settings = get_app_settings() - assert app_settings.SMTP_ENABLE is True +@pytest.mark.parametrize("data", smtp_cases, ids=smtp_cases_ids) +def test_smtp_enable_with_bad_data_tls(data: SMTPValidationCase): + is_valid = AppSettings.validate_smtp( + data.host, + data.port, + data.from_name, + data.from_email, + data.auth_strategy, + data.user, + data.password, + ) + + assert is_valid is data.is_valid