diff --git a/elixir/README.md b/elixir/README.md index 3dd1c8b60..01f608a4a 100644 --- a/elixir/README.md +++ b/elixir/README.md @@ -418,7 +418,7 @@ iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)5> context = %Domain.Aut iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)6> {:ok, identity} = Domain.Auth.Adapters.Email.request_sign_in_token(identity, context) {:ok, ...} -iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)7> Web.Mailer.AuthEmail.sign_in_link_email(identity) |> Web.Mailer.deliver() +iex(web@web-3vmw.us-east1-d.c.firezone-staging.internal)7> Domain.Mailer.AuthEmail.sign_in_link_email(identity) |> Domain.Mailer.deliver() {:ok, %{id: "d24dbe9a-d0f5-4049-ac0d-0df793725a80"}} ``` diff --git a/elixir/apps/api/lib/api/plugs/auth.ex b/elixir/apps/api/lib/api/plugs/auth.ex index 73140e140..7d5868ce1 100644 --- a/elixir/apps/api/lib/api/plugs/auth.ex +++ b/elixir/apps/api/lib/api/plugs/auth.ex @@ -11,12 +11,6 @@ defmodule API.Plugs.Auth do assign(conn, :subject, subject) else _ -> - # conn - # |> put_resp_content_type("application/json") - # |> send_resp(401, Jason.encode!(%{"error" => "invalid_access_token"})) - # |> halt() - - # TODO: BRIAN - Confirm that this change won't break anything with the clients or gateways conn |> put_status(401) |> Phoenix.Controller.put_view(json: API.ErrorJSON) diff --git a/elixir/apps/domain/.formatter.exs b/elixir/apps/domain/.formatter.exs index 9819eca3c..0e0518ee2 100644 --- a/elixir/apps/domain/.formatter.exs +++ b/elixir/apps/domain/.formatter.exs @@ -1,7 +1,8 @@ [ import_deps: [ :ecto, - :plug + :plug, + :phoenix ], inputs: [ "*.{heex,ex,exs}", diff --git a/elixir/apps/domain/lib/domain/actors.ex b/elixir/apps/domain/lib/domain/actors.ex index a05571057..2dc83b11f 100644 --- a/elixir/apps/domain/lib/domain/actors.ex +++ b/elixir/apps/domain/lib/domain/actors.ex @@ -402,6 +402,16 @@ defmodule Domain.Actors do end) end + def all_admins_for_account!(%Accounts.Account{} = account, opts \\ []) do + {preload, _opts} = Keyword.pop(opts, :preload, []) + + Actor.Query.not_disabled() + |> Actor.Query.by_account_id(account.id) + |> Actor.Query.by_type(:account_admin_user) + |> Repo.all(opts) + |> Repo.preload(preload) + end + def list_actors(%Auth.Subject{} = subject, opts \\ []) do with :ok <- Auth.ensure_has_permissions(subject, Authorizer.manage_actors_permission()) do Actor.Query.not_deleted() diff --git a/elixir/apps/domain/lib/domain/application.ex b/elixir/apps/domain/lib/domain/application.ex index a0d5d7c99..798d5dc44 100644 --- a/elixir/apps/domain/lib/domain/application.ex +++ b/elixir/apps/domain/lib/domain/application.ex @@ -30,6 +30,8 @@ defmodule Domain.Application do Domain.Gateways, Domain.Clients, Domain.Billing, + Domain.Mailer, + Domain.Mailer.RateLimiter, # Observability Domain.Telemetry diff --git a/elixir/apps/domain/lib/domain/auth.ex b/elixir/apps/domain/lib/domain/auth.ex index 4ebb9e870..02b98dff0 100644 --- a/elixir/apps/domain/lib/domain/auth.ex +++ b/elixir/apps/domain/lib/domain/auth.ex @@ -352,6 +352,12 @@ defmodule Domain.Auth do end end + def all_identities_for(%Actors.Actor{} = actor, opts \\ []) do + Identity.Query.not_deleted() + |> Identity.Query.by_actor_id(actor.id) + |> Repo.all(opts) + end + def list_identities_for(%Actors.Actor{} = actor, %Subject{} = subject, opts \\ []) do with :ok <- ensure_has_permissions(subject, Authorizer.manage_identities_permission()) do Identity.Query.not_deleted() @@ -373,6 +379,19 @@ defmodule Domain.Auth do |> Repo.all() end + def get_identity_email(%Identity{} = identity) do + provider_email(identity) || identity.provider_identifier + end + + def identity_has_email?(%Identity{} = identity) do + not is_nil(provider_email(identity)) or identity.provider.adapter == :email or + identity.provider_identifier =~ "@" + end + + defp provider_email(%Identity{} = identity) do + get_in(identity.provider_state, ["userinfo", "email"]) + end + # used by IdP adapters def upsert_identity(%Actors.Actor{} = actor, %Provider{} = provider, attrs) do Identity.Changeset.create_identity(actor, provider, attrs) diff --git a/elixir/apps/domain/lib/domain/auth/adapter/openid_connect/directory_sync.ex b/elixir/apps/domain/lib/domain/auth/adapter/openid_connect/directory_sync.ex index d4455cd59..1fba3c752 100644 --- a/elixir/apps/domain/lib/domain/auth/adapter/openid_connect/directory_sync.ex +++ b/elixir/apps/domain/lib/domain/auth/adapter/openid_connect/directory_sync.ex @@ -209,6 +209,7 @@ defmodule Domain.Auth.Adapter.OpenIDConnect.DirectorySync do Auth.Provider.Changeset.sync_requires_manual_intervention(provider, user_message) |> Domain.Repo.update!() + |> send_sync_error_email() :error @@ -224,6 +225,7 @@ defmodule Domain.Auth.Adapter.OpenIDConnect.DirectorySync do Auth.Provider.Changeset.sync_failed(provider, user_message) |> Domain.Repo.update!() + |> send_sync_error_email() |> log_sync_error(log_message) :error @@ -382,6 +384,37 @@ defmodule Domain.Auth.Adapter.OpenIDConnect.DirectorySync do end) end + defp send_sync_error_email(provider) do + provider = Repo.preload(provider, :account) + + if sync_error_email_sent_today?(provider) do + Logger.debug("Sync error email already sent today") + + provider + else + Domain.Actors.all_admins_for_account!(provider.account, preload: :identities) + |> Enum.flat_map(fn actor -> + Enum.map(actor.identities, &Domain.Auth.get_identity_email(&1)) + end) + |> Enum.uniq() + |> Enum.each(fn email -> + Domain.Mailer.SyncEmail.sync_error_email(provider, email) + |> Domain.Mailer.deliver() + end) + + Auth.Provider.Changeset.sync_error_emailed(provider) + |> Domain.Repo.update!() + end + end + + defp sync_error_email_sent_today?(provider) do + if last_email_time = provider.sync_error_emailed_at do + DateTime.diff(DateTime.utc_now(), last_email_time, :hour) < 24 + else + false + end + end + if Mix.env() == :test do # We need this function to reuse the connection that was checked out in a parent process. # diff --git a/elixir/apps/domain/lib/domain/auth/adapters/okta/api_client.ex b/elixir/apps/domain/lib/domain/auth/adapters/okta/api_client.ex index ebbf87e0b..f96f93bde 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/okta/api_client.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/okta/api_client.ex @@ -100,6 +100,7 @@ defmodule Domain.Auth.Adapters.Okta.APIClient do end end + # TODO: Need to catch 401/403 specifically when error message is in header defp list(uri, headers, api_token) do headers = headers ++ [{"Authorization", "Bearer #{api_token}"}] request = Finch.build(:get, uri, headers) diff --git a/elixir/apps/domain/lib/domain/auth/adapters/okta/jobs/sync_directory.ex b/elixir/apps/domain/lib/domain/auth/adapters/okta/jobs/sync_directory.ex index ba5c9fa0a..4c13e58ee 100644 --- a/elixir/apps/domain/lib/domain/auth/adapters/okta/jobs/sync_directory.ex +++ b/elixir/apps/domain/lib/domain/auth/adapters/okta/jobs/sync_directory.ex @@ -46,6 +46,16 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectory do message = "#{error_code} => #{error_summary}" {:error, message, "Okta API returned #{status}: #{message}"} + # TODO: Okta API client needs to be updated to pull message from header + {:error, {401, ""}} -> + message = "401 - Unauthorized" + {:error, message, message} + + # TODO: Okta API client needs to be updated to pull message from header + {:error, {403, ""}} -> + message = "403 - Forbidden" + {:error, message, message} + {:error, :retry_later} -> message = "Okta API is temporarily unavailable" {:error, message, message} diff --git a/elixir/apps/domain/lib/domain/auth/provider.ex b/elixir/apps/domain/lib/domain/auth/provider.ex index 514638b2a..52bcc83d9 100644 --- a/elixir/apps/domain/lib/domain/auth/provider.ex +++ b/elixir/apps/domain/lib/domain/auth/provider.ex @@ -23,6 +23,7 @@ defmodule Domain.Auth.Provider do field :last_sync_error, :string field :last_synced_at, :utc_datetime_usec field :sync_disabled_at, :utc_datetime_usec + field :sync_error_emailed_at, :utc_datetime_usec field :disabled_at, :utc_datetime_usec field :deleted_at, :utc_datetime_usec diff --git a/elixir/apps/domain/lib/domain/auth/provider/changeset.ex b/elixir/apps/domain/lib/domain/auth/provider/changeset.ex index 2b3165eb0..f939ee54d 100644 --- a/elixir/apps/domain/lib/domain/auth/provider/changeset.ex +++ b/elixir/apps/domain/lib/domain/auth/provider/changeset.ex @@ -5,7 +5,7 @@ defmodule Domain.Auth.Provider.Changeset do @create_fields ~w[id name adapter provisioner adapter_config adapter_state disabled_at]a @update_fields ~w[name adapter_config - last_syncs_failed last_sync_error sync_disabled_at + last_syncs_failed last_sync_error sync_disabled_at sync_error_emailed_at adapter_state provisioner disabled_at deleted_at]a @required_fields ~w[name adapter adapter_config provisioner]a @@ -47,9 +47,9 @@ defmodule Domain.Auth.Provider.Changeset do provider |> change() |> put_change(:last_synced_at, DateTime.utc_now()) - |> put_change(:last_sync_error, nil) |> put_change(:last_syncs_failed, 0) |> put_change(:sync_disabled_at, nil) + |> put_change(:sync_error_emailed_at, nil) end def sync_failed(%Provider{} = provider, error) do @@ -62,6 +62,12 @@ defmodule Domain.Auth.Provider.Changeset do |> put_change(:last_syncs_failed, last_syncs_failed + 1) end + def sync_error_emailed(%Provider{} = provider) do + provider + |> change() + |> put_change(:sync_error_emailed_at, DateTime.utc_now()) + end + def sync_requires_manual_intervention(%Provider{} = provider, error) do sync_failed(provider, error) |> put_change(:sync_disabled_at, DateTime.utc_now()) diff --git a/elixir/apps/domain/lib/domain/cldr.ex b/elixir/apps/domain/lib/domain/cldr.ex new file mode 100644 index 000000000..7c67c4c7a --- /dev/null +++ b/elixir/apps/domain/lib/domain/cldr.ex @@ -0,0 +1,5 @@ +defmodule Domain.CLDR do + use Cldr, + locales: ["en"], + providers: [Cldr.Number, Cldr.Calendar, Cldr.DateTime] +end diff --git a/elixir/apps/web/lib/web/mailer.ex b/elixir/apps/domain/lib/domain/mailer.ex similarity index 91% rename from elixir/apps/web/lib/web/mailer.ex rename to elixir/apps/domain/lib/domain/mailer.ex index e6b708dac..44b1c7dea 100644 --- a/elixir/apps/web/lib/web/mailer.ex +++ b/elixir/apps/domain/lib/domain/mailer.ex @@ -1,8 +1,8 @@ -defmodule Web.Mailer do +defmodule Domain.Mailer do use Supervisor alias Swoosh.Mailer alias Swoosh.Email - alias Web.Mailer.RateLimiter + alias Domain.Mailer.RateLimiter require Logger def start_link(arg) do @@ -42,7 +42,7 @@ defmodule Web.Mailer do custom adapter implementation that does nothing. """ def deliver(email, config \\ []) do - opts = Mailer.parse_config(:web, __MODULE__, [], config) + opts = Mailer.parse_config(:domain, __MODULE__, [], config) metadata = %{email: email, config: config, mailer: __MODULE__} if opts[:adapter] do @@ -80,14 +80,14 @@ defmodule Web.Mailer do end def active? do - mailer_config = Domain.Config.fetch_env!(:web, Web.Mailer) + mailer_config = Domain.Config.fetch_env!(:domain, Domain.Mailer) mailer_config[:from_email] && mailer_config[:adapter] end def default_email do # Fail hard if email not configured from_email = - Domain.Config.fetch_env!(:web, Web.Mailer) + Domain.Config.fetch_env!(:domain, Domain.Mailer) |> Keyword.fetch!(:from_email) Email.new() diff --git a/elixir/apps/web/lib/web/mailer/auth_email.ex b/elixir/apps/domain/lib/domain/mailer/auth_email.ex similarity index 71% rename from elixir/apps/web/lib/web/mailer/auth_email.ex rename to elixir/apps/domain/lib/domain/mailer/auth_email.ex index 6675e99bf..e7c604597 100644 --- a/elixir/apps/web/lib/web/mailer/auth_email.ex +++ b/elixir/apps/domain/lib/domain/mailer/auth_email.ex @@ -1,7 +1,7 @@ -defmodule Web.Mailer.AuthEmail do - use Web, :html +defmodule Domain.Mailer.AuthEmail do import Swoosh.Email - import Web.Mailer + import Domain.Mailer + import Phoenix.Template, only: [embed_templates: 2] embed_templates "auth_email/*.html", suffix: "_html" embed_templates "auth_email/*.text", suffix: "_text" @@ -12,7 +12,7 @@ defmodule Web.Mailer.AuthEmail do user_agent, remote_ip ) do - sign_in_form_url = url(~p"/#{account}") + sign_in_form_url = url("/#{account.slug}") default_email() |> subject("Welcome to Firezone") @@ -40,11 +40,12 @@ defmodule Web.Mailer.AuthEmail do sign_in_url = url( - ~p"/#{identity.account}/sign_in/providers/#{identity.provider_id}/verify_sign_in_token?#{params}" + "/#{identity.account.slug}/sign_in/providers/#{identity.provider_id}/verify_sign_in_token", + params ) sign_in_token_created_at = - Cldr.DateTime.to_string!(identity.provider_state["token_created_at"], Web.CLDR, + Cldr.DateTime.to_string!(identity.provider_state["token_created_at"], Domain.CLDR, format: :short ) <> " UTC" @@ -69,11 +70,27 @@ defmodule Web.Mailer.AuthEmail do ) do default_email() |> subject("Welcome to Firezone") - |> to(get_identity_email(identity)) + |> to(Domain.Auth.get_identity_email(identity)) |> render_body(__MODULE__, :new_user, account: account, identity: identity, subject: subject ) end + + def url(path, params \\ %{}) do + Domain.Config.fetch_env!(:domain, :web_external_url) + |> URI.parse() + |> URI.append_path(path) + |> maybe_append_query(params) + |> URI.to_string() + end + + def maybe_append_query(uri, params) do + if Enum.empty?(params) do + uri + else + URI.append_query(uri, URI.encode_query(params)) + end + end end diff --git a/elixir/apps/web/lib/web/mailer/auth_email/new_user.html.heex b/elixir/apps/domain/lib/domain/mailer/auth_email/new_user.html.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/auth_email/new_user.html.heex rename to elixir/apps/domain/lib/domain/mailer/auth_email/new_user.html.eex diff --git a/elixir/apps/web/lib/web/mailer/auth_email/new_user.text.heex b/elixir/apps/domain/lib/domain/mailer/auth_email/new_user.text.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/auth_email/new_user.text.heex rename to elixir/apps/domain/lib/domain/mailer/auth_email/new_user.text.eex diff --git a/elixir/apps/web/lib/web/mailer/auth_email/sign_in_link.html.heex b/elixir/apps/domain/lib/domain/mailer/auth_email/sign_in_link.html.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/auth_email/sign_in_link.html.heex rename to elixir/apps/domain/lib/domain/mailer/auth_email/sign_in_link.html.eex diff --git a/elixir/apps/web/lib/web/mailer/auth_email/sign_in_link.text.heex b/elixir/apps/domain/lib/domain/mailer/auth_email/sign_in_link.text.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/auth_email/sign_in_link.text.heex rename to elixir/apps/domain/lib/domain/mailer/auth_email/sign_in_link.text.eex diff --git a/elixir/apps/web/lib/web/mailer/auth_email/sign_up_link.html.heex b/elixir/apps/domain/lib/domain/mailer/auth_email/sign_up_link.html.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/auth_email/sign_up_link.html.heex rename to elixir/apps/domain/lib/domain/mailer/auth_email/sign_up_link.html.eex diff --git a/elixir/apps/web/lib/web/mailer/auth_email/sign_up_link.text.heex b/elixir/apps/domain/lib/domain/mailer/auth_email/sign_up_link.text.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/auth_email/sign_up_link.text.heex rename to elixir/apps/domain/lib/domain/mailer/auth_email/sign_up_link.text.eex diff --git a/elixir/apps/web/lib/web/mailer/beta_email.ex b/elixir/apps/domain/lib/domain/mailer/beta_email.ex similarity index 85% rename from elixir/apps/web/lib/web/mailer/beta_email.ex rename to elixir/apps/domain/lib/domain/mailer/beta_email.ex index 8696bf630..d55b0fa21 100644 --- a/elixir/apps/web/lib/web/mailer/beta_email.ex +++ b/elixir/apps/domain/lib/domain/mailer/beta_email.ex @@ -1,7 +1,7 @@ -defmodule Web.Mailer.BetaEmail do - use Web, :html +defmodule Domain.Mailer.BetaEmail do import Swoosh.Email - import Web.Mailer + import Domain.Mailer + import Phoenix.Template, only: [embed_templates: 2] embed_templates "beta_email/*.text", suffix: "_text" diff --git a/elixir/apps/web/lib/web/mailer/beta_email/rest_api_request.text.heex b/elixir/apps/domain/lib/domain/mailer/beta_email/rest_api_request.text.eex similarity index 100% rename from elixir/apps/web/lib/web/mailer/beta_email/rest_api_request.text.heex rename to elixir/apps/domain/lib/domain/mailer/beta_email/rest_api_request.text.eex diff --git a/elixir/apps/web/lib/web/mailer/rate_limiter.ex b/elixir/apps/domain/lib/domain/mailer/rate_limiter.ex similarity index 98% rename from elixir/apps/web/lib/web/mailer/rate_limiter.ex rename to elixir/apps/domain/lib/domain/mailer/rate_limiter.ex index 15930a063..3c5e553e7 100644 --- a/elixir/apps/web/lib/web/mailer/rate_limiter.ex +++ b/elixir/apps/domain/lib/domain/mailer/rate_limiter.ex @@ -1,4 +1,4 @@ -defmodule Web.Mailer.RateLimiter do +defmodule Domain.Mailer.RateLimiter do use GenServer @default_ets_table_name __MODULE__.ETS diff --git a/elixir/apps/domain/lib/domain/mailer/sync_email.ex b/elixir/apps/domain/lib/domain/mailer/sync_email.ex new file mode 100644 index 000000000..82c177bd2 --- /dev/null +++ b/elixir/apps/domain/lib/domain/mailer/sync_email.ex @@ -0,0 +1,15 @@ +defmodule Domain.Mailer.SyncEmail do + import Swoosh.Email + import Domain.Mailer + import Phoenix.Template, only: [embed_templates: 2] + + embed_templates "sync_email/*.html", suffix: "_html" + embed_templates "sync_email/*.text", suffix: "_text" + + def sync_error_email(%Domain.Auth.Provider{} = provider, email) do + default_email() + |> subject("Firezone Identity Provider Sync Error") + |> to(email) + |> render_body(__MODULE__, :sync_error, account: provider.account, provider: provider) + end +end diff --git a/elixir/apps/domain/lib/domain/mailer/sync_email/sync_error.html.eex b/elixir/apps/domain/lib/domain/mailer/sync_email/sync_error.html.eex new file mode 100644 index 000000000..a6085469e --- /dev/null +++ b/elixir/apps/domain/lib/domain/mailer/sync_email/sync_error.html.eex @@ -0,0 +1,136 @@ + + + + + + + + + + + Firezone Sync Error + + + +
+
+ + + + +
+ + + + + + + + + + + +
+

+ <%= @provider.name %> Sync Error! +

+

+ <%= @provider.name %> has failed to sync <%= @provider.last_syncs_failed %> times. +

+
+

+ Below is the last sync error message: +

+
<%= @provider.last_sync_error %>
+
+

+
+

+ Identity Provider Details +

+ + + + + + + + + + + + + + + + + +
Account<%= @account.name %>
Account ID<%= @account.id %>
Provider Name<%= @provider.name %>
Provider ID<%= @provider.id %>
+

+
+

+ Please verify that all Identity Provider information entered in to Firezone is correct. If the problem persists, please reach out to Firezone support + using Slack or email. +
+
+ Thanks,
The Firezone Team +

+
+
+

+ Blazing-fast alternative to legacy VPNs +

+

+ Docs + • + Github + • + X +

+
+
+
+
+ + diff --git a/elixir/apps/domain/lib/domain/mailer/sync_email/sync_error.text.eex b/elixir/apps/domain/lib/domain/mailer/sync_email/sync_error.text.eex new file mode 100644 index 000000000..18d80e66e --- /dev/null +++ b/elixir/apps/domain/lib/domain/mailer/sync_email/sync_error.text.eex @@ -0,0 +1,12 @@ +Identity Provider Sync Error! + +An Identity Provider in your Firezone Account has failed to sync <%= @provider.last_syncs_failed%> times. + +The following is the last sync error message: +<%= @provider.last_sync_error %> + +Identity Provider details: + Account: <%= @account.name %> + Account ID: <%= @account.id %> + Provider Name: <%= @provider.name %> + Provider ID: <%= @provider.id %> diff --git a/elixir/apps/domain/mix.exs b/elixir/apps/domain/mix.exs index d5e59795f..b52edde51 100644 --- a/elixir/apps/domain/mix.exs +++ b/elixir/apps/domain/mix.exs @@ -64,6 +64,18 @@ defmodule Domain.MixProject do # Erlang Clustering {:libcluster, "~> 3.3"}, + # CLDR and unit conversions + {:ex_cldr_dates_times, "~> 2.13"}, + {:ex_cldr_numbers, "~> 2.31"}, + {:ex_cldr, "~> 2.38"}, + + # Mailer deps + {:gen_smtp, "~> 1.0"}, + {:multipart, "~> 0.4.0"}, + {:phoenix_html, "~> 4.0"}, + {:phoenix_swoosh, "~> 1.0"}, + {:phoenix_template, "~> 1.0.4"}, + # Observability and Runtime debugging {:bandit, "~> 1.0"}, {:plug, "~> 1.15"}, diff --git a/elixir/apps/domain/priv/repo/migrations/20240823195642_add_sync_error_email_to_provider.exs b/elixir/apps/domain/priv/repo/migrations/20240823195642_add_sync_error_email_to_provider.exs new file mode 100644 index 000000000..7bc98f30a --- /dev/null +++ b/elixir/apps/domain/priv/repo/migrations/20240823195642_add_sync_error_email_to_provider.exs @@ -0,0 +1,9 @@ +defmodule Domain.Repo.Migrations.AddSyncErrorEmailToProvider do + use Ecto.Migration + + def change do + alter table(:auth_providers) do + add(:sync_error_emailed_at, :utc_datetime_usec) + end + end +end diff --git a/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs index 1ac3e3763..cb9959996 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/google_workspace/jobs/sync_directory_test.exs @@ -774,5 +774,75 @@ defmodule Domain.Auth.Adapters.GoogleWorkspace.Jobs.SyncDirectoryTest do cancel_bypass_expectations_check(bypass) end + + test "sends email on failed directory sync", %{account: account} do + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + _identity = Fixtures.Auth.create_identity(account: account, actor: actor) + + error_message = + "Admin SDK API has not been used in project XXXX before or it is disabled. " <> + "Enable it by visiting https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=XXXX " <> + "then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry." + + response = %{ + "error" => %{ + "code" => 403, + "message" => error_message, + "errors" => [ + %{ + "message" => error_message, + "domain" => "usageLimits", + "reason" => "accessNotConfigured", + "extendedHelp" => "https://console.developers.google.com" + } + ], + "status" => "PERMISSION_DENIED", + "details" => [ + %{ + "@type" => "type.googleapis.com/google.rpc.Help", + "links" => [ + %{ + "description" => "Google developers console API activation", + "url" => + "https://console.developers.google.com/apis/api/admin.googleapis.com/overview?project=100421656358" + } + ] + }, + %{ + "@type" => "type.googleapis.com/google.rpc.ErrorInfo", + "reason" => "SERVICE_DISABLED", + "domain" => "googleapis.com", + "metadata" => %{ + "service" => "admin.googleapis.com", + "consumer" => "projects/100421656358" + } + } + ] + } + } + + bypass = Bypass.open() + GoogleWorkspaceDirectory.override_endpoint_url("http://localhost:#{bypass.port}/") + + for path <- [ + "/admin/directory/v1/users", + "/admin/directory/v1/customer/my_customer/orgunits", + "/admin/directory/v1/groups" + ] do + Bypass.stub(bypass, "GET", path, fn conn -> + Plug.Conn.send_resp(conn, 403, Jason.encode!(response)) + end) + end + + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok + + assert_email_sent(fn email -> + assert email.subject == "Firezone Identity Provider Sync Error" + assert email.text_body =~ "failed to sync 1 times" + end) + + cancel_bypass_expectations_check(bypass) + end end end diff --git a/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs index 6d8dbf4c1..bbc30b437 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/jumpcloud/jobs/sync_directory_test.exs @@ -561,5 +561,36 @@ defmodule Domain.Auth.Adapters.JumpCloud.Jobs.SyncDirectoryTest do cancel_bypass_expectations_check(bypass) end + + test "sends email on failed directory sync", %{account: account} do + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + _identity = Fixtures.Auth.create_identity(account: account, actor: actor) + + bypass = Bypass.open() + + WorkOSDirectory.override_base_url("http://localhost:#{bypass.port}") + + for path <- [ + "/directories", + "/directory_users", + "/directory_groups" + ] do + Bypass.stub(bypass, "GET", path, fn conn -> + conn + |> Plug.Conn.prepend_resp_headers([{"content-type", "application/json"}]) + |> Plug.Conn.send_resp(500, Jason.encode!(%{})) + end) + end + + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok + + assert_email_sent(fn email -> + assert email.subject == "Firezone Identity Provider Sync Error" + assert email.text_body =~ "failed to sync 1 times" + end) + + cancel_bypass_expectations_check(bypass) + end end end diff --git a/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs b/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs index 4a02e4583..6234010fd 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/microsoft_entra/jobs/sync_directory_test.exs @@ -483,5 +483,32 @@ defmodule Domain.Auth.Adapters.MicrosoftEntra.Jobs.SyncDirectoryTest do cancel_bypass_expectations_check(bypass) end + + test "sends email on failed directory sync", %{account: account} do + bypass = Bypass.open() + MicrosoftEntraDirectory.override_endpoint_url("http://localhost:#{bypass.port}/") + + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + _identity = Fixtures.Auth.create_identity(account: account, actor: actor) + + for path <- [ + "v1.0/users", + "v1.0/groups" + ] do + Bypass.stub(bypass, "GET", path, fn conn -> + Plug.Conn.send_resp(conn, 500, "") + end) + end + + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok + + assert_email_sent(fn email -> + assert email.subject == "Firezone Identity Provider Sync Error" + assert email.text_body =~ "failed to sync 1 times" + end) + + cancel_bypass_expectations_check(bypass) + end end end diff --git a/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory.exs b/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs similarity index 93% rename from elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory.exs rename to elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs index 53d886092..d7b0a3ba3 100644 --- a/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory.exs +++ b/elixir/apps/domain/test/domain/auth/adapters/okta/jobs/sync_directory_test.exs @@ -33,8 +33,6 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do end test "syncs IdP data", %{provider: provider, bypass: bypass} do - # bypass = Bypass.open(port: bypass.port) - groups = [ %{ "id" => "GROUP_DEVOPS_ID", @@ -243,7 +241,8 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do OktaDirectory.mock_group_members_list_endpoint(bypass, group["id"], members) end) - assert execute(%{}) == :ok + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok groups = Actors.Group |> Repo.all() assert length(groups) == 2 @@ -287,7 +286,8 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do test "does not crash on endpoint errors", %{bypass: bypass} do Bypass.down(bypass) - assert execute(%{}) == :ok + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok assert Repo.aggregate(Actors.Group, :count) == 0 end @@ -337,7 +337,8 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do OktaDirectory.mock_groups_list_endpoint(bypass, []) OktaDirectory.mock_users_list_endpoint(bypass, users) - assert execute(%{}) == :ok + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok assert updated_identity = Repo.get(Domain.Auth.Identity, identity.id) @@ -666,7 +667,8 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do one_member ) - assert execute(%{}) == :ok + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok assert updated_group = Repo.get(Domain.Actors.Group, group.id) assert updated_group.name == "Group:Engineering" @@ -758,7 +760,8 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do end) end - assert execute(%{}) == :ok + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok assert updated_provider = Repo.get(Domain.Auth.Provider, provider.id) refute updated_provider.last_synced_at @@ -774,7 +777,8 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do end) end - assert execute(%{}) == :ok + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok assert updated_provider = Repo.get(Domain.Auth.Provider, provider.id) refute updated_provider.last_synced_at @@ -783,5 +787,40 @@ defmodule Domain.Auth.Adapters.Okta.Jobs.SyncDirectoryTest do cancel_bypass_expectations_check(bypass) end + + test "sends email on failed directory sync", %{ + account: account, + bypass: bypass + } do + actor = Fixtures.Actors.create_actor(type: :account_admin_user, account: account) + _identity = Fixtures.Auth.create_identity(account: account, actor: actor) + + response = %{ + "errorCode" => "E0000011", + "errorSummary" => "Invalid token provided", + "errorLink" => "E0000011", + "errorId" => "sampleU-5P2FZVslkYBMP_Rsq", + "errorCauses" => [] + } + + for path <- [ + "api/v1/users", + "api/v1/groups" + ] do + Bypass.stub(bypass, "GET", path, fn conn -> + Plug.Conn.send_resp(conn, 401, Jason.encode!(response)) + end) + end + + {:ok, pid} = Task.Supervisor.start_link() + assert execute(%{task_supervisor: pid}) == :ok + + assert_email_sent(fn email -> + assert email.subject == "Firezone Identity Provider Sync Error" + assert email.text_body =~ "failed to sync 1 times" + end) + + cancel_bypass_expectations_check(bypass) + end end end diff --git a/elixir/apps/web/test/web/mailer/auth_email_test.exs b/elixir/apps/domain/test/domain/mailer/auth_email_test.exs similarity index 93% rename from elixir/apps/web/test/web/mailer/auth_email_test.exs rename to elixir/apps/domain/test/domain/mailer/auth_email_test.exs index 6152d2cda..32ccb8074 100644 --- a/elixir/apps/web/test/web/mailer/auth_email_test.exs +++ b/elixir/apps/domain/test/domain/mailer/auth_email_test.exs @@ -1,6 +1,6 @@ -defmodule Web.Mailer.AuthEmailTest do - use Web.ConnCase, async: true - import Web.Mailer.AuthEmail +defmodule Domain.Mailer.AuthEmailTest do + use Domain.DataCase, async: true + import Domain.Mailer.AuthEmail setup do Domain.Config.put_env_override(:outbound_email_adapter_configured?, true) diff --git a/elixir/apps/web/test/web/mailer/rate_limiter_test.exs b/elixir/apps/domain/test/domain/mailer/rate_limiter_test.exs similarity index 97% rename from elixir/apps/web/test/web/mailer/rate_limiter_test.exs rename to elixir/apps/domain/test/domain/mailer/rate_limiter_test.exs index cc8f28c25..31593012f 100644 --- a/elixir/apps/web/test/web/mailer/rate_limiter_test.exs +++ b/elixir/apps/domain/test/domain/mailer/rate_limiter_test.exs @@ -1,6 +1,6 @@ -defmodule Web.Mailer.RateLimiterTest do +defmodule Domain.Mailer.RateLimiterTest do use ExUnit.Case, async: true - import Web.Mailer.RateLimiter + import Domain.Mailer.RateLimiter describe "init/1" do test "creates a ETS table" do diff --git a/elixir/apps/domain/test/domain/mailer/sync_error_email_test.exs b/elixir/apps/domain/test/domain/mailer/sync_error_email_test.exs new file mode 100644 index 000000000..0cbad69fe --- /dev/null +++ b/elixir/apps/domain/test/domain/mailer/sync_error_email_test.exs @@ -0,0 +1,37 @@ +defmodule Domain.Mailer.SyncErrorEmailTest do + use Domain.DataCase, async: true + import Domain.Mailer.SyncEmail + + setup do + account = Fixtures.Accounts.create_account() + {provider, _bypass} = Fixtures.Auth.start_and_create_okta_provider(account: account) + + %{ + account: account, + provider: provider + } + end + + describe "sync_error_email/2" do + test "should contain sync error info", %{provider: provider} do + admin_email = "admin@foo.local" + expected_msg = "403 - Forbidden" + + provider = + provider + |> Domain.Repo.preload(:account) + |> set_provider_failure("Error while syncing") + |> set_provider_failure(expected_msg) + + email_body = sync_error_email(provider, admin_email) + + assert email_body.text_body =~ "2 times" + assert email_body.text_body =~ expected_msg + end + end + + defp set_provider_failure(provider, message) do + Domain.Auth.Provider.Changeset.sync_failed(provider, message) + |> Domain.Repo.update!() + end +end diff --git a/elixir/apps/web/test/web/mailer_test.exs b/elixir/apps/domain/test/domain/mailer_test.exs similarity index 93% rename from elixir/apps/web/test/web/mailer_test.exs rename to elixir/apps/domain/test/domain/mailer_test.exs index 6c118370f..dfbec4d71 100644 --- a/elixir/apps/web/test/web/mailer_test.exs +++ b/elixir/apps/domain/test/domain/mailer_test.exs @@ -1,6 +1,6 @@ -defmodule Web.MailerTest do +defmodule Domain.MailerTest do use ExUnit.Case, async: true - import Web.Mailer + import Domain.Mailer describe "deliver_with_rate_limit/2" do test "delivers email with rate limit" do diff --git a/elixir/apps/domain/test/support/data_case.ex b/elixir/apps/domain/test/support/data_case.ex index 05af40bc9..37e16844b 100644 --- a/elixir/apps/domain/test/support/data_case.ex +++ b/elixir/apps/domain/test/support/data_case.ex @@ -20,6 +20,7 @@ defmodule Domain.DataCase do quote do import Ecto import Ecto.Changeset + import Swoosh.TestAssertions import Domain.DataCase alias Domain.Repo diff --git a/elixir/apps/web/test/support/mailer/mailer_test_adapter.ex b/elixir/apps/domain/test/support/mailer/mailer_test_adapter.ex similarity index 89% rename from elixir/apps/web/test/support/mailer/mailer_test_adapter.ex rename to elixir/apps/domain/test/support/mailer/mailer_test_adapter.ex index d39e756d7..6fd62db13 100644 --- a/elixir/apps/web/test/support/mailer/mailer_test_adapter.ex +++ b/elixir/apps/domain/test/support/mailer/mailer_test_adapter.ex @@ -1,4 +1,4 @@ -defmodule Web.Mailer.TestAdapter do +defmodule Domain.Mailer.TestAdapter do use Swoosh.Adapter @impl true diff --git a/elixir/apps/web/.formatter.exs b/elixir/apps/web/.formatter.exs index fe4eec393..4978f4996 100644 --- a/elixir/apps/web/.formatter.exs +++ b/elixir/apps/web/.formatter.exs @@ -1,7 +1,10 @@ [ import_deps: [:phoenix, :phoenix_live_view], plugins: [Phoenix.LiveView.HTMLFormatter], - inputs: ["*.{xml.heex,html.heex,ex,exs}", "{config,lib,test}/**/*.{xml.heex,html.heex,ex,exs}"], + inputs: [ + "*.{xml.heex,html.heex,ex,exs}", + "{config,lib,test}/**/*.{xml.heex,html.heex,ex,exs}" + ], locals_without_parens: [ assert_authenticated: 2, assert_unauthenticated: 1, diff --git a/elixir/apps/web/lib/web/application.ex b/elixir/apps/web/lib/web/application.ex index ab2e3d77e..c0d740891 100644 --- a/elixir/apps/web/lib/web/application.ex +++ b/elixir/apps/web/lib/web/application.ex @@ -8,8 +8,6 @@ defmodule Web.Application do _ = OpentelemetryPhoenix.setup(adapter: :cowboy2) children = [ - Web.Mailer, - Web.Mailer.RateLimiter, Web.Endpoint ] diff --git a/elixir/apps/web/lib/web/components/core_components.ex b/elixir/apps/web/lib/web/components/core_components.ex index 3fdfb67b3..a9d6447b8 100644 --- a/elixir/apps/web/lib/web/components/core_components.ex +++ b/elixir/apps/web/lib/web/components/core_components.ex @@ -1044,16 +1044,11 @@ defmodule Web.CoreComponents do end def get_identity_email(identity) do - provider_email(identity) || identity.provider_identifier + Domain.Auth.get_identity_email(identity) end def identity_has_email?(identity) do - not is_nil(provider_email(identity)) or identity.provider.adapter == :email or - identity.provider_identifier =~ "@" - end - - defp provider_email(identity) do - get_in(identity.provider_state, ["userinfo", "email"]) + Domain.Auth.identity_has_email?(identity) end attr :account, :any, required: true diff --git a/elixir/apps/web/lib/web/controllers/auth_controller.ex b/elixir/apps/web/lib/web/controllers/auth_controller.ex index 1f6421c78..86371b567 100644 --- a/elixir/apps/web/lib/web/controllers/auth_controller.ex +++ b/elixir/apps/web/lib/web/controllers/auth_controller.ex @@ -159,14 +159,14 @@ defmodule Web.AuthController do # attacks where you can trick user into logging in into an attacker account. fragment = identity.provider_virtual_state.fragment - Web.Mailer.AuthEmail.sign_in_link_email( + Domain.Mailer.AuthEmail.sign_in_link_email( identity, nonce, conn.assigns.user_agent, conn.remote_ip, redirect_params ) - |> Web.Mailer.deliver_with_rate_limit( + |> Domain.Mailer.deliver_with_rate_limit( rate_limit_key: {:sign_in_link, identity.id}, rate_limit: 3, rate_limit_interval: :timer.minutes(5) @@ -207,7 +207,7 @@ defmodule Web.AuthController do with {:ok, provider} <- Domain.Auth.fetch_active_provider_by_id(provider_id), {:ok, identity, encoded_fragment} <- Domain.Auth.sign_in(provider, identity_id, nonce, secret, context) do - :ok = Web.Mailer.RateLimiter.reset_rate_limit({:sign_in_link, identity.id}) + :ok = Domain.Mailer.RateLimiter.reset_rate_limit({:sign_in_link, identity.id}) Web.Auth.signed_in(conn, provider, identity, context, encoded_fragment, redirect_params) else {:error, :not_found} -> diff --git a/elixir/apps/web/lib/web/live/actors/show.ex b/elixir/apps/web/lib/web/live/actors/show.ex index bbeb5ff1e..10a7cd10c 100644 --- a/elixir/apps/web/lib/web/live/actors/show.ex +++ b/elixir/apps/web/lib/web/live/actors/show.ex @@ -636,12 +636,12 @@ defmodule Web.Actors.Show do def handle_event("send_welcome_email", %{"id" => id}, socket) do {:ok, identity} = Auth.fetch_identity_by_id(id, socket.assigns.subject) - Web.Mailer.AuthEmail.new_user_email( + Domain.Mailer.AuthEmail.new_user_email( socket.assigns.account, identity, socket.assigns.subject ) - |> Web.Mailer.deliver_with_rate_limit( + |> Domain.Mailer.deliver_with_rate_limit( rate_limit: 3, rate_limit_key: {:welcome_email, identity.id}, rate_limit_interval: :timer.minutes(3) diff --git a/elixir/apps/web/lib/web/live/actors/users/new_identity.ex b/elixir/apps/web/lib/web/live/actors/users/new_identity.ex index 4a65db196..9c1846baa 100644 --- a/elixir/apps/web/lib/web/live/actors/users/new_identity.ex +++ b/elixir/apps/web/lib/web/live/actors/users/new_identity.ex @@ -111,12 +111,12 @@ defmodule Web.Actors.Users.NewIdentity do socket.assigns.subject ) do if socket.assigns.provider.adapter == :email do - Web.Mailer.AuthEmail.new_user_email( + Domain.Mailer.AuthEmail.new_user_email( socket.assigns.account, identity, socket.assigns.subject ) - |> Web.Mailer.deliver() + |> Domain.Mailer.deliver() end socket = push_navigate(socket, to: next_path(socket)) diff --git a/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex b/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex index db2c2dd88..40292c526 100644 --- a/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex +++ b/elixir/apps/web/lib/web/live/settings/api_clients/beta.ex @@ -62,11 +62,11 @@ defmodule Web.Settings.ApiClients.Beta do end def handle_event("request_access", _params, socket) do - Web.Mailer.BetaEmail.rest_api_beta_email( + Domain.Mailer.BetaEmail.rest_api_beta_email( socket.assigns.account, socket.assigns.subject ) - |> Web.Mailer.deliver() + |> Domain.Mailer.deliver() socket = socket diff --git a/elixir/apps/web/lib/web/live/sign_up.ex b/elixir/apps/web/lib/web/live/sign_up.ex index b9fc2968d..1d841e163 100644 --- a/elixir/apps/web/lib/web/live/sign_up.ex +++ b/elixir/apps/web/lib/web/live/sign_up.ex @@ -469,13 +469,13 @@ defmodule Web.SignUp do |> Ecto.Multi.run( :send_email, fn _repo, %{account: account, identity: identity} -> - Web.Mailer.AuthEmail.sign_up_link_email( + Domain.Mailer.AuthEmail.sign_up_link_email( account, identity, socket.assigns.user_agent, socket.assigns.real_ip ) - |> Web.Mailer.deliver_with_rate_limit( + |> Domain.Mailer.deliver_with_rate_limit( rate_limit_key: {:sign_up_link, String.downcase(identity.provider_identifier)}, rate_limit: 3, rate_limit_interval: :timer.minutes(30) diff --git a/elixir/apps/web/mix.exs b/elixir/apps/web/mix.exs index ee57cbbc5..01201a91c 100644 --- a/elixir/apps/web/mix.exs +++ b/elixir/apps/web/mix.exs @@ -48,10 +48,7 @@ defmodule Web.MixProject do {:gettext, "~> 0.20"}, {:remote_ip, "~> 1.0"}, - # CLDR and unit conversions - {:ex_cldr_dates_times, "~> 2.13"}, - {:ex_cldr_numbers, "~> 2.31"}, - {:ex_cldr, "~> 2.40"}, + # Unit conversions {:tzdata, "~> 1.1"}, {:sizeable, "~> 1.0"}, @@ -65,11 +62,6 @@ defmodule Web.MixProject do {:recon, "~> 2.5"}, {:observer_cli, "~> 1.7"}, - # Mailer deps - {:multipart, "~> 0.4.0"}, - {:phoenix_swoosh, "~> 1.0"}, - {:gen_smtp, "~> 1.0"}, - # Observability {:opentelemetry_telemetry, "~> 1.1.1", override: true}, {:opentelemetry_cowboy, "~> 0.3"}, 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 262b46498..5ccdfa5df 100644 --- a/elixir/apps/web/test/web/controllers/auth_controller_test.exs +++ b/elixir/apps/web/test/web/controllers/auth_controller_test.exs @@ -779,7 +779,7 @@ defmodule Web.AuthControllerTest do email_secret: email_secret } do key = {:sign_in_link, identity.id} - Web.Mailer.RateLimiter.rate_limit(key, 3, 60_000, fn -> :ok end) + Domain.Mailer.RateLimiter.rate_limit(key, 3, 60_000, fn -> :ok end) conn = conn @@ -791,7 +791,7 @@ defmodule Web.AuthControllerTest do assert conn.assigns.flash == %{} assert redirected_to(conn) == ~p"/#{account}/sites" - refute :ets.tab2list(Web.Mailer.RateLimiter.ETS) + refute :ets.tab2list(Domain.Mailer.RateLimiter.ETS) |> Enum.any?(fn {ets_key, _, _} -> ets_key == key end) end end diff --git a/elixir/config/config.exs b/elixir/config/config.exs index 7a3fe7e33..c7bb12943 100644 --- a/elixir/config/config.exs +++ b/elixir/config/config.exs @@ -100,6 +100,8 @@ config :domain, docker_registry: "us-east1-docker.pkg.dev/firezone-staging/firez config :domain, outbound_email_adapter_configured?: false +config :domain, web_external_url: "http://localhost:13000" + ############################### ##### Web ##################### ############################### @@ -213,7 +215,7 @@ config :phoenix, :json_library, Jason config :swoosh, :api_client, Swoosh.ApiClient.Finch -config :web, Web.Mailer, +config :domain, Domain.Mailer, adapter: Domain.Mailer.NoopAdapter, from_email: "test@firez.one" diff --git a/elixir/config/dev.exs b/elixir/config/dev.exs index 48391e01f..0b8499306 100644 --- a/elixir/config/dev.exs +++ b/elixir/config/dev.exs @@ -69,7 +69,7 @@ config :phoenix_live_reload, :dirs, [ config :web, Web.Plugs.SecureHeaders, csp_policy: [ "default-src 'self' 'nonce-${nonce}' https://api-js.mixpanel.com", - "img-src 'self' data: https://www.gravatar.com https://track.hubspot.com", + "img-src 'self' data: https://www.gravatar.com https://track.hubspot.com https://www.firezone.dev", "style-src 'self' 'unsafe-inline'", "script-src 'self' 'unsafe-inline' http://cdn.mxpnl.com http://*.hs-analytics.net https://cdn.tailwindcss.com/" ] @@ -109,7 +109,7 @@ config :phoenix, :stacktrace_depth, 20 # Initialize plugs at runtime for faster development compilation config :phoenix, :plug_init_mode, :runtime -config :web, Web.Mailer, adapter: Swoosh.Adapters.Local +config :domain, Domain.Mailer, adapter: Swoosh.Adapters.Local config :workos, WorkOS.Client, api_key: System.get_env("WORKOS_API_KEY"), diff --git a/elixir/config/runtime.exs b/elixir/config/runtime.exs index 08f601623..15037e0a3 100644 --- a/elixir/config/runtime.exs +++ b/elixir/config/runtime.exs @@ -73,6 +73,8 @@ if config_env() == :prod do config :domain, outbound_email_adapter_configured?: !!compile_config!(:outbound_email_adapter) + config :domain, web_external_url: compile_config!(:web_external_url) + # Enable background jobs only on dedicated nodes config :domain, Domain.Tokens.Jobs.DeleteExpiredTokens, enabled: compile_config!(:background_jobs_enabled) @@ -221,8 +223,8 @@ if config_env() == :prod do config :openid_connect, finch_transport_opts: compile_config!(:http_client_ssl_opts) - config :web, - Web.Mailer, + config :domain, + Domain.Mailer, [ adapter: compile_config!(:outbound_email_adapter), from_email: compile_config!(:outbound_email_from) diff --git a/elixir/config/test.exs b/elixir/config/test.exs index cb3339e91..2a17e49e7 100644 --- a/elixir/config/test.exs +++ b/elixir/config/test.exs @@ -28,6 +28,8 @@ config :domain, Domain.GoogleCloudPlatform, service_account_email: "foo@iam.exam config :domain, Domain.Telemetry.GoogleCloudMetricsReporter, project_id: "fz-test" +config :domain, web_external_url: "http://localhost:13100" + ############################### ##### Web ##################### ############################### @@ -57,7 +59,7 @@ config :api, API.Endpoint, ############################### ##### Third-party configs ##### ############################### -config :web, Web.Mailer, adapter: Web.Mailer.TestAdapter +config :domain, Domain.Mailer, adapter: Domain.Mailer.TestAdapter config :logger, level: :warning