From 48451b782dffb249f4cf62d372949db8b8c9d907 Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Thu, 26 Jun 2025 14:18:30 +0200 Subject: [PATCH] Move email env var reading to yml files (#35191) --- .prettierignore | 3 + config/application.rb | 2 + config/email.yml | 21 ++++ config/environments/production.rb | 38 +------ lib/mastodon/email_configuration_helper.rb | 33 +++++++ .../email_configuration_helper_spec.rb | 98 +++++++++++++++++++ 6 files changed, 162 insertions(+), 33 deletions(-) create mode 100644 config/email.yml create mode 100644 lib/mastodon/email_configuration_helper.rb create mode 100644 spec/lib/mastodon/email_configuration_helper_spec.rb diff --git a/.prettierignore b/.prettierignore index 8f16e731c..098dac671 100644 --- a/.prettierignore +++ b/.prettierignore @@ -81,3 +81,6 @@ AUTHORS.md # Process a few selected JS files !lint-staged.config.js + +# Ignore config YAML files that include ERB/ruby code prettier does not understand +/config/email.yml diff --git a/config/application.rb b/config/application.rb index 2021810ed..90cfe4742 100644 --- a/config/application.rb +++ b/config/application.rb @@ -35,6 +35,7 @@ require_relative '../lib/paperclip/response_with_limit_adapter' require_relative '../lib/terrapin/multi_pipe_extensions' require_relative '../lib/mastodon/middleware/public_file_server' require_relative '../lib/mastodon/middleware/socket_cleanup' +require_relative '../lib/mastodon/email_configuration_helper' require_relative '../lib/mastodon/feature' require_relative '../lib/mastodon/snowflake' require_relative '../lib/mastodon/version' @@ -104,6 +105,7 @@ module Mastodon config.x.cache_buster = config_for(:cache_buster) config.x.captcha = config_for(:captcha) + config.x.email = config_for(:email) config.x.mastodon = config_for(:mastodon) config.x.omniauth = config_for(:omniauth) config.x.translation = config_for(:translation) diff --git a/config/email.yml b/config/email.yml new file mode 100644 index 000000000..0480520c3 --- /dev/null +++ b/config/email.yml @@ -0,0 +1,21 @@ +# Note that these settings only apply in `production` even when other +# keys are added here. +production: + delivery_method: <%= ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp') %> + from_address: <%= ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost') %> + reply_to: <%= ENV.fetch('SMTP_REPLY_TO', nil) %> + return_path: <%= ENV.fetch('SMTP_RETURN_PATH', nil) %> + smtp_settings: + port: <%= ENV.fetch('SMTP_PORT', nil) %> + address: <%= ENV.fetch('SMTP_SERVER', nil) %> + user_name: <%= ENV.fetch('SMTP_LOGIN', nil) %> + password: <%= ENV.fetch('SMTP_PASSWORD', nil) %> + domain: <%= ENV.fetch('SMTP_DOMAIN', ENV.fetch('LOCAL_DOMAIN', nil)) %> + authentication: <%= ENV.fetch('SMTP_AUTH_METHOD', 'plain') %> + ca_file: <%= ENV.fetch('SMTP_CA_FILE', '/etc/ssl/certs/ca-certificates.crt') %> + openssl_verify_mode: <%= ENV.fetch('SMTP_OPENSSL_VERIFY_MODE', nil) %> + enable_starttls: <%= ENV.fetch('SMTP_ENABLE_STARTTLS', nil) %> + enable_starttls_auto: <%= ENV.fetch('SMTP_ENABLE_STARTTLS_AUTO', true) != 'false' %> + tls: <%= ENV.fetch('SMTP_TLS', false) == 'true' ? true : nil %> + ssl: <%= ENV.fetch('SMTP_SSL', false) == 'true' ? true : nil %> + read_timeout: 20 diff --git a/config/environments/production.rb b/config/environments/production.rb index efe422c3c..af57eec56 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -99,7 +99,7 @@ Rails.application.configure do config.action_mailer.perform_caching = false # E-mails - outgoing_email_address = ENV.fetch('SMTP_FROM_ADDRESS', 'notifications@localhost') + outgoing_email_address = config.x.email.from_address outgoing_email_domain = Mail::Address.new(outgoing_email_address).domain config.action_mailer.default_options = { @@ -107,40 +107,12 @@ Rails.application.configure do message_id: -> { "<#{Mail.random_tag}@#{outgoing_email_domain}>" }, } - config.action_mailer.default_options[:reply_to] = ENV['SMTP_REPLY_TO'] if ENV['SMTP_REPLY_TO'].present? - config.action_mailer.default_options[:return_path] = ENV['SMTP_RETURN_PATH'] if ENV['SMTP_RETURN_PATH'].present? + config.action_mailer.default_options[:reply_to] = config.x.email.reply_to if config.x.email.reply_to.present? + config.action_mailer.default_options[:return_path] = config.x.email.return_path if config.x.email.return_path.present? - enable_starttls = nil - enable_starttls_auto = nil + config.action_mailer.smtp_settings = Mastodon::EmailConfigurationHelper.smtp_settings(config.x.email.smtp_settings) - case ENV.fetch('SMTP_ENABLE_STARTTLS', nil) - when 'always' - enable_starttls = true - when 'never' - enable_starttls = false - when 'auto' - enable_starttls_auto = true - else - enable_starttls_auto = ENV['SMTP_ENABLE_STARTTLS_AUTO'] != 'false' - end - - config.action_mailer.smtp_settings = { - port: ENV.fetch('SMTP_PORT', nil), - address: ENV.fetch('SMTP_SERVER', nil), - user_name: ENV['SMTP_LOGIN'].presence, - password: ENV['SMTP_PASSWORD'].presence, - domain: ENV['SMTP_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil), - authentication: ENV['SMTP_AUTH_METHOD'] == 'none' ? nil : ENV['SMTP_AUTH_METHOD'] || :plain, - ca_file: ENV['SMTP_CA_FILE'].presence || '/etc/ssl/certs/ca-certificates.crt', - openssl_verify_mode: ENV.fetch('SMTP_OPENSSL_VERIFY_MODE', nil), - enable_starttls: enable_starttls, - enable_starttls_auto: enable_starttls_auto, - tls: ENV['SMTP_TLS'].presence && ENV['SMTP_TLS'] == 'true', - ssl: ENV['SMTP_SSL'].presence && ENV['SMTP_SSL'] == 'true', - read_timeout: 20, - } - - config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym + config.action_mailer.delivery_method = config.x.email.delivery_method.to_sym config.action_dispatch.default_headers = { 'Server' => 'Mastodon', diff --git a/lib/mastodon/email_configuration_helper.rb b/lib/mastodon/email_configuration_helper.rb new file mode 100644 index 000000000..f5014ad68 --- /dev/null +++ b/lib/mastodon/email_configuration_helper.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Mastodon + module EmailConfigurationHelper + module_function + + # Convert smtp settings from environment variables (or defaults in + # `config/email.yml`) into the format that `ActionMailer` understands + def smtp_settings(config) + enable_starttls = nil + enable_starttls_auto = nil + + case config[:enable_starttls] + when 'always' + enable_starttls = true + when 'never' + enable_starttls = false + when 'auto' + enable_starttls_auto = true + else + enable_starttls_auto = config[:enable_starttls_auto] != 'false' + end + + authentication = config[:authentication] == 'none' ? nil : (config[:authentication] || 'plain') + + config.merge( + authentication:, + enable_starttls:, + enable_starttls_auto: + ) + end + end +end diff --git a/spec/lib/mastodon/email_configuration_helper_spec.rb b/spec/lib/mastodon/email_configuration_helper_spec.rb new file mode 100644 index 000000000..c08f87d16 --- /dev/null +++ b/spec/lib/mastodon/email_configuration_helper_spec.rb @@ -0,0 +1,98 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Mastodon::EmailConfigurationHelper do + describe '#smtp_settings' do + subject { described_class } + + let(:converted_settings) { subject.smtp_settings(configuration) } + let(:base_configuration) do + { + address: 'localhost', + port: 25, + user_name: 'mastodon', + password: 'mastodon', + } + end + + context 'when `enable_starttls` is "always"' do + let(:configuration) do + base_configuration.merge({ enable_starttls: 'always' }) + end + + it 'converts this to `true`' do + expect(converted_settings[:enable_starttls]).to be true + end + end + + context 'when `enable_starttls` is "never"' do + let(:configuration) do + base_configuration.merge({ enable_starttls: 'never' }) + end + + it 'converts this to `false`' do + expect(converted_settings[:enable_starttls]).to be false + end + end + + context 'when `enable_starttls` is "auto"' do + let(:configuration) do + base_configuration.merge({ enable_starttls: 'auto' }) + end + + it 'sets `enable_starttls_auto` instead' do + expect(converted_settings[:enable_starttls]).to be_nil + expect(converted_settings[:enable_starttls_auto]).to be true + end + end + + context 'when `enable_starttls` is unset' do + context 'when `enable_starttls_auto` is unset' do + let(:configuration) { base_configuration } + + it 'sets `enable_starttls_auto` to `true`' do + expect(converted_settings[:enable_starttls_auto]).to be true + end + end + + context 'when `enable_starttls_auto` is set to "false"' do + let(:configuration) do + base_configuration.merge({ enable_starttls_auto: 'false' }) + end + + it 'sets `enable_starttls_auto` to `false`' do + expect(converted_settings[:enable_starttls_auto]).to be false + end + end + end + + context 'when `authentication` is set to "none"' do + let(:configuration) do + base_configuration.merge({ authentication: 'none' }) + end + + it 'sets `authentication` to `nil`' do + expect(converted_settings[:authentication]).to be_nil + end + end + + context 'when `authentication` is set to `login`' do + let(:configuration) do + base_configuration.merge({ authentication: 'login' }) + end + + it 'is left as-is' do + expect(converted_settings[:authentication]).to eq 'login' + end + end + + context 'when `authentication` is unset' do + let(:configuration) { base_configuration } + + it 'sets `authentication` to "plain"' do + expect(converted_settings[:authentication]).to eq 'plain' + end + end + end +end