diff --git a/elixir/apps/web/lib/web/live/actors/show.ex b/elixir/apps/web/lib/web/live/actors/show.ex index c6c88bfb7..ab1f3beeb 100644 --- a/elixir/apps/web/lib/web/live/actors/show.ex +++ b/elixir/apps/web/lib/web/live/actors/show.ex @@ -8,13 +8,27 @@ defmodule Web.Actors.Show do def mount(%{"id" => id}, _session, socket) do with {:ok, actor} <- Actors.fetch_actor_by_id(id, socket.assigns.subject, - preload: [:last_seen_at, groups: [:provider]] + preload: [:identities, :last_seen_at, groups: [:provider]] ) do :ok = Clients.subscribe_to_clients_presence_for_actor(actor) + available_providers = + Auth.all_active_providers_for_account!(socket.assigns.account) + |> Enum.filter(fn provider -> + # TODO: This will be refactored to enforce with a DB constraint, but for now + # we don't allow creating multiple identities per actor for the same provider. + Enum.all?(actor.identities, fn identity -> + identity.provider_id != provider.id + end) and + Auth.fetch_provider_capabilities!(provider) + |> Keyword.fetch!(:provisioners) + |> Enum.member?(:manual) + end) + socket = socket |> assign( + available_providers: available_providers, page_title: "Actor #{actor.name}", flow_activities_enabled?: Accounts.flow_activities_enabled?(socket.assigns.account), actor: actor @@ -232,7 +246,7 @@ defmodule Web.Actors.Show do Each authentication identity is associated with an identity provider and is used to identify the actor upon successful authentication. - <:action :if={is_nil(@actor.deleted_at)}> + <:action :if={is_nil(@actor.deleted_at) and Enum.any?(@available_providers)}> <.add_button :if={@actor.type != :service_account} navigate={~p"/#{@account}/actors/users/#{@actor}/new_identity"} @@ -664,8 +678,27 @@ defmodule Web.Actors.Show do {:ok, identity} = Auth.fetch_identity_by_id(id, socket.assigns.subject) {:ok, _identity} = Auth.delete_identity(identity, socket.assigns.subject) + {:ok, actor} = + Actors.fetch_actor_by_id(socket.assigns.actor.id, socket.assigns.subject, + preload: [:identities] + ) + + available_providers = + Auth.all_active_providers_for_account!(socket.assigns.account) + |> Enum.filter(fn provider -> + # TODO: This will be refactored to enforce with a DB constraint, but for now + # we don't allow creating multiple identities per actor for the same provider. + Enum.all?(actor.identities, fn identity -> + identity.provider_id != provider.id + end) and + Auth.fetch_provider_capabilities!(provider) + |> Keyword.fetch!(:provisioners) + |> Enum.member?(:manual) + end) + socket = socket + |> assign(actor: actor, available_providers: available_providers) |> reload_live_table!("identities") |> put_flash(:info, "Identity was deleted.") 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 89b44e1c2..aa55f45c6 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 @@ -6,7 +6,7 @@ defmodule Web.Actors.Users.NewIdentity do def mount(%{"id" => id} = params, _session, socket) do with {:ok, actor} <- Actors.fetch_actor_by_id(id, socket.assigns.subject, - preload: [:memberships], + preload: [:memberships, :identities], filter: [ deleted?: false, types: ["account_user", "account_admin_user"] @@ -15,9 +15,14 @@ defmodule Web.Actors.Users.NewIdentity do providers = Auth.all_active_providers_for_account!(socket.assigns.account) |> Enum.filter(fn provider -> - Auth.fetch_provider_capabilities!(provider) - |> Keyword.fetch!(:provisioners) - |> Enum.member?(:manual) + # TODO: This will be refactored to enforce with a DB constraint, but for now + # we don't allow creating multiple identities per actor for the same provider. + Enum.all?(actor.identities, fn identity -> + identity.provider_id != provider.id + end) and + Auth.fetch_provider_capabilities!(provider) + |> Keyword.fetch!(:provisioners) + |> Enum.member?(:manual) end) provider = List.first(providers) diff --git a/elixir/apps/web/test/web/live/actors/users/new_identity_test.exs b/elixir/apps/web/test/web/live/actors/users/new_identity_test.exs index 7f1f34d5f..4333b88a1 100644 --- a/elixir/apps/web/test/web/live/actors/users/new_identity_test.exs +++ b/elixir/apps/web/test/web/live/actors/users/new_identity_test.exs @@ -5,16 +5,22 @@ defmodule Web.Live.Actors.User.NewIdentityTest do Domain.Config.put_env_override(:outbound_email_adapter_configured?, true) account = Fixtures.Accounts.create_account() - provider = Fixtures.Auth.create_email_provider(account: account) + _provider = Fixtures.Auth.create_email_provider(account: account) + + # TODO: Users won't be able to naturally arrive at some of the routes tested on this page without another + # manual provisioning provider like OIDC, so we add it here. Clean this up when identities are refactored. + {oidc_provider, _bypass} = + Fixtures.Auth.start_and_create_openid_connect_provider(account: account) actor = Fixtures.Actors.create_actor( type: :account_admin_user, account: account, - provider: provider + provider: oidc_provider ) - identity = Fixtures.Auth.create_identity(account: account, provider: provider, actor: actor) + identity = + Fixtures.Auth.create_identity(account: account, provider: oidc_provider, actor: actor) %{ account: account,