diff --git a/elixir/apps/domain/lib/domain/auth.ex b/elixir/apps/domain/lib/domain/auth.ex index 87a75dfd0..8e437c0e1 100644 --- a/elixir/apps/domain/lib/domain/auth.ex +++ b/elixir/apps/domain/lib/domain/auth.ex @@ -49,7 +49,7 @@ defmodule Domain.Auth do def create_provider(%Accounts.Account{} = account, attrs) do Provider.Changeset.create_changeset(account, attrs) - |> Adapters.ensure_provisioned() + |> Adapters.ensure_provisioned_for_account(account) |> Repo.insert() end diff --git a/elixir/apps/domain/lib/domain/auth/adapter.ex b/elixir/apps/domain/lib/domain/auth/adapter.ex index 70ccbdcaf..6ce4c99a9 100644 --- a/elixir/apps/domain/lib/domain/auth/adapter.ex +++ b/elixir/apps/domain/lib/domain/auth/adapter.ex @@ -1,4 +1,5 @@ defmodule Domain.Auth.Adapter do + alias Domain.Accounts alias Domain.Auth.{Provider, Identity} @doc """ @@ -13,8 +14,10 @@ defmodule Domain.Auth.Adapter do It should impotently ensure that the provider is provisioned on the third-party end, eg. it can use a REST API to configure SCIM webhook and token. """ - @callback ensure_provisioned(%Ecto.Changeset{data: %Provider{}}) :: - %Ecto.Changeset{data: %Provider{}} + @callback ensure_provisioned_for_account( + %Ecto.Changeset{data: %Provider{}}, + %Accounts.Account{} + ) :: %Ecto.Changeset{data: %Provider{}} @doc """ A callback which is triggered when the provider is deleted. diff --git a/elixir/apps/domain/lib/domain/auth/adapters.ex b/elixir/apps/domain/lib/domain/auth/adapters.ex index c1507f363..1fdc6b509 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters.ex @@ -1,5 +1,6 @@ defmodule Domain.Auth.Adapters do use Supervisor + alias Domain.Accounts alias Domain.Auth.{Provider, Identity} @adapters %{ @@ -26,13 +27,16 @@ defmodule Domain.Auth.Adapters do %Ecto.Changeset{} = adapter.identity_changeset(provider, changeset) end - def ensure_provisioned(%Ecto.Changeset{changes: %{adapter: adapter}} = changeset) + def ensure_provisioned_for_account( + %Ecto.Changeset{changes: %{adapter: adapter}} = changeset, + %Accounts.Account{} = account + ) when adapter in @adapter_names do adapter = Map.fetch!(@adapters, adapter) - %Ecto.Changeset{} = adapter.ensure_provisioned(changeset) + %Ecto.Changeset{} = adapter.ensure_provisioned_for_account(changeset, account) end - def ensure_provisioned(%Ecto.Changeset{} = changeset) do + def ensure_provisioned_for_account(%Ecto.Changeset{} = changeset, %Accounts.Account{}) do changeset end diff --git a/elixir/apps/domain/lib/domain/auth/adapters/email.ex b/elixir/apps/domain/lib/domain/auth/adapters/email.ex index 7f5d5cf0d..67cbcb34b 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/email.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/email.ex @@ -1,6 +1,7 @@ defmodule Domain.Auth.Adapters.Email do use Supervisor alias Domain.Repo + alias Domain.Accounts alias Domain.Auth.{Identity, Provider, Adapter} @behaviour Adapter @@ -31,16 +32,16 @@ defmodule Domain.Auth.Adapters.Email do end @impl true - def ensure_provisioned(%Ecto.Changeset{} = changeset) do - # XXX: Re-enable this when web app will start handling email delivery again, - # we will need to verify that it's configured. - # email_disabled? = Config.fetch_config!(:outbound_email_adapter) == Domain.Mailer.NoopAdapter + def ensure_provisioned_for_account(%Ecto.Changeset{} = changeset, %Accounts.Account{} = account) do + %{ + outbound_email_adapter: outbound_email_adapter + } = Domain.Config.fetch_resolved_configs!(account.id, [:outbound_email_adapter]) - # if email_disabled? do - # Ecto.Changeset.add_error(changeset, :adapter, "email adapter is not configured") - # else - changeset - # end + if is_nil(outbound_email_adapter) do + Ecto.Changeset.add_error(changeset, :adapter, "email adapter is not configured") + else + changeset + end end @impl true diff --git a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex index a2476a61e..dbb183c7f 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/openid_connect.ex @@ -1,6 +1,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do use Supervisor alias Domain.Repo + alias Domain.Accounts alias Domain.Auth.{Identity, Provider, Adapter} alias Domain.Auth.Adapters.OpenIDConnect.{Settings, State, PKCE} require Logger @@ -28,7 +29,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnect do end @impl true - def ensure_provisioned(%Ecto.Changeset{} = changeset) do + def ensure_provisioned_for_account(%Ecto.Changeset{} = changeset, %Accounts.Account{}) do Domain.Changeset.cast_polymorphic_embed(changeset, :adapter_config, required: true, with: fn current_attrs, attrs -> diff --git a/elixir/apps/domain/lib/domain/auth/adapters/token.ex b/elixir/apps/domain/lib/domain/auth/adapters/token.ex index 7a29591c8..1f146dac3 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/token.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/token.ex @@ -5,6 +5,7 @@ defmodule Domain.Auth.Adapters.Token do """ use Supervisor alias Domain.Repo + alias Domain.Accounts alias Domain.Auth.{Identity, Provider, Adapter} alias Domain.Auth.Adapters.Token.State @@ -65,7 +66,7 @@ defmodule Domain.Auth.Adapters.Token do end @impl true - def ensure_provisioned(%Ecto.Changeset{} = changeset) do + def ensure_provisioned_for_account(%Ecto.Changeset{} = changeset, %Accounts.Account{}) do changeset end diff --git a/elixir/apps/domain/lib/domain/auth/adapters/userpass.ex b/elixir/apps/domain/lib/domain/auth/adapters/userpass.ex index a6f6d145c..8ee7e98fb 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/userpass.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/userpass.ex @@ -5,6 +5,7 @@ defmodule Domain.Auth.Adapters.UserPass do """ use Supervisor alias Domain.Repo + alias Domain.Accounts alias Domain.Auth.{Identity, Provider, Adapter} alias Domain.Auth.Adapters.UserPass.Password @@ -56,7 +57,7 @@ defmodule Domain.Auth.Adapters.UserPass do end @impl true - def ensure_provisioned(%Ecto.Changeset{} = changeset) do + def ensure_provisioned_for_account(%Ecto.Changeset{} = changeset, %Accounts.Account{}) do changeset end diff --git a/elixir/apps/domain/lib/domain/config/definitions.ex b/elixir/apps/domain/lib/domain/config/definitions.ex index 78a745f1b..1cfec6497 100644 --- a/elixir/apps/domain/lib/domain/config/definitions.ex +++ b/elixir/apps/domain/lib/domain/config/definitions.ex @@ -509,7 +509,7 @@ defmodule Domain.Config.Definitions do ) @doc """ - Method to use for sending outbound email. + Method to use for sending outbound email. If not set, sending emails will be disabled (default). """ defconfig( :outbound_email_adapter, @@ -533,11 +533,10 @@ defmodule Domain.Config.Definitions do Swoosh.Adapters.Sendinblue, Swoosh.Adapters.Sendmail, Swoosh.Adapters.SocketLabs, - Swoosh.Adapters.SparkPost, - Domain.Mailer.NoopAdapter + Swoosh.Adapters.SparkPost ] )}, - default: Domain.Mailer.NoopAdapter + default: nil ) @doc """ diff --git a/elixir/apps/domain/lib/domain/mailer/noop_adapter.ex b/elixir/apps/domain/lib/domain/mailer/noop_adapter.ex deleted file mode 100644 index 8f592ffd3..000000000 --- a/elixir/apps/domain/lib/domain/mailer/noop_adapter.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule Domain.Mailer.NoopAdapter do - @moduledoc """ - When mailer is not configure, use noop adapter as a drop-in replacement - so that we don't have to add conditional logic to every single call to - `Web.Mailer.deliver/2`. - - # XXX: Having this module in the Domain app is a workaround for the following issue: - # https://github.com/elixir-lang/elixir/issues/12777 - # Move this module back to the Web app once this is fixed. - """ - use Swoosh.Adapter - require Logger - - @impl true - def deliver(email, _opts) do - Logger.info("Mailer is not configured, dropping email: #{inspect(email)}") - {:ok, %{}} - end -end diff --git a/elixir/apps/domain/priv/repo/seeds.exs b/elixir/apps/domain/priv/repo/seeds.exs index 213bf8b56..5c1b4e0b4 100644 --- a/elixir/apps/domain/priv/repo/seeds.exs +++ b/elixir/apps/domain/priv/repo/seeds.exs @@ -1,5 +1,9 @@ alias Domain.{Repo, Accounts, Auth, Actors, Relays, Gateways, Resources} +# Seeds can be run both with MIX_ENV=prod and MIX_ENV=test, for test env we don't have +# an adapter configured and creation of email provider will fail, so we will override it here. +System.put_env("OUTBOUND_EMAIL_ADAPTER", "Elixir.Swoosh.Adapters.Postmark") + # This function is used to update fields if STATIC_SEEDS is set, # which helps with static docker-compose environment for local development. maybe_repo_update = fn resource, values -> diff --git a/elixir/apps/domain/test/domain/actors_test.exs b/elixir/apps/domain/test/domain/actors_test.exs index 9610676e2..1daf8f0cc 100644 --- a/elixir/apps/domain/test/domain/actors_test.exs +++ b/elixir/apps/domain/test/domain/actors_test.exs @@ -193,6 +193,8 @@ defmodule Domain.ActorsTest do describe "create_actor/4" do setup do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) provider_identifier = AuthFixtures.random_provider_identifier(provider) @@ -282,6 +284,8 @@ defmodule Domain.ActorsTest do describe "create_actor/5" do setup do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) provider_identifier = AuthFixtures.random_provider_identifier(provider) @@ -452,6 +456,8 @@ defmodule Domain.ActorsTest do Task.async(fn -> allow_child_sandbox_access(test_pid) + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) @@ -657,6 +663,8 @@ defmodule Domain.ActorsTest do Task.async(fn -> allow_child_sandbox_access(test_pid) + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) diff --git a/elixir/apps/domain/test/domain/auth/adapters/email_test.exs b/elixir/apps/domain/test/domain/auth/adapters/email_test.exs index 0da90e0ef..72fc276f4 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/email_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/email_test.exs @@ -6,6 +6,8 @@ defmodule Domain.Auth.Adapters.EmailTest do describe "identity_changeset/2" do setup do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) changeset = %Auth.Identity{} |> Ecto.Changeset.change() @@ -38,10 +40,20 @@ defmodule Domain.Auth.Adapters.EmailTest do end end - describe "ensure_provisioned/1" do + describe "ensure_provisioned_for_account/2" do test "returns changeset as is" do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + + account = AccountsFixtures.create_account() changeset = %Ecto.Changeset{} - assert ensure_provisioned(changeset) == changeset + assert ensure_provisioned_for_account(changeset, account) == changeset + end + + test "returns error when email adapter is not configured" do + account = AccountsFixtures.create_account() + changeset = %Ecto.Changeset{} + changeset = ensure_provisioned_for_account(changeset, account) + assert changeset.errors == [adapter: {"email adapter is not configured", []}] end end @@ -74,6 +86,8 @@ defmodule Domain.Auth.Adapters.EmailTest do describe "verify_secret/2" do setup do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) identity = AuthFixtures.create_identity(account: account, provider: provider) diff --git a/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs b/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs index a0d5053d8..7ee45de28 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/openid_connect_test.exs @@ -35,15 +35,16 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do end end - describe "ensure_provisioned/1" do + describe "ensure_provisioned_for_account/2" do test "returns changeset errors in invalid adapter config" do - changeset = Ecto.Changeset.change(%Auth.Provider{}, %{}) - assert %Ecto.Changeset{} = changeset = ensure_provisioned(changeset) + account = AccountsFixtures.create_account() + changeset = Ecto.Changeset.change(%Auth.Provider{account_id: account.id}, %{}) + assert %Ecto.Changeset{} = changeset = ensure_provisioned_for_account(changeset, account) assert errors_on(changeset) == %{adapter_config: ["can't be blank"]} attrs = AuthFixtures.provider_attrs(adapter: :openid_connect, adapter_config: %{}) - changeset = Ecto.Changeset.change(%Auth.Provider{}, attrs) - assert %Ecto.Changeset{} = changeset = ensure_provisioned(changeset) + changeset = Ecto.Changeset.change(%Auth.Provider{account_id: account.id}, attrs) + assert %Ecto.Changeset{} = changeset = ensure_provisioned_for_account(changeset, account) assert errors_on(changeset) == %{ adapter_config: %{ @@ -70,7 +71,7 @@ defmodule Domain.Auth.Adapters.OpenIDConnectTest do changeset = Ecto.Changeset.change(%Auth.Provider{account_id: account.id}, attrs) - assert %Ecto.Changeset{} = changeset = ensure_provisioned(changeset) + assert %Ecto.Changeset{} = changeset = ensure_provisioned_for_account(changeset, account) assert {:ok, provider} = Repo.insert(changeset) assert provider.name == attrs.name diff --git a/elixir/apps/domain/test/domain/auth/adapters/token_test.exs b/elixir/apps/domain/test/domain/auth/adapters/token_test.exs index e016ddc0a..cb4e38073 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/token_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/token_test.exs @@ -67,10 +67,11 @@ defmodule Domain.Auth.Adapters.TokenTest do end end - describe "ensure_provisioned/1" do + describe "ensure_provisioned_for_account/2" do test "returns changeset as is" do + account = AccountsFixtures.create_account() changeset = %Ecto.Changeset{} - assert ensure_provisioned(changeset) == changeset + assert ensure_provisioned_for_account(changeset, account) == changeset end end diff --git a/elixir/apps/domain/test/domain/auth/adapters/userpass_test.exs b/elixir/apps/domain/test/domain/auth/adapters/userpass_test.exs index 0b4a526ba..4d3024891 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/userpass_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/userpass_test.exs @@ -91,10 +91,11 @@ defmodule Domain.Auth.Adapters.UserPassTest do end end - describe "ensure_provisioned/1" do + describe "ensure_provisioned_for_account/2" do test "returns changeset as is" do + account = AccountsFixtures.create_account() changeset = %Ecto.Changeset{} - assert ensure_provisioned(changeset) == changeset + assert ensure_provisioned_for_account(changeset, account) == changeset end end diff --git a/elixir/apps/domain/test/domain/auth_test.exs b/elixir/apps/domain/test/domain/auth_test.exs index 23d8c3e79..d84b5c00f 100644 --- a/elixir/apps/domain/test/domain/auth_test.exs +++ b/elixir/apps/domain/test/domain/auth_test.exs @@ -14,6 +14,7 @@ defmodule Domain.AuthTest do test "returns error when provider is disabled" do account = AccountsFixtures.create_account() AuthFixtures.create_userpass_provider(account: account) + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) identity = @@ -32,6 +33,7 @@ defmodule Domain.AuthTest do test "returns error when provider is deleted" do account = AccountsFixtures.create_account() AuthFixtures.create_userpass_provider(account: account) + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) identity = @@ -48,6 +50,7 @@ defmodule Domain.AuthTest do end test "returns provider" do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider() assert {:ok, fetched_provider} = fetch_active_provider_by_id(provider.id) assert fetched_provider.id == provider.id @@ -58,6 +61,7 @@ defmodule Domain.AuthTest do test "returns active providers for a given account" do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) userpass_provider = AuthFixtures.create_userpass_provider(account: account) email_provider = AuthFixtures.create_email_provider(account: account) token_provider = AuthFixtures.create_token_provider(account: account) @@ -124,7 +128,7 @@ defmodule Domain.AuthTest do test "returns error if email provider is already enabled", %{ account: account } do - # email, userpass, token + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) AuthFixtures.create_email_provider(account: account) attrs = AuthFixtures.provider_attrs(adapter: :email) assert {:error, changeset} = create_provider(account, attrs) @@ -135,7 +139,6 @@ defmodule Domain.AuthTest do test "returns error if userpass provider is already enabled", %{ account: account } do - # userpass, userpass, token AuthFixtures.create_userpass_provider(account: account) attrs = AuthFixtures.provider_attrs(adapter: :userpass) assert {:error, changeset} = create_provider(account, attrs) @@ -176,6 +179,8 @@ defmodule Domain.AuthTest do } do attrs = AuthFixtures.provider_attrs() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + assert {:ok, provider} = create_provider(account, attrs) assert provider.name == attrs.name @@ -186,15 +191,15 @@ defmodule Domain.AuthTest do assert is_nil(provider.deleted_at) end - # test "returns error when email provider is disabled", %{ - # account: account - # } do - # Domain.Config.put_system_env_override(:outbound_email_adapter, Domain.Mailer.NoopAdapter) - # attrs = AuthFixtures.provider_attrs() + test "returns error when email provider is disabled", %{ + account: account + } do + Domain.Config.put_system_env_override(:outbound_email_adapter, nil) + attrs = AuthFixtures.provider_attrs() - # assert {:error, changeset} = create_provider(account, attrs) - # assert errors_on(changeset) == %{adapter: ["email adapter is not configured"]} - # end + assert {:error, changeset} = create_provider(account, attrs) + assert errors_on(changeset) == %{adapter: ["email adapter is not configured"]} + end end describe "create_provider/3" do @@ -233,6 +238,7 @@ defmodule Domain.AuthTest do describe "disable_provider/2" do setup do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) actor = @@ -379,6 +385,7 @@ defmodule Domain.AuthTest do identity = AuthFixtures.create_identity(account: account, actor: actor) subject = AuthFixtures.create_subject(identity) + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) {:ok, provider} = disable_provider(provider, subject) @@ -413,6 +420,7 @@ defmodule Domain.AuthTest do test "does not allow to enable providers in other accounts", %{ subject: subject } do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider() assert enable_provider(provider, subject) == {:error, :not_found} end @@ -432,6 +440,7 @@ defmodule Domain.AuthTest do describe "delete_provider/2" do setup do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) actor = @@ -582,6 +591,7 @@ defmodule Domain.AuthTest do describe "create_identity/3" do test "creates an identity" do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) provider_identifier = AuthFixtures.random_provider_identifier(provider) @@ -608,6 +618,7 @@ defmodule Domain.AuthTest do test "returns error when identifier is invalid" do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) actor = @@ -630,6 +641,7 @@ defmodule Domain.AuthTest do describe "replace_identity/3" do setup do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) actor = @@ -719,6 +731,7 @@ defmodule Domain.AuthTest do describe "delete_identity/2" do setup do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) actor = @@ -837,6 +850,7 @@ defmodule Domain.AuthTest do describe "sign_in/5" do setup do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) user_agent = AuthFixtures.user_agent() remote_ip = AuthFixtures.remote_ip() @@ -1318,6 +1332,7 @@ defmodule Domain.AuthTest do describe "sign_in/3" do setup do account = AccountsFixtures.create_account() + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) user_agent = AuthFixtures.user_agent() remote_ip = AuthFixtures.remote_ip() @@ -1535,6 +1550,7 @@ defmodule Domain.AuthTest do test "returns error when subject has no access to given provider", %{ subject: subject } do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider() assert ensure_has_access_to(subject, provider) == {:error, :unauthorized} end @@ -1543,6 +1559,7 @@ defmodule Domain.AuthTest do subject: subject, account: account } do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) provider = AuthFixtures.create_email_provider(account: account) assert ensure_has_access_to(subject, provider) == :ok end diff --git a/elixir/apps/web/lib/web/mailer.ex b/elixir/apps/web/lib/web/mailer.ex index 81e496689..aede7be7a 100644 --- a/elixir/apps/web/lib/web/mailer.ex +++ b/elixir/apps/web/lib/web/mailer.ex @@ -1,6 +1,34 @@ defmodule Web.Mailer do - use Swoosh.Mailer, otp_app: :web + alias Swoosh.Mailer alias Swoosh.Email + require Logger + + @doc """ + Delivers an email via configured Swoosh adapter. + + If adapter is not configured or is set to nil, the delivery will be ignored and + function will return `{:ok, %{}}`. + + Notice: this code is copied from `Swoosh.Mailer.deliver/2` and modified to + not send emails if adapter is not configured. This is needed to avoid + custom adapter implementation that does nothing. + """ + def deliver(email, config \\ []) do + opts = Mailer.parse_config(:web, __MODULE__, [], config) + metadata = %{email: email, config: config, mailer: __MODULE__} + + if opts[:adapter] do + :telemetry.span([:swoosh, :deliver], metadata, fn -> + case Mailer.deliver(email, opts) do + {:ok, result} -> {{:ok, result}, Map.put(metadata, :result, result)} + {:error, error} -> {{:error, error}, Map.put(metadata, :error, error)} + end + end) + else + Logger.info("Emails are not configured", email_subject: inspect(email.subject)) + {:ok, %{}} + end + end defp render_template(view, template, format, assigns) do heex = apply(view, String.to_atom("#{template}_#{format}"), [assigns]) diff --git a/elixir/apps/web/test/web/acceptance/auth/email_test.exs b/elixir/apps/web/test/web/acceptance/auth/email_test.exs index ae98f5bdd..6ec053205 100644 --- a/elixir/apps/web/test/web/acceptance/auth/email_test.exs +++ b/elixir/apps/web/test/web/acceptance/auth/email_test.exs @@ -3,6 +3,8 @@ defmodule Web.Acceptance.Auth.EmailTest do alias Domain.{AccountsFixtures, AuthFixtures} feature "renders success on invalid email to prevent enumeration attacks", %{session: session} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() AuthFixtures.create_email_provider(account: account) @@ -17,6 +19,8 @@ defmodule Web.Acceptance.Auth.EmailTest do end feature "allows to log in using email link", %{session: session} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) diff --git a/elixir/apps/web/test/web/acceptance/auth_test.exs b/elixir/apps/web/test/web/acceptance/auth_test.exs index bf881d56b..c4a7ca158 100644 --- a/elixir/apps/web/test/web/acceptance/auth_test.exs +++ b/elixir/apps/web/test/web/acceptance/auth_test.exs @@ -3,6 +3,8 @@ defmodule Web.Acceptance.AuthTest do alias Domain.{AccountsFixtures, AuthFixtures} feature "renders all sign in options", %{session: session} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() AuthFixtures.create_userpass_provider(account: account) diff --git a/elixir/apps/web/test/web/controllers/auth_controller_test.exs b/elixir/apps/web/test/web/controllers/auth_controller_test.exs index 5f6b6a453..5dcab09fe 100644 --- a/elixir/apps/web/test/web/controllers/auth_controller_test.exs +++ b/elixir/apps/web/test/web/controllers/auth_controller_test.exs @@ -2,6 +2,11 @@ defmodule Web.AuthControllerTest do use Web.ConnCase, async: true alias Domain.{AccountsFixtures, AuthFixtures} + setup do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + %{} + end + describe "verify_credentials/2" do test "redirects with an error when provider does not exist", %{conn: conn} do account_id = Ecto.UUID.generate() @@ -558,6 +563,8 @@ defmodule Web.AuthControllerTest do describe "sign_out/2" do test "redirects to the sign in page and renews the session", %{conn: conn} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) identity = AuthFixtures.create_identity(account: account, provider: provider) @@ -573,6 +580,8 @@ defmodule Web.AuthControllerTest do end test "broadcasts to the given live_socket_id", %{conn: conn} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) identity = AuthFixtures.create_identity(account: account, provider: provider) diff --git a/elixir/apps/web/test/web/live/auth_live/email_live_test.exs b/elixir/apps/web/test/web/live/auth_live/email_live_test.exs index 716e6939b..b579ad00d 100644 --- a/elixir/apps/web/test/web/live/auth_live/email_live_test.exs +++ b/elixir/apps/web/test/web/live/auth_live/email_live_test.exs @@ -3,6 +3,8 @@ defmodule Web.Auth.EmailLiveTest do alias Domain.{AccountsFixtures, AuthFixtures} test "renders email page", %{conn: conn} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() provider = AuthFixtures.create_email_provider(account: account) diff --git a/elixir/apps/web/test/web/live/auth_live/providers_live_test.exs b/elixir/apps/web/test/web/live/auth_live/providers_live_test.exs index 02e6cd2d7..8e4195afc 100644 --- a/elixir/apps/web/test/web/live/auth_live/providers_live_test.exs +++ b/elixir/apps/web/test/web/live/auth_live/providers_live_test.exs @@ -3,6 +3,8 @@ defmodule Web.Auth.ProvidersLiveTest do alias Domain.{AccountsFixtures, AuthFixtures} test "renders active providers on the page", %{conn: conn} do + Domain.Config.put_system_env_override(:outbound_email_adapter, Swoosh.Adapters.Postmark) + account = AccountsFixtures.create_account() email_provider = AuthFixtures.create_email_provider(account: account)